diff --git a/.github/actions/install-rust/action.yml b/.github/actions/install-rust/action.yml index 73f378dd1d..0bb3bb67d5 100644 --- a/.github/actions/install-rust/action.yml +++ b/.github/actions/install-rust/action.yml @@ -28,7 +28,7 @@ runs: elif [ "${{ inputs.toolchain }}" = "msrv" ]; then echo "version=1.$msrv.0" >> "$GITHUB_OUTPUT" elif [ "${{ inputs.toolchain }}" = "wasmtime-ci-pinned-nightly" ]; then - echo "version=nightly-2025-07-08" >> "$GITHUB_OUTPUT" + echo "version=nightly-2025-08-06" >> "$GITHUB_OUTPUT" else echo "version=${{ inputs.toolchain }}" >> "$GITHUB_OUTPUT" fi diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 65c54b7857..105e313a6e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -398,6 +398,14 @@ jobs: - name: wasmtime-wasi-http checks: | -p wasmtime-wasi-http --no-default-features + + - name: wasmtime-wasi + checks: | + -p wasmtime-wasi --no-default-features + -p wasmtime-wasi --no-default-features --features p0 + -p wasmtime-wasi --no-default-features --features p1 + -p wasmtime-wasi --no-default-features --features p2 + -p wasmtime-wasi --no-default-features --features p3 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -582,10 +590,8 @@ jobs: with: toolchain: wasmtime-ci-pinned-nightly - # Check that `pulley-interpreter` compiles with tail calls enabled. Don't - # actually run the tests with tail calls enabled, because they are not yet - # implemented in rustc and cause an ICE. - - run: cargo check -p pulley-interpreter --all-features + # Check that `pulley-interpreter` works with tail calls enabled. + - run: cargo test -p pulley-interpreter --all-features env: RUSTFLAGS: "--cfg pulley_tail_calls" - run: cargo check -p pulley-interpreter --all-features diff --git a/Cargo.lock b/Cargo.lock index f8c2277c50..f62977ed3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,7 +289,7 @@ dependencies = [ [[package]] name = "byte-array-literals" -version = "36.0.0" +version = "37.0.0" [[package]] name = "byteorder" @@ -693,7 +693,7 @@ dependencies = [ [[package]] name = "cranelift" -version = "0.123.0" +version = "0.124.0" dependencies = [ "cranelift-codegen", "cranelift-frontend", @@ -706,7 +706,7 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.123.0" +version = "0.124.0" dependencies = [ "arbitrary", "arbtest", @@ -724,21 +724,21 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64-meta" -version = "0.123.0" +version = "0.124.0" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.123.0" +version = "0.124.0" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.123.0" +version = "0.124.0" dependencies = [ "arbitrary", "serde", @@ -747,7 +747,7 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.123.0" +version = "0.124.0" dependencies = [ "anyhow", "bumpalo", @@ -782,7 +782,7 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.123.0" +version = "0.124.0" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -793,18 +793,18 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.123.0" +version = "0.124.0" [[package]] name = "cranelift-control" -version = "0.123.0" +version = "0.124.0" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.123.0" +version = "0.124.0" dependencies = [ "cranelift-bitset", "serde", @@ -844,7 +844,7 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.123.0" +version = "0.124.0" dependencies = [ "cranelift-codegen", "env_logger 0.11.5", @@ -869,7 +869,7 @@ dependencies = [ [[package]] name = "cranelift-interpreter" -version = "0.123.0" +version = "0.124.0" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -883,7 +883,7 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.123.0" +version = "0.124.0" dependencies = [ "codespan-reporting", "log", @@ -892,7 +892,7 @@ dependencies = [ [[package]] name = "cranelift-jit" -version = "0.123.0" +version = "0.124.0" dependencies = [ "anyhow", "cranelift", @@ -914,7 +914,7 @@ dependencies = [ [[package]] name = "cranelift-module" -version = "0.123.0" +version = "0.124.0" dependencies = [ "anyhow", "cranelift-codegen", @@ -926,7 +926,7 @@ dependencies = [ [[package]] name = "cranelift-native" -version = "0.123.0" +version = "0.124.0" dependencies = [ "cranelift-codegen", "libc", @@ -935,7 +935,7 @@ dependencies = [ [[package]] name = "cranelift-object" -version = "0.123.0" +version = "0.124.0" dependencies = [ "anyhow", "cranelift-codegen", @@ -950,7 +950,7 @@ dependencies = [ [[package]] name = "cranelift-reader" -version = "0.123.0" +version = "0.124.0" dependencies = [ "anyhow", "cranelift-codegen", @@ -960,7 +960,7 @@ dependencies = [ [[package]] name = "cranelift-serde" -version = "0.123.0" +version = "0.124.0" dependencies = [ "clap", "cranelift-codegen", @@ -970,7 +970,7 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.123.0" +version = "0.124.0" [[package]] name = "cranelift-tools" @@ -1221,7 +1221,7 @@ checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" [[package]] name = "embedding" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "dlmalloc", @@ -2373,7 +2373,7 @@ dependencies = [ [[package]] name = "min-platform-host" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "libloading", @@ -2816,7 +2816,7 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "arbitrary", @@ -2840,7 +2840,7 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "36.0.0" +version = "37.0.0" dependencies = [ "proc-macro2", "quote", @@ -4066,7 +4066,7 @@ version = "0.1.0" [[package]] name = "verify-component-adapter" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "wasmparser 0.236.0", @@ -4134,7 +4134,7 @@ dependencies = [ [[package]] name = "wasi-common" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "bitflags 2.6.0", @@ -4173,7 +4173,7 @@ dependencies = [ [[package]] name = "wasi-preview1-component-adapter" -version = "36.0.0" +version = "37.0.0" dependencies = [ "bitflags 2.6.0", "byte-array-literals", @@ -4444,7 +4444,7 @@ dependencies = [ [[package]] name = "wasmtime" -version = "36.0.0" +version = "37.0.0" dependencies = [ "addr2line 0.25.0", "anyhow", @@ -4483,7 +4483,7 @@ dependencies = [ "smallvec", "target-lexicon", "tempfile", - "wasi-common", + "tokio", "wasm-encoder 0.236.0", "wasm-wave", "wasmparser 0.236.0", @@ -4509,30 +4509,30 @@ dependencies = [ [[package]] name = "wasmtime-bench-api" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "cap-std", "clap", "shuffling-allocator", "target-lexicon", - "wasi-common", "wasmtime", "wasmtime-cli-flags", + "wasmtime-wasi", "wasmtime-wasi-nn", "wat", ] [[package]] name = "wasmtime-c-api" -version = "36.0.0" +version = "37.0.0" dependencies = [ "wasmtime-c-api-impl", ] [[package]] name = "wasmtime-c-api-impl" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "cap-std", @@ -4549,7 +4549,7 @@ dependencies = [ [[package]] name = "wasmtime-cli" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "async-trait", @@ -4622,7 +4622,7 @@ dependencies = [ [[package]] name = "wasmtime-cli-flags" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "clap", @@ -4637,7 +4637,7 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "clap", @@ -4743,14 +4743,14 @@ dependencies = [ [[package]] name = "wasmtime-internal-asm-macros" -version = "36.0.0" +version = "37.0.0" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-internal-c-api-macros" -version = "36.0.0" +version = "37.0.0" dependencies = [ "proc-macro2", "quote", @@ -4758,7 +4758,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-cache" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "base64", @@ -4779,7 +4779,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-macro" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "component-macro-test-helpers", @@ -4799,11 +4799,11 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-util" -version = "36.0.0" +version = "37.0.0" [[package]] name = "wasmtime-internal-cranelift" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "cfg-if", @@ -4828,7 +4828,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-explorer" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "capstone", @@ -4843,7 +4843,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "backtrace", @@ -4858,7 +4858,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "36.0.0" +version = "37.0.0" dependencies = [ "cc", "object 0.37.1", @@ -4868,7 +4868,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "cfg-if", @@ -4878,18 +4878,18 @@ dependencies = [ [[package]] name = "wasmtime-internal-math" -version = "36.0.0" +version = "37.0.0" dependencies = [ "libm", ] [[package]] name = "wasmtime-internal-slab" -version = "36.0.0" +version = "37.0.0" [[package]] name = "wasmtime-internal-unwinder" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "cfg-if", @@ -4900,7 +4900,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "36.0.0" +version = "37.0.0" dependencies = [ "proc-macro2", "quote", @@ -4909,7 +4909,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-winch" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "cranelift-codegen", @@ -4924,7 +4924,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-wit-bindgen" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "bitflags 2.6.0", @@ -4935,7 +4935,7 @@ dependencies = [ [[package]] name = "wasmtime-internal-wmemcheck" -version = "36.0.0" +version = "37.0.0" [[package]] name = "wasmtime-test-macros" @@ -4950,7 +4950,7 @@ dependencies = [ [[package]] name = "wasmtime-test-util" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "arbitrary", @@ -4968,7 +4968,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "async-trait", @@ -5003,7 +5003,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi-config" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "test-programs-artifacts", @@ -5014,7 +5014,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi-http" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "async-trait", @@ -5046,7 +5046,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi-io" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "async-trait", @@ -5057,7 +5057,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi-keyvalue" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "test-programs-artifacts", @@ -5068,7 +5068,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi-nn" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "cap-std", @@ -5089,7 +5089,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi-threads" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "log", @@ -5101,7 +5101,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi-tls" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "bytes", @@ -5117,7 +5117,7 @@ dependencies = [ [[package]] name = "wasmtime-wasi-tls-nativetls" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "futures", @@ -5132,7 +5132,7 @@ dependencies = [ [[package]] name = "wasmtime-wast" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "json-from-wast", @@ -5210,7 +5210,7 @@ dependencies = [ [[package]] name = "wiggle" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "async-trait", @@ -5227,7 +5227,7 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "heck 0.5.0", @@ -5239,7 +5239,7 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "36.0.0" +version = "37.0.0" dependencies = [ "proc-macro2", "quote", @@ -5294,7 +5294,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "36.0.0" +version = "37.0.0" dependencies = [ "anyhow", "cranelift-assembler-x64", diff --git a/Cargo.toml b/Cargo.toml index fcda76f552..b8023b8f6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -174,12 +174,12 @@ exclude = [ ] [workspace.package] -version = "36.0.0" +version = "37.0.0" authors = ["The Wasmtime Project Developers"] edition = "2024" # Wasmtime's current policy is that this number can be no larger than the # current stable release of Rust minus 2. -rust-version = "1.86.0" +rust-version = "1.87.0" [workspace.lints.rust] # Turn on some lints which are otherwise allow-by-default in rustc. @@ -216,6 +216,7 @@ unnecessary_cast = 'warn' allow_attributes_without_reason = 'warn' from_over_into = 'warn' redundant_field_names = 'warn' +multiple_bound_locations = 'warn' [workspace.dependencies] # Public crates related to Wasmtime. @@ -226,19 +227,19 @@ redundant_field_names = 'warn' # tooling but aren't intended to be widely depended on. # # All of these crates are supported though in the sense that -wasmtime = { path = "crates/wasmtime", version = "36.0.0", default-features = false } -wasmtime-cli-flags = { path = "crates/cli-flags", version = "=36.0.0" } -wasmtime-environ = { path = "crates/environ", version = "=36.0.0" } -wasmtime-wasi = { path = "crates/wasi", version = "36.0.0", default-features = false } -wasmtime-wasi-io = { path = "crates/wasi-io", version = "36.0.0", default-features = false } -wasmtime-wasi-http = { path = "crates/wasi-http", version = "36.0.0", default-features = false } -wasmtime-wasi-nn = { path = "crates/wasi-nn", version = "36.0.0" } -wasmtime-wasi-config = { path = "crates/wasi-config", version = "36.0.0" } -wasmtime-wasi-keyvalue = { path = "crates/wasi-keyvalue", version = "36.0.0" } -wasmtime-wasi-threads = { path = "crates/wasi-threads", version = "36.0.0" } -wasmtime-wasi-tls = { path = "crates/wasi-tls", version = "36.0.0" } -wasmtime-wasi-tls-nativetls = { path = "crates/wasi-tls-nativetls", version = "36.0.0" } -wasmtime-wast = { path = "crates/wast", version = "=36.0.0" } +wasmtime = { path = "crates/wasmtime", version = "37.0.0", default-features = false } +wasmtime-cli-flags = { path = "crates/cli-flags", version = "=37.0.0" } +wasmtime-environ = { path = "crates/environ", version = "=37.0.0" } +wasmtime-wasi = { path = "crates/wasi", version = "37.0.0", default-features = false } +wasmtime-wasi-io = { path = "crates/wasi-io", version = "37.0.0", default-features = false } +wasmtime-wasi-http = { path = "crates/wasi-http", version = "37.0.0", default-features = false } +wasmtime-wasi-nn = { path = "crates/wasi-nn", version = "37.0.0" } +wasmtime-wasi-config = { path = "crates/wasi-config", version = "37.0.0" } +wasmtime-wasi-keyvalue = { path = "crates/wasi-keyvalue", version = "37.0.0" } +wasmtime-wasi-threads = { path = "crates/wasi-threads", version = "37.0.0" } +wasmtime-wasi-tls = { path = "crates/wasi-tls", version = "37.0.0" } +wasmtime-wasi-tls-nativetls = { path = "crates/wasi-tls-nativetls", version = "37.0.0" } +wasmtime-wast = { path = "crates/wast", version = "=37.0.0" } # Internal Wasmtime-specific crates. # @@ -247,54 +248,54 @@ wasmtime-wast = { path = "crates/wast", version = "=36.0.0" } # that these are internal unsupported crates for external use. These exist as # part of the project organization of other public crates in Wasmtime and are # otherwise not supported in terms of CVEs for example. -wasmtime-wmemcheck = { path = "crates/wmemcheck", version = "=36.0.0", package = 'wasmtime-internal-wmemcheck' } -wasmtime-c-api-macros = { path = "crates/c-api-macros", version = "=36.0.0", package = 'wasmtime-internal-c-api-macros' } -wasmtime-cache = { path = "crates/cache", version = "=36.0.0", package = 'wasmtime-internal-cache' } -wasmtime-cranelift = { path = "crates/cranelift", version = "=36.0.0", package = 'wasmtime-internal-cranelift' } -wasmtime-winch = { path = "crates/winch", version = "=36.0.0", package = 'wasmtime-internal-winch' } -wasmtime-explorer = { path = "crates/explorer", version = "=36.0.0", package = 'wasmtime-internal-explorer' } -wasmtime-fiber = { path = "crates/fiber", version = "=36.0.0", package = 'wasmtime-internal-fiber' } -wasmtime-jit-debug = { path = "crates/jit-debug", version = "=36.0.0", package = 'wasmtime-internal-jit-debug' } -wasmtime-component-util = { path = "crates/component-util", version = "=36.0.0", package = 'wasmtime-internal-component-util' } -wasmtime-component-macro = { path = "crates/component-macro", version = "=36.0.0", package = 'wasmtime-internal-component-macro' } -wasmtime-asm-macros = { path = "crates/asm-macros", version = "=36.0.0", package = 'wasmtime-internal-asm-macros' } -wasmtime-versioned-export-macros = { path = "crates/versioned-export-macros", version = "=36.0.0", package = 'wasmtime-internal-versioned-export-macros' } -wasmtime-slab = { path = "crates/slab", version = "=36.0.0", package = 'wasmtime-internal-slab' } -wasmtime-jit-icache-coherence = { path = "crates/jit-icache-coherence", version = "=36.0.0", package = 'wasmtime-internal-jit-icache-coherence' } -wasmtime-wit-bindgen = { path = "crates/wit-bindgen", version = "=36.0.0", package = 'wasmtime-internal-wit-bindgen' } -wasmtime-math = { path = "crates/math", version = "=36.0.0", package = 'wasmtime-internal-math' } -wasmtime-unwinder = { path = "crates/unwinder", version = "=36.0.0", package = 'wasmtime-internal-unwinder' } +wasmtime-wmemcheck = { path = "crates/wmemcheck", version = "=37.0.0", package = 'wasmtime-internal-wmemcheck' } +wasmtime-c-api-macros = { path = "crates/c-api-macros", version = "=37.0.0", package = 'wasmtime-internal-c-api-macros' } +wasmtime-cache = { path = "crates/cache", version = "=37.0.0", package = 'wasmtime-internal-cache' } +wasmtime-cranelift = { path = "crates/cranelift", version = "=37.0.0", package = 'wasmtime-internal-cranelift' } +wasmtime-winch = { path = "crates/winch", version = "=37.0.0", package = 'wasmtime-internal-winch' } +wasmtime-explorer = { path = "crates/explorer", version = "=37.0.0", package = 'wasmtime-internal-explorer' } +wasmtime-fiber = { path = "crates/fiber", version = "=37.0.0", package = 'wasmtime-internal-fiber' } +wasmtime-jit-debug = { path = "crates/jit-debug", version = "=37.0.0", package = 'wasmtime-internal-jit-debug' } +wasmtime-component-util = { path = "crates/component-util", version = "=37.0.0", package = 'wasmtime-internal-component-util' } +wasmtime-component-macro = { path = "crates/component-macro", version = "=37.0.0", package = 'wasmtime-internal-component-macro' } +wasmtime-asm-macros = { path = "crates/asm-macros", version = "=37.0.0", package = 'wasmtime-internal-asm-macros' } +wasmtime-versioned-export-macros = { path = "crates/versioned-export-macros", version = "=37.0.0", package = 'wasmtime-internal-versioned-export-macros' } +wasmtime-slab = { path = "crates/slab", version = "=37.0.0", package = 'wasmtime-internal-slab' } +wasmtime-jit-icache-coherence = { path = "crates/jit-icache-coherence", version = "=37.0.0", package = 'wasmtime-internal-jit-icache-coherence' } +wasmtime-wit-bindgen = { path = "crates/wit-bindgen", version = "=37.0.0", package = 'wasmtime-internal-wit-bindgen' } +wasmtime-math = { path = "crates/math", version = "=37.0.0", package = 'wasmtime-internal-math' } +wasmtime-unwinder = { path = "crates/unwinder", version = "=37.0.0", package = 'wasmtime-internal-unwinder' } # Miscellaneous crates without a `wasmtime-*` prefix in their name but still # used in the `wasmtime-*` family of crates depending on various features/etc. -wiggle = { path = "crates/wiggle", version = "=36.0.0", default-features = false } -wiggle-macro = { path = "crates/wiggle/macro", version = "=36.0.0" } -wiggle-generate = { path = "crates/wiggle/generate", version = "=36.0.0" } -wasi-common = { path = "crates/wasi-common", version = "=36.0.0", default-features = false } -pulley-interpreter = { path = 'pulley', version = "=36.0.0" } -pulley-macros = { path = 'pulley/macros', version = "=36.0.0" } +wiggle = { path = "crates/wiggle", version = "=37.0.0", default-features = false } +wiggle-macro = { path = "crates/wiggle/macro", version = "=37.0.0" } +wiggle-generate = { path = "crates/wiggle/generate", version = "=37.0.0" } +wasi-common = { path = "crates/wasi-common", version = "=37.0.0", default-features = false } +pulley-interpreter = { path = 'pulley', version = "=37.0.0" } +pulley-macros = { path = 'pulley/macros', version = "=37.0.0" } # Cranelift crates in this workspace -cranelift-assembler-x64 = { path = "cranelift/assembler-x64", version = "0.123.0" } -cranelift-codegen = { path = "cranelift/codegen", version = "0.123.0", default-features = false, features = ["std", "unwind"] } -cranelift-frontend = { path = "cranelift/frontend", version = "0.123.0" } -cranelift-entity = { path = "cranelift/entity", version = "0.123.0" } -cranelift-native = { path = "cranelift/native", version = "0.123.0" } -cranelift-module = { path = "cranelift/module", version = "0.123.0" } -cranelift-interpreter = { path = "cranelift/interpreter", version = "0.123.0" } -cranelift-reader = { path = "cranelift/reader", version = "0.123.0" } +cranelift-assembler-x64 = { path = "cranelift/assembler-x64", version = "0.124.0" } +cranelift-codegen = { path = "cranelift/codegen", version = "0.124.0", default-features = false, features = ["std", "unwind"] } +cranelift-frontend = { path = "cranelift/frontend", version = "0.124.0" } +cranelift-entity = { path = "cranelift/entity", version = "0.124.0" } +cranelift-native = { path = "cranelift/native", version = "0.124.0" } +cranelift-module = { path = "cranelift/module", version = "0.124.0" } +cranelift-interpreter = { path = "cranelift/interpreter", version = "0.124.0" } +cranelift-reader = { path = "cranelift/reader", version = "0.124.0" } cranelift-filetests = { path = "cranelift/filetests" } -cranelift-object = { path = "cranelift/object", version = "0.123.0" } -cranelift-jit = { path = "cranelift/jit", version = "0.123.0" } +cranelift-object = { path = "cranelift/object", version = "0.124.0" } +cranelift-jit = { path = "cranelift/jit", version = "0.124.0" } cranelift-fuzzgen = { path = "cranelift/fuzzgen" } -cranelift-bforest = { path = "cranelift/bforest", version = "0.123.0" } -cranelift-bitset = { path = "cranelift/bitset", version = "0.123.0" } -cranelift-control = { path = "cranelift/control", version = "0.123.0" } -cranelift-srcgen = { path = "cranelift/srcgen", version = "0.123.0" } -cranelift = { path = "cranelift/umbrella", version = "0.123.0" } +cranelift-bforest = { path = "cranelift/bforest", version = "0.124.0" } +cranelift-bitset = { path = "cranelift/bitset", version = "0.124.0" } +cranelift-control = { path = "cranelift/control", version = "0.124.0" } +cranelift-srcgen = { path = "cranelift/srcgen", version = "0.124.0" } +cranelift = { path = "cranelift/umbrella", version = "0.124.0" } # Winch crates in this workspace. -winch-codegen = { path = "winch/codegen", version = "=36.0.0" } +winch-codegen = { path = "winch/codegen", version = "=37.0.0" } # Internal crates not published to crates.io used in testing, builds, etc wasi-preview1-component-adapter = { path = "crates/wasi-preview1-component-adapter" } diff --git a/README.md b/README.md index 9d04a43355..cc1b1b2635 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ curl https://wasmtime.dev/install.sh -sSf | bash ``` This script installs into `$WASMTIME_HOME` (defaults to `$HOME/.wasmtime`), and executable is placed in `$WASMTIME_HOME/bin`. +After running the install script above, follow the on-screen instructions. + Windows or otherwise interested users can download installers and binaries directly from the [GitHub Releases](https://github.com/bytecodealliance/wasmtime/releases) page. diff --git a/RELEASES.md b/RELEASES.md index 312fd70fa0..15c1282926 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,4 @@ -## 36.0.0 +## 37.0.0 Unreleased. @@ -6,22 +6,13 @@ Unreleased. ### Changed -Users who implemented `WasiHttpView::is_forbidden_header` from `wasmtime-wasi-http` now need to include `DEFAULT_FORBIDDEN_HEADERS`, e.g. `DEFAULT_FORBIDDEN_HEADERS.contains(name) || name.as_str() == "custom-forbidden-header"` #11292 - -### Fixed - -* Fix a panic in the host caused by preview1 guests using `fd_renumber`. - [CVE-2025-53901](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-fm79-3f68-h2fc). - -* Fix a panic in the preview1 adapter caused by guests using `fd_renumber`. - [#11277](https://github.com/bytecodealliance/wasmtime/pull/11277) - -------------------------------------------------------------------------------- Release notes for previous releases of Wasmtime can be found on the respective release branches of the Wasmtime repository. +* [36.0.x](https://github.com/bytecodealliance/wasmtime/blob/release-36.0.0/RELEASES.md) * [35.0.x](https://github.com/bytecodealliance/wasmtime/blob/release-35.0.0/RELEASES.md) * [34.0.x](https://github.com/bytecodealliance/wasmtime/blob/release-34.0.0/RELEASES.md) * [33.0.x](https://github.com/bytecodealliance/wasmtime/blob/release-33.0.0/RELEASES.md) diff --git a/benches/instantiation.rs b/benches/instantiation.rs index 824cf9492d..836923816d 100644 --- a/benches/instantiation.rs +++ b/benches/instantiation.rs @@ -6,15 +6,15 @@ use std::process::Command; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}; use std::thread; -use wasi_common::{WasiCtx, sync::WasiCtxBuilder}; use wasmtime::*; +use wasmtime_wasi::{WasiCtx, p1::WasiP1Ctx}; -fn store(engine: &Engine) -> Store { - let wasi = WasiCtxBuilder::new().build(); +fn store(engine: &Engine) -> Store { + let wasi = WasiCtx::builder().build_p1(); Store::new(engine, wasi) } -fn instantiate(pre: &InstancePre, engine: &Engine) -> Result<()> { +fn instantiate(pre: &InstancePre, engine: &Engine) -> Result<()> { let mut store = store(engine); let _instance = pre.instantiate(&mut store)?; Ok(()) @@ -49,7 +49,7 @@ fn bench_sequential(c: &mut Criterion, path: &Path) { // benchmark programs. linker.func_wrap("bench", "start", || {}).unwrap(); linker.func_wrap("bench", "end", || {}).unwrap(); - wasi_common::sync::add_to_linker(&mut linker, |cx| cx).unwrap(); + wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |cx| cx).unwrap(); let pre = linker .instantiate_pre(&module) .expect("failed to pre-instantiate"); @@ -83,7 +83,7 @@ fn bench_parallel(c: &mut Criterion, path: &Path) { // benchmark programs. linker.func_wrap("bench", "start", || {}).unwrap(); linker.func_wrap("bench", "end", || {}).unwrap(); - wasi_common::sync::add_to_linker(&mut linker, |cx| cx).unwrap(); + wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |cx| cx).unwrap(); let pre = Arc::new( linker .instantiate_pre(&module) diff --git a/benches/wasi.rs b/benches/wasi.rs index d93b83858d..5f7bd1434e 100644 --- a/benches/wasi.rs +++ b/benches/wasi.rs @@ -2,8 +2,8 @@ use criterion::{Criterion, criterion_group, criterion_main}; use std::{fs::File, path::Path, time::Instant}; -use wasi_common::{WasiCtx, sync::WasiCtxBuilder}; use wasmtime::{Engine, Linker, Module, Store, TypedFunc}; +use wasmtime_wasi::{DirPerms, FilePerms, WasiCtx, p1::WasiP1Ctx}; criterion_group!(benches, bench_wasi); criterion_main!(benches); @@ -47,27 +47,26 @@ fn bench_wasi(c: &mut Criterion) { /// - execute the body of the function for that number of loop iterations /// - return a single `u64` indicating how many loop iterations were executed /// (to double-check) -fn instantiate(wat: &[u8]) -> (Store, TypedFunc) { +fn instantiate(wat: &[u8]) -> (Store, TypedFunc) { let engine = Engine::default(); let wasi = wasi_context(); let mut store = Store::new(&engine, wasi); let module = Module::new(&engine, wat).unwrap(); let mut linker = Linker::new(&engine); - wasi_common::sync::add_to_linker(&mut linker, |cx| cx).unwrap(); + wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |cx| cx).unwrap(); let instance = linker.instantiate(&mut store, &module).unwrap(); let run = instance.get_typed_func(&mut store, "run").unwrap(); (store, run) } /// Build a WASI context with some actual data to retrieve. -fn wasi_context() -> WasiCtx { - WasiCtxBuilder::new() +fn wasi_context() -> WasiP1Ctx { + WasiCtx::builder() .envs(&[ ("a".to_string(), "b".to_string()), ("b".to_string(), "c".to_string()), ("c".to_string(), "d".to_string()), ]) - .unwrap() .args(&[ "exe".to_string(), "--flag1".to_string(), @@ -75,15 +74,7 @@ fn wasi_context() -> WasiCtx { "--flag3".to_string(), "--flag4".to_string(), ]) + .preopened_dir("benches/wasi", "/", DirPerms::READ, FilePerms::READ) .unwrap() - .preopened_dir( - wasi_common::sync::Dir::open_ambient_dir( - "benches/wasi", - wasi_common::sync::ambient_authority(), - ) - .unwrap(), - "/", - ) - .unwrap() - .build() + .build_p1() } diff --git a/ci/build-release-artifacts.sh b/ci/build-release-artifacts.sh index 62311983b2..0ac4278641 100755 --- a/ci/build-release-artifacts.sh +++ b/ci/build-release-artifacts.sh @@ -71,6 +71,12 @@ fi cargo build --release $flags --target $target -p wasmtime-cli $bin_flags --features run +# For the C API force unwind tables to be emitted to make the generated objects +# more flexible. Embedders can always build without this but this enables +# libunwind to produce better backtraces by default when Wasmtime is linked into +# a different project that wants to unwind. +export RUSTFLAGS="$RUSTFLAGS -C force-unwind-tables" + mkdir -p target/c-api-build cd target/c-api-build cmake \ diff --git a/ci/build-test-matrix.js b/ci/build-test-matrix.js index d600fa9299..95db8b7f7f 100644 --- a/ci/build-test-matrix.js +++ b/ci/build-test-matrix.js @@ -13,7 +13,7 @@ const GENERIC_BUCKETS = 3; // Crates which are their own buckets. These are the very slowest to // compile-and-test crates. -const SINGLE_CRATE_BUCKETS = ["wasmtime", "wasmtime-cli", "wasmtime-wasi", 'component-async-tests']; +const SINGLE_CRATE_BUCKETS = ["wasmtime", "wasmtime-cli", "wasmtime-wasi"]; const ubuntu = 'ubuntu-24.04'; const windows = 'windows-2025'; diff --git a/ci/vendor-wit.sh b/ci/vendor-wit.sh index a831a0683a..008be68142 100755 --- a/ci/vendor-wit.sh +++ b/ci/vendor-wit.sh @@ -68,13 +68,13 @@ make_vendor "wasi-config" "config@f4d699b" make_vendor "wasi-keyvalue" "keyvalue@219ea36" -# make_vendor "wasi/src/p3" " -# cli@939bd6d@wit-0.3.0-draft -# clocks@13d1c82@wit-0.3.0-draft -# filesystem@e2a2ddc@wit-0.3.0-draft -# random@4e94663@wit-0.3.0-draft -# sockets@e863ee2@wit-0.3.0-draft -# " +make_vendor "wasi/src/p3" " + cli@939bd6d@wit-0.3.0-draft + clocks@13d1c82@wit-0.3.0-draft + filesystem@2007d36@wit-0.3.0-draft + random@4e94663@wit-0.3.0-draft + sockets@e863ee2@wit-0.3.0-draft +" rm -rf $cache_dir diff --git a/cranelift/assembler-x64/Cargo.toml b/cranelift/assembler-x64/Cargo.toml index 604f70fa2e..8da4188881 100644 --- a/cranelift/assembler-x64/Cargo.toml +++ b/cranelift/assembler-x64/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cranelift-assembler-x64" description = "A Cranelift-specific x64 assembler" -version = "0.123.0" +version = "0.124.0" license = "Apache-2.0 WITH LLVM-exception" edition.workspace = true rust-version.workspace = true @@ -16,7 +16,7 @@ arbtest = "0.3.1" capstone = { workspace = true } [build-dependencies] -cranelift-assembler-x64-meta = { path = "meta", version = "0.123.0" } +cranelift-assembler-x64-meta = { path = "meta", version = "0.124.0" } [lints] workspace = true diff --git a/cranelift/assembler-x64/meta/Cargo.toml b/cranelift/assembler-x64/meta/Cargo.toml index ca6e0134b5..6aaeeccdbf 100644 --- a/cranelift/assembler-x64/meta/Cargo.toml +++ b/cranelift/assembler-x64/meta/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cranelift-assembler-x64-meta" description = "Generate a Cranelift-specific assembler for x64 instructions" -version = "0.123.0" +version = "0.124.0" license = "Apache-2.0 WITH LLVM-exception" edition.workspace = true rust-version.workspace = true diff --git a/cranelift/assembler-x64/meta/src/dsl.rs b/cranelift/assembler-x64/meta/src/dsl.rs index 9d14c7afa0..475aa7d8d2 100644 --- a/cranelift/assembler-x64/meta/src/dsl.rs +++ b/cranelift/assembler-x64/meta/src/dsl.rs @@ -127,9 +127,7 @@ impl core::fmt::Display for Inst { custom, } = self; write!(f, "{name}: {format} => {encoding}")?; - if !features.is_empty() { - write!(f, " [{features}]")?; - } + write!(f, " [{features}]")?; if let Some(alternate) = alternate { write!(f, " (alternate: {alternate})")?; } diff --git a/cranelift/assembler-x64/meta/src/dsl/features.rs b/cranelift/assembler-x64/meta/src/dsl/features.rs index ca584ded00..87e4172b38 100644 --- a/cranelift/assembler-x64/meta/src/dsl/features.rs +++ b/cranelift/assembler-x64/meta/src/dsl/features.rs @@ -1,61 +1,65 @@ //! A DSL for describing x64 CPU features. use core::fmt; -use std::ops::BitOr; +use std::ops::{BitAnd, BitOr}; -/// A collection of CPU features. +/// A boolean term of CPU features. /// -/// An instruction is valid when _any_ of the features in the collection are -/// enabled; i.e., the collection is an `OR` expression. +/// An instruction is valid when the boolean term (a recursive tree of `AND` and +/// `OR` terms) is satisfied. /// /// ``` /// # use cranelift_assembler_x64_meta::dsl::{Features, Feature}; /// let fs = Feature::_64b | Feature::compat; -/// assert_eq!(fs.to_string(), "_64b | compat"); -/// ``` -/// -/// Duplicate features are not allowed and will cause a panic. -/// -/// ```should_panic -/// # use cranelift_assembler_x64_meta::dsl::Feature; -/// let fs = Feature::_64b | Feature::_64b; +/// assert_eq!(fs.to_string(), "(_64b | compat)"); /// ``` #[derive(PartialEq)] -pub struct Features(Vec); +pub enum Features { + And(Box, Box), + Or(Box, Box), + Feature(Feature), +} impl Features { - #[must_use] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - pub fn iter(&self) -> impl Iterator { - self.0.iter() + pub(crate) fn is_sse(&self) -> bool { + use Feature::*; + match self { + Features::And(lhs, rhs) => lhs.is_sse() || rhs.is_sse(), + Features::Or(lhs, rhs) => lhs.is_sse() || rhs.is_sse(), + Features::Feature(feature) => { + matches!(feature, sse | sse2 | sse3 | ssse3 | sse41 | sse42) + } + } } +} - pub fn contains(&self, feature: Feature) -> bool { - self.0.contains(&feature) +impl fmt::Display for Features { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Features::And(lhs, rhs) => write!(f, "({lhs} & {rhs})"), + Features::Or(lhs, rhs) => write!(f, "({lhs} | {rhs})"), + Features::Feature(feature) => write!(f, "{feature:#?}"), + } } +} - pub(crate) fn is_sse(&self) -> bool { - use Feature::*; - self.0 - .iter() - .any(|f| matches!(f, sse | sse2 | sse3 | ssse3 | sse41 | sse42)) +impl BitOr for Features +where + T: Into, +{ + type Output = Features; + fn bitor(self, rhs: T) -> Self::Output { + Features::Or(Box::new(self), Box::new(rhs.into())) } } -impl fmt::Display for Features { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - self.0 - .iter() - .map(ToString::to_string) - .collect::>() - .join(" | ") - ) +impl BitAnd for Features +where + T: Into, +{ + type Output = Features; + fn bitand(self, rhs: T) -> Self::Output { + Features::And(Box::new(self), Box::new(rhs.into())) } } @@ -132,30 +136,27 @@ impl fmt::Display for Feature { } impl From for Features { - fn from(flag: Feature) -> Self { - Features(vec![flag]) - } -} - -impl From> for Features { - fn from(flag: Option) -> Self { - Features(flag.into_iter().collect()) + fn from(f: Feature) -> Self { + Features::Feature(f) } } -impl BitOr for Feature { +impl BitAnd for Feature +where + T: Into, +{ type Output = Features; - fn bitor(self, rhs: Self) -> Self::Output { - assert_ne!(self, rhs, "duplicate feature: {self:?}"); - Features(vec![self, rhs]) + fn bitand(self, rhs: T) -> Self::Output { + Features::from(self) & rhs.into() } } -impl BitOr for Features { +impl BitOr for Feature +where + T: Into, +{ type Output = Features; - fn bitor(mut self, rhs: Feature) -> Self::Output { - assert!(!self.0.contains(&rhs), "duplicate feature: {rhs:?}"); - self.0.push(rhs); - self + fn bitor(self, rhs: T) -> Self::Output { + Features::from(self) | rhs.into() } } diff --git a/cranelift/assembler-x64/meta/src/generate.rs b/cranelift/assembler-x64/meta/src/generate.rs index b344d58c8e..73767490bf 100644 --- a/cranelift/assembler-x64/meta/src/generate.rs +++ b/cranelift/assembler-x64/meta/src/generate.rs @@ -15,6 +15,7 @@ pub fn rust_assembler(f: &mut Formatter, insts: &[dsl::Inst]) { generate_inst_display_impl(f, insts); generate_inst_encode_impl(f, insts); generate_inst_visit_impl(f, insts); + generate_inst_is_available_impl(f, insts); generate_inst_features_impl(f, insts); // Generate per-instruction structs. @@ -27,8 +28,8 @@ pub fn rust_assembler(f: &mut Formatter, insts: &[dsl::Inst]) { f.empty_line(); } - // Generate the `Feature` enum. - dsl::Feature::generate_enum(f); + // Generate the `Feature` trait. + dsl::Feature::generate_macro(f); } /// `enum Inst { ... }` @@ -117,10 +118,27 @@ fn generate_inst_visit_impl(f: &mut Formatter, insts: &[dsl::Inst]) { fmtln!(f, "}}"); } +/// `impl Inst { fn is_available... }` +fn generate_inst_is_available_impl(f: &mut Formatter, insts: &[dsl::Inst]) { + f.add_block("impl Inst", |f| { + f.add_block( + "pub fn is_available(&self, f: &impl AvailableFeatures) -> bool", + |f| { + f.add_block("match self", |f| { + for inst in insts { + let variant_name = inst.name(); + fmtln!(f, "Self::{variant_name}(i) => i.is_available(f),"); + } + }); + }, + ); + }); +} + /// `impl Inst { fn features... }` fn generate_inst_features_impl(f: &mut Formatter, insts: &[dsl::Inst]) { f.add_block("impl Inst", |f| { - f.add_block("pub fn features(&self) -> Vec", |f| { + f.add_block("pub fn features(&self) -> &'static Features", |f| { f.add_block("match self", |f| { for inst in insts { let variant_name = inst.name(); diff --git a/cranelift/assembler-x64/meta/src/generate/features.rs b/cranelift/assembler-x64/meta/src/generate/features.rs index 43ad585d3d..1893af93b7 100644 --- a/cranelift/assembler-x64/meta/src/generate/features.rs +++ b/cranelift/assembler-x64/meta/src/generate/features.rs @@ -1,21 +1,101 @@ //! Generate feature-related Rust code. use super::{Formatter, fmtln}; -use crate::{dsl, generate::generate_derive}; +use crate::dsl; impl dsl::Feature { - /// `pub enum Feature { ... }` + /// `macro_rules! for_each_feature { ... }` /// - /// This function recreates the `Feature` struct itself in the generated - /// code. - pub fn generate_enum(f: &mut Formatter) { + /// This function generates a macro to allow generating code for each CPU + /// feature. + pub(crate) fn generate_macro(f: &mut Formatter) { fmtln!(f, "#[doc(hidden)]"); - generate_derive(f); - fmtln!(f, "#[derive(PartialEq)]"); // Add more helpful derives. - f.add_block("pub enum Feature", |f| { - for feature in dsl::ALL_FEATURES { - fmtln!(f, "{feature},"); - } + fmtln!(f, "#[macro_export]"); + f.add_block("macro_rules! for_each_feature", |f| { + f.add_block("($m:ident) =>", |f| { + f.add_block("$m!", |f| { + for feature in dsl::ALL_FEATURES { + fmtln!(f, "{feature}"); + } + }); + }); }); } } + +impl dsl::Features { + /// E.g., `features.is_sse2() && features.is_64b()` + /// + /// Generate a boolean expression that checks if the features are available. + pub(crate) fn generate_boolean_expr(&self, name: &str) -> String { + use dsl::Features::*; + match self { + And(lhs, rhs) => { + let lhs = lhs.generate_inner_boolean_expr(name); + let rhs = rhs.generate_inner_boolean_expr(name); + format!("{lhs} && {rhs}") + } + Or(lhs, rhs) => { + let lhs = lhs.generate_inner_boolean_expr(name); + let rhs = rhs.generate_inner_boolean_expr(name); + format!("{lhs} || {rhs}") + } + Feature(feature) => { + format!("{name}.{feature}()") + } + } + } + + // This adds parentheses for inner terms. + fn generate_inner_boolean_expr(&self, name: &str) -> String { + use dsl::Features::*; + match self { + And(lhs, rhs) => { + let lhs = lhs.generate_inner_boolean_expr(name); + let rhs = rhs.generate_inner_boolean_expr(name); + format!("({lhs} && {rhs})") + } + Or(lhs, rhs) => { + let lhs = lhs.generate_inner_boolean_expr(name); + let rhs = rhs.generate_inner_boolean_expr(name); + format!("({lhs} || {rhs})") + } + Feature(feature) => format!("{name}.{feature}()"), + } + } + + /// E.g., `Features::Or(Features::Feature(compat), Features::Feature(64b))` + /// + /// Generate a Rust constructor expression that contains the feature + /// boolean term. + pub(crate) fn generate_constructor_expr(&self, f: &mut Formatter) { + let mut index = 0; + let name = self.generate_inner_constructor_expr(f, &mut index); + fmtln!(f, "{name}"); + } + + fn generate_inner_constructor_expr(&self, f: &mut Formatter, index: &mut u32) -> String { + use dsl::Features::*; + + let name = format!("F{index}"); + *index += 1; + + let const_expr = format!("const {name}: &'static Features"); + match self { + And(lhs, rhs) => { + let lhs = lhs.generate_inner_constructor_expr(f, index); + let rhs = rhs.generate_inner_constructor_expr(f, index); + fmtln!(f, "{const_expr} = &Features::And({lhs}, {rhs});"); + } + Or(lhs, rhs) => { + let lhs = lhs.generate_inner_constructor_expr(f, index); + let rhs = rhs.generate_inner_constructor_expr(f, index); + fmtln!(f, "{const_expr} = &Features::Or({lhs}, {rhs});"); + } + Feature(feature) => { + fmtln!(f, "{const_expr} = &Features::Feature(Feature::{feature});"); + } + } + name + } +} diff --git a/cranelift/assembler-x64/meta/src/generate/inst.rs b/cranelift/assembler-x64/meta/src/generate/inst.rs index f7724042ce..dcd3664018 100644 --- a/cranelift/assembler-x64/meta/src/generate/inst.rs +++ b/cranelift/assembler-x64/meta/src/generate/inst.rs @@ -65,6 +65,8 @@ impl dsl::Inst { f.empty_line(); self.generate_visit_function(f); f.empty_line(); + self.generate_is_available_function(f); + f.empty_line(); self.generate_features_function(f); }); } @@ -222,16 +224,23 @@ impl dsl::Inst { }); } - /// `fn features(&self) -> Vec { ... }` + /// `fn is_available(&self, ...) -> bool { ... }` + fn generate_is_available_function(&self, f: &mut Formatter) { + fmtln!(f, "#[must_use]"); + f.add_block( + "pub fn is_available(&self, features: &impl AvailableFeatures) -> bool", + |f| { + let expr = self.features.generate_boolean_expr("features"); + fmtln!(f, "{expr}"); + }, + ); + } + + /// `fn features(&self) -> Features { ... }` fn generate_features_function(&self, f: &mut Formatter) { fmtln!(f, "#[must_use]"); - f.add_block("pub fn features(&self) -> Vec", |f| { - let flags = self - .features - .iter() - .map(|f| format!("Feature::{f}")) - .collect::>(); - fmtln!(f, "vec![{}]", flags.join(", ")); + f.add_block("pub fn features(&self) -> &'static Features", |f| { + self.features.generate_constructor_expr(f); }); } diff --git a/cranelift/assembler-x64/meta/src/instructions/abs.rs b/cranelift/assembler-x64/meta/src/instructions/abs.rs index 4394cdc385..ddd66574a9 100644 --- a/cranelift/assembler-x64/meta/src/instructions/abs.rs +++ b/cranelift/assembler-x64/meta/src/instructions/abs.rs @@ -4,17 +4,17 @@ use crate::dsl::{align, evex, fmt, inst, r, rex, vex, w}; #[rustfmt::skip] // Keeps instructions on a single line. pub fn list() -> Vec { vec![ - inst("pabsb", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1C]), _64b | compat | ssse3).alt(avx, "vpabsb_a"), - inst("vpabsb", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1C), _64b | compat | avx), + inst("pabsb", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1C]), (_64b | compat) & ssse3).alt(avx, "vpabsb_a"), + inst("vpabsb", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1C), (_64b | compat) & avx), // FIXME: uncomment once the avx512bw feature is bound - // inst("vpabsb", fmt("B", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().wig().op(0x1C).r(), _64b | compat | avx512vl | avx512bw), - inst("pabsw", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1D]), _64b | compat | ssse3).alt(avx, "vpabsw_a"), - inst("vpabsw", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1D), _64b | compat | avx), + // inst("vpabsb", fmt("B", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().wig().op(0x1C).r(), (_64b | compat) & avx512vl & avx512bw), + inst("pabsw", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1D]), (_64b | compat) & ssse3).alt(avx, "vpabsw_a"), + inst("vpabsw", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1D), (_64b | compat) & avx), // FIXME: uncomment once the avx512bw feature is bound - // inst("vpabsw", fmt("B", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().wig().op(0x1D).r(), _64b | compat | avx512vl | avx512bw), - inst("pabsd", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1E]), _64b | compat | ssse3).alt(avx, "vpabsd_a"), - inst("vpabsd", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1E), _64b | compat | avx), - inst("vpabsd", fmt("C", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w0().op(0x1E).r(), _64b | compat | avx512vl | avx512f), - inst("vpabsq", fmt("C", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w1().op(0x1F).r(), _64b | compat | avx512vl | avx512f), + // inst("vpabsw", fmt("B", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().wig().op(0x1D).r(), (_64b | compat) & avx512vl & avx512bw), + inst("pabsd", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x1E]), (_64b | compat) & ssse3).alt(avx, "vpabsd_a"), + inst("vpabsd", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x1E), (_64b | compat) & avx), + inst("vpabsd", fmt("C", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w0().op(0x1E).r(), (_64b | compat) & avx512vl & avx512f), + inst("vpabsq", fmt("C", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w1().op(0x1F).r(), (_64b | compat) & avx512vl & avx512f), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/add.rs b/cranelift/assembler-x64/meta/src/instructions/add.rs index 27c0995451..f5969061b0 100644 --- a/cranelift/assembler-x64/meta/src/instructions/add.rs +++ b/cranelift/assembler-x64/meta/src/instructions/add.rs @@ -68,34 +68,34 @@ pub fn list() -> Vec { inst("lock_xaddl", fmt("MR", [rw(m32), rw(r32)]), rex([0xf0, 0x0f, 0xc1]).r(), _64b | compat).custom(Mnemonic | Visit), inst("lock_xaddq", fmt("MR", [rw(m64), rw(r64)]), rex([0xf0, 0x0f, 0xc1]).w().r(), _64b).custom(Mnemonic | Visit), // Vector instructions. - inst("addss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x58]).r(), _64b | compat | sse).alt(avx, "vaddss_b"), - inst("addsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x58]).r(), _64b | compat | sse2).alt(avx, "vaddsd_b"), - inst("addps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x58]).r(), _64b | compat | sse).alt(avx, "vaddps_b"), - inst("addpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x58]).r(), _64b | compat | sse2).alt(avx, "vaddpd_b"), - inst("paddb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xFC]).r(), _64b | compat | sse2).alt(avx, "vpaddb_b"), - inst("paddw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xFD]).r(), _64b | compat | sse2).alt(avx, "vpaddw_b"), - inst("paddd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xFE]).r(), _64b | compat | sse2).alt(avx, "vpaddd_b"), - inst("paddq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD4]).r(), _64b | compat | sse2).alt(avx, "vpaddq_b"), - inst("paddsb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xEC]).r(), _64b | compat | sse2).alt(avx, "vpaddsb_b"), - inst("paddsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xED]).r(), _64b | compat | sse2).alt(avx, "vpaddsw_b"), - inst("paddusb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDC]).r(), _64b | compat | sse2).alt(avx, "vpaddusb_b"), - inst("paddusw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDD]).r(), _64b | compat | sse2).alt(avx, "vpaddusw_b"), - inst("phaddw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x01]).r(), _64b | compat | ssse3).alt(avx, "vphaddw_b"), - inst("phaddd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x02]).r(), _64b | compat | ssse3).alt(avx, "vphaddd_b"), - inst("vaddss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(L128)._f3()._0f().op(0x58).r(), _64b | compat | avx), - inst("vaddsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(L128)._f2()._0f().op(0x58).r(), _64b | compat | avx), - inst("vaddps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x58).r(), _64b | compat | avx), - inst("vaddpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x58).r(), _64b | compat | avx), - inst("vpaddb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xFC).r(), _64b | compat | avx), - inst("vpaddw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xFD).r(), _64b | compat | avx), - inst("vpaddd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xFE).r(), _64b | compat | avx), - inst("vpaddq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD4).r(), _64b | compat | avx), - inst("vpaddsb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xEC).r(), _64b | compat | avx), - inst("vpaddsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xED).r(), _64b | compat | avx), - inst("vpaddusb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDC).r(), _64b | compat | avx), - inst("vpaddusw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDD).r(), _64b | compat | avx), - inst("vphaddw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x01).r(), _64b | compat | avx), - inst("vphaddd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x02).r(), _64b | compat | avx), - inst("vaddpd", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Full)._66()._0f().w1().op(0x58).r(), _64b | compat | avx512vl), + inst("addss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x58]).r(), (_64b | compat) & sse).alt(avx, "vaddss_b"), + inst("addsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x58]).r(), (_64b | compat) & sse2).alt(avx, "vaddsd_b"), + inst("addps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x58]).r(), (_64b | compat) & sse).alt(avx, "vaddps_b"), + inst("addpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x58]).r(), (_64b | compat) & sse2).alt(avx, "vaddpd_b"), + inst("paddb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xFC]).r(), (_64b | compat) & sse2).alt(avx, "vpaddb_b"), + inst("paddw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xFD]).r(), (_64b | compat) & sse2).alt(avx, "vpaddw_b"), + inst("paddd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xFE]).r(), (_64b | compat) & sse2).alt(avx, "vpaddd_b"), + inst("paddq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD4]).r(), (_64b | compat) & sse2).alt(avx, "vpaddq_b"), + inst("paddsb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xEC]).r(), (_64b | compat) & sse2).alt(avx, "vpaddsb_b"), + inst("paddsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xED]).r(), (_64b | compat) & sse2).alt(avx, "vpaddsw_b"), + inst("paddusb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDC]).r(), (_64b | compat) & sse2).alt(avx, "vpaddusb_b"), + inst("paddusw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDD]).r(), (_64b | compat) & sse2).alt(avx, "vpaddusw_b"), + inst("phaddw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x01]).r(), (_64b | compat) & ssse3).alt(avx, "vphaddw_b"), + inst("phaddd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x02]).r(), (_64b | compat) & ssse3).alt(avx, "vphaddd_b"), + inst("vaddss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(L128)._f3()._0f().op(0x58).r(), (_64b | compat) & avx), + inst("vaddsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(L128)._f2()._0f().op(0x58).r(), (_64b | compat) & avx), + inst("vaddps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x58).r(), (_64b | compat) & avx), + inst("vaddpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x58).r(), (_64b | compat) & avx), + inst("vpaddb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xFC).r(), (_64b | compat) & avx), + inst("vpaddw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xFD).r(), (_64b | compat) & avx), + inst("vpaddd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xFE).r(), (_64b | compat) & avx), + inst("vpaddq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD4).r(), (_64b | compat) & avx), + inst("vpaddsb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xEC).r(), (_64b | compat) & avx), + inst("vpaddsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xED).r(), (_64b | compat) & avx), + inst("vpaddusb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDC).r(), (_64b | compat) & avx), + inst("vpaddusw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDD).r(), (_64b | compat) & avx), + inst("vphaddw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x01).r(), (_64b | compat) & avx), + inst("vphaddd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x02).r(), (_64b | compat) & avx), + inst("vaddpd", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Full)._66()._0f().w1().op(0x58).r(), (_64b | compat) & avx512vl), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/align.rs b/cranelift/assembler-x64/meta/src/instructions/align.rs index 712401eb59..76f80e2f69 100644 --- a/cranelift/assembler-x64/meta/src/instructions/align.rs +++ b/cranelift/assembler-x64/meta/src/instructions/align.rs @@ -5,7 +5,7 @@ use crate::dsl::{align, fmt, inst, r, rex, rw, vex, w}; pub fn list() -> Vec { vec![ // Packed align right. - inst("palignr", fmt("A", [rw(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x0F]).ib(), _64b | compat | ssse3).alt(avx, "vpalignr_b"), - inst("vpalignr", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f3a().ib().op(0x0F), _64b | compat | avx), + inst("palignr", fmt("A", [rw(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x0F]).ib(), (_64b | compat) & ssse3).alt(avx, "vpalignr_b"), + inst("vpalignr", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f3a().ib().op(0x0F), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/and.rs b/cranelift/assembler-x64/meta/src/instructions/and.rs index 395ebf05b3..b22e7aa2fb 100644 --- a/cranelift/assembler-x64/meta/src/instructions/and.rs +++ b/cranelift/assembler-x64/meta/src/instructions/and.rs @@ -30,8 +30,8 @@ pub fn list() -> Vec { inst("andl", fmt("RM", [rw(r32), r(rm32)]), rex(0x23).r(), _64b | compat), inst("andq", fmt("RM", [rw(r64), r(rm64)]), rex(0x23).w().r(), _64b), // BMI1 andn - inst("andnl", fmt("RVM", [w(r32a), r(r32b), r(rm32)]), vex(LZ)._0f38().w0().op(0xF2), _64b | compat | bmi1), - inst("andnq", fmt("RVM", [w(r64a), r(r64b), r(rm64)]), vex(LZ)._0f38().w1().op(0xF2), _64b | bmi1), + inst("andnl", fmt("RVM", [w(r32a), r(r32b), r(rm32)]), vex(LZ)._0f38().w0().op(0xF2), (_64b | compat) & bmi1), + inst("andnq", fmt("RVM", [w(r64a), r(r64b), r(rm64)]), vex(LZ)._0f38().w1().op(0xF2), _64b & bmi1), // `LOCK`-prefixed memory-writing instructions. inst("lock_andb", fmt("MI", [rw(m8), r(imm8)]), rex([0xf0, 0x80]).digit(4).ib(), _64b | compat).custom(Mnemonic), inst("lock_andw", fmt("MI", [rw(m16), r(imm16)]), rex([0xf0, 0x66, 0x81]).digit(4).iw(), _64b | compat).custom(Mnemonic), @@ -44,17 +44,17 @@ pub fn list() -> Vec { inst("lock_andl", fmt("MR", [rw(m32), r(r32)]), rex([0xf0, 0x21]).r(), _64b | compat).custom(Mnemonic), inst("lock_andq", fmt("MR", [rw(m64), r(r64)]), rex([0xf0, 0x21]).w().r(), _64b).custom(Mnemonic), // Vector instructions. - inst("andps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x54]).r(), _64b | compat | sse).alt(avx, "vandps_b"), - inst("andpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x54]).r(), _64b | compat | sse2).alt(avx, "vandpd_b"), - inst("andnps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x55]).r(), _64b | compat | sse).alt(avx, "vandnps_b"), - inst("andnpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x55]).r(), _64b | compat | sse2).alt(avx, "vandnpd_b"), - inst("pand", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDB]).r(), _64b | compat | sse2).alt(avx, "vpand_b"), - inst("pandn", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDF]).r(), _64b | compat | sse2).alt(avx, "vpandn_b"), - inst("vandps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x54).r(), _64b | compat | avx), - inst("vandpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x54).r(), _64b | compat | avx), - inst("vandnps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x55).r(), _64b | compat | avx), - inst("vandnpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x55).r(), _64b | compat | avx), - inst("vpand", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDB).r(), _64b | compat | avx), - inst("vpandn", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDF).r(), _64b | compat | avx), + inst("andps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x54]).r(), (_64b | compat) & sse).alt(avx, "vandps_b"), + inst("andpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x54]).r(), (_64b | compat) & sse2).alt(avx, "vandpd_b"), + inst("andnps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x55]).r(), (_64b | compat) & sse).alt(avx, "vandnps_b"), + inst("andnpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x55]).r(), (_64b | compat) & sse2).alt(avx, "vandnpd_b"), + inst("pand", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDB]).r(), (_64b | compat) & sse2).alt(avx, "vpand_b"), + inst("pandn", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDF]).r(), (_64b | compat) & sse2).alt(avx, "vpandn_b"), + inst("vandps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x54).r(), (_64b | compat) & avx), + inst("vandpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x54).r(), (_64b | compat) & avx), + inst("vandnps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x55).r(), (_64b | compat) & avx), + inst("vandnpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x55).r(), (_64b | compat) & avx), + inst("vpand", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDB).r(), (_64b | compat) & avx), + inst("vpandn", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDF).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/atomic.rs b/cranelift/assembler-x64/meta/src/instructions/atomic.rs index 8c0f95ba7a..bc039b58fa 100644 --- a/cranelift/assembler-x64/meta/src/instructions/atomic.rs +++ b/cranelift/assembler-x64/meta/src/instructions/atomic.rs @@ -20,8 +20,8 @@ pub fn list() -> Vec { inst("xchgl", fmt("RM", [rw(r32), rw(m32)]), rex(0x87).r(), _64b | compat).custom(Display), inst("xchgq", fmt("RM", [rw(r64), rw(m64)]), rex(0x87).w().r(), _64b).custom(Display), - inst("cmpxchg16b", cmpxchg16b_m.clone(), rex([0x0f, 0xc7]).digit(1).w(), _64b | cmpxchg16b), - inst("lock_cmpxchg16b", cmpxchg16b_m.clone(), rex([0xf0, 0x0f, 0xc7]).digit(1).w(), _64b | cmpxchg16b).custom(Mnemonic), + inst("cmpxchg16b", cmpxchg16b_m.clone(), rex([0x0f, 0xc7]).digit(1).w(), _64b & cmpxchg16b), + inst("lock_cmpxchg16b", cmpxchg16b_m.clone(), rex([0xf0, 0x0f, 0xc7]).digit(1).w(), _64b & cmpxchg16b).custom(Mnemonic), inst("cmpxchgb", fmt("MR", [rw(rm8), r(r8), rw(implicit(al))]), rex([0x0f, 0xb0]).r(), _64b | compat), inst("cmpxchgw", fmt("MR", [rw(rm16), r(r16), rw(implicit(ax))]), rex([0x66, 0x0f, 0xb1]).r(), _64b | compat), diff --git a/cranelift/assembler-x64/meta/src/instructions/avg.rs b/cranelift/assembler-x64/meta/src/instructions/avg.rs index 236cdcfc8f..4dbf4cffe2 100644 --- a/cranelift/assembler-x64/meta/src/instructions/avg.rs +++ b/cranelift/assembler-x64/meta/src/instructions/avg.rs @@ -4,9 +4,9 @@ use crate::dsl::{align, fmt, inst, r, rex, rw, vex, w}; #[rustfmt::skip] // Keeps instructions on a single line. pub fn list() -> Vec { vec![ - inst("pavgb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE0]), _64b | compat | sse2).alt(avx, "vpavgb_b"), - inst("pavgw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE3]), _64b | compat | sse2).alt(avx, "vpavgw_b"), - inst("vpavgb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE0), _64b | compat | avx), - inst("vpavgw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE3), _64b | compat | avx), + inst("pavgb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE0]), (_64b | compat) & sse2).alt(avx, "vpavgb_b"), + inst("pavgw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE3]), (_64b | compat) & sse2).alt(avx, "vpavgw_b"), + inst("vpavgb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE0), (_64b | compat) & avx), + inst("vpavgw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE3), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/bitmanip.rs b/cranelift/assembler-x64/meta/src/instructions/bitmanip.rs index 1b7b0b35a3..d25cc65f96 100644 --- a/cranelift/assembler-x64/meta/src/instructions/bitmanip.rs +++ b/cranelift/assembler-x64/meta/src/instructions/bitmanip.rs @@ -12,17 +12,17 @@ pub fn list() -> Vec { inst("bsrl", fmt("RM", [w(r32), r(rm32)]), rex([0x0F, 0xBD]).r(), _64b | compat), inst("bsrq", fmt("RM", [w(r64), r(rm64)]), rex([0x0F, 0xBD]).r().w(), _64b), - inst("tzcntw", fmt("A", [w(r16), r(rm16)]), rex([0x66, 0xF3, 0x0F, 0xBC]).r(), _64b | compat | bmi1), - inst("tzcntl", fmt("A", [w(r32), r(rm32)]), rex([0xF3, 0x0F, 0xBC]).r(), _64b | compat | bmi1), - inst("tzcntq", fmt("A", [w(r64), r(rm64)]), rex([0xF3, 0x0F, 0xBC]).r().w(), _64b | bmi1), + inst("tzcntw", fmt("A", [w(r16), r(rm16)]), rex([0x66, 0xF3, 0x0F, 0xBC]).r(), (_64b | compat) & bmi1), + inst("tzcntl", fmt("A", [w(r32), r(rm32)]), rex([0xF3, 0x0F, 0xBC]).r(), (_64b | compat) & bmi1), + inst("tzcntq", fmt("A", [w(r64), r(rm64)]), rex([0xF3, 0x0F, 0xBC]).r().w(), _64b & bmi1), - inst("lzcntw", fmt("RM", [w(r16), r(rm16)]), rex([0x66, 0xF3, 0x0F, 0xBD]).r(), _64b | compat | lzcnt), - inst("lzcntl", fmt("RM", [w(r32), r(rm32)]), rex([0xF3, 0x0F, 0xBD]).r(), _64b | compat | lzcnt), - inst("lzcntq", fmt("RM", [w(r64), r(rm64)]), rex([0xF3, 0x0F, 0xBD]).r().w(), _64b | lzcnt), + inst("lzcntw", fmt("RM", [w(r16), r(rm16)]), rex([0x66, 0xF3, 0x0F, 0xBD]).r(), (_64b | compat) & lzcnt), + inst("lzcntl", fmt("RM", [w(r32), r(rm32)]), rex([0xF3, 0x0F, 0xBD]).r(), (_64b | compat) & lzcnt), + inst("lzcntq", fmt("RM", [w(r64), r(rm64)]), rex([0xF3, 0x0F, 0xBD]).r().w(), _64b & lzcnt), - inst("popcntw", fmt("RM", [w(r16), r(rm16)]), rex([0x66, 0xF3, 0x0F, 0xB8]).r(), _64b | compat | popcnt), - inst("popcntl", fmt("RM", [w(r32), r(rm32)]), rex([0xF3, 0x0F, 0xB8]).r(), _64b | compat | popcnt), - inst("popcntq", fmt("RM", [w(r64), r(rm64)]), rex([0xF3, 0x0F, 0xB8]).r().w(), _64b | popcnt), + inst("popcntw", fmt("RM", [w(r16), r(rm16)]), rex([0x66, 0xF3, 0x0F, 0xB8]).r(), (_64b | compat) & popcnt), + inst("popcntl", fmt("RM", [w(r32), r(rm32)]), rex([0xF3, 0x0F, 0xB8]).r(), (_64b | compat) & popcnt), + inst("popcntq", fmt("RM", [w(r64), r(rm64)]), rex([0xF3, 0x0F, 0xB8]).r().w(), _64b & popcnt), inst("btw", fmt("MR", [r(rm16), r(r16)]).flags(W), rex([0x66, 0x0F, 0xA3]).r(), _64b | compat), inst("btl", fmt("MR", [r(rm32), r(r32)]).flags(W), rex([0x0F, 0xA3]).r(), _64b | compat), @@ -51,21 +51,21 @@ pub fn list() -> Vec { inst("bswapq", fmt("O", [rw(r64)]), rex([0x0F, 0xC8]).w().ro(), _64b), // BMI1 instructions - inst("blsrl", fmt("VM", [w(r32), r(rm32)]), vex(LZ)._0f38().w0().op(0xF3).digit(1), _64b | compat | bmi1), - inst("blsrq", fmt("VM", [w(r64), r(rm64)]), vex(LZ)._0f38().w1().op(0xF3).digit(1), _64b | bmi1), - inst("blsmskl", fmt("VM", [w(r32), r(rm32)]), vex(LZ)._0f38().w0().op(0xF3).digit(2), _64b | compat | bmi1), - inst("blsmskq", fmt("VM", [w(r64), r(rm64)]), vex(LZ)._0f38().w1().op(0xF3).digit(2), _64b | bmi1), - inst("blsil", fmt("VM", [w(r32), r(rm32)]), vex(LZ)._0f38().w0().op(0xF3).digit(3), _64b | compat | bmi1), - inst("blsiq", fmt("VM", [w(r64), r(rm64)]), vex(LZ)._0f38().w1().op(0xF3).digit(3), _64b | compat | bmi1), + inst("blsrl", fmt("VM", [w(r32), r(rm32)]), vex(LZ)._0f38().w0().op(0xF3).digit(1), (_64b | compat) & bmi1), + inst("blsrq", fmt("VM", [w(r64), r(rm64)]), vex(LZ)._0f38().w1().op(0xF3).digit(1), _64b & bmi1), + inst("blsmskl", fmt("VM", [w(r32), r(rm32)]), vex(LZ)._0f38().w0().op(0xF3).digit(2), (_64b | compat) & bmi1), + inst("blsmskq", fmt("VM", [w(r64), r(rm64)]), vex(LZ)._0f38().w1().op(0xF3).digit(2), _64b & bmi1), + inst("blsil", fmt("VM", [w(r32), r(rm32)]), vex(LZ)._0f38().w0().op(0xF3).digit(3), (_64b | compat) & bmi1), + inst("blsiq", fmt("VM", [w(r64), r(rm64)]), vex(LZ)._0f38().w1().op(0xF3).digit(3), (_64b | compat) & bmi1), // BMI2 instructions - inst("bzhil", fmt("RMV", [w(r32a), r(rm32), r(r32b)]), vex(LZ)._0f38().w0().op(0xF5), _64b | compat | bmi2), - inst("bzhiq", fmt("RMV", [w(r64a), r(rm64), r(r64b)]), vex(LZ)._0f38().w1().op(0xF5), _64b | bmi2), + inst("bzhil", fmt("RMV", [w(r32a), r(rm32), r(r32b)]), vex(LZ)._0f38().w0().op(0xF5), (_64b | compat) & bmi2), + inst("bzhiq", fmt("RMV", [w(r64a), r(rm64), r(r64b)]), vex(LZ)._0f38().w1().op(0xF5), _64b & bmi2), - inst("vpopcntb", fmt("A", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().w0().op(0x54).r(), _64b | compat | avx512vl | avx512bitalg), - inst("vpopcntw", fmt("A", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().w1().op(0x54).r(), _64b | compat | avx512vl | avx512bitalg), + inst("vpopcntb", fmt("A", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().w0().op(0x54).r(), (_64b | compat) & avx512vl & avx512bitalg), + inst("vpopcntw", fmt("A", [w(xmm1), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().w1().op(0x54).r(), (_64b | compat) & avx512vl & avx512bitalg), // FIXME: uncomment when avx512vpopcntdq is bound in cranelift - // inst("vpopcntd", fmt("A", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w0().op(0x55).r(), _64b | compat | avx512vl | avx512vpopcntdq), - // inst("vpopcntq", fmt("A", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w1().op(0x55).r(), _64b | compat | avx512vl | avx512vpopcntdq), + // inst("vpopcntd", fmt("A", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w0().op(0x55).r(), (_64b | compat) & avx512vl & avx512vpopcntdq), + // inst("vpopcntq", fmt("A", [w(xmm1), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w1().op(0x55).r(), (_64b | compat) & avx512vl & avx512vpopcntdq), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/cmp.rs b/cranelift/assembler-x64/meta/src/instructions/cmp.rs index 8a3337df07..7a65fc9159 100644 --- a/cranelift/assembler-x64/meta/src/instructions/cmp.rs +++ b/cranelift/assembler-x64/meta/src/instructions/cmp.rs @@ -38,38 +38,38 @@ pub fn list() -> Vec { inst("testl", fmt("MR", [r(rm32), r(r32)]).flags(W), rex([0x85]), _64b | compat), inst("testq", fmt("MR", [r(rm64), r(r64)]).flags(W), rex([0x85]).w(), _64b | compat), // `AND` xmm and set flags. - inst("ptest", fmt("RM", [r(xmm1), r(align(xmm_m128))]).flags(W), rex([0x66, 0x0F, 0x38, 0x17]).r(), _64b | compat | sse41).alt(avx, "vptest_rm"), - inst("vptest", fmt("RM", [r(xmm1), r(xmm_m128)]).flags(W), vex(L128)._66()._0f38().op(0x17).r(), _64b | compat | avx), + inst("ptest", fmt("RM", [r(xmm1), r(align(xmm_m128))]).flags(W), rex([0x66, 0x0F, 0x38, 0x17]).r(), (_64b | compat) & sse41).alt(avx, "vptest_rm"), + inst("vptest", fmt("RM", [r(xmm1), r(xmm_m128)]).flags(W), vex(L128)._66()._0f38().op(0x17).r(), (_64b | compat) & avx), // Compare floating point and set flags. - inst("ucomiss", fmt("A", [r(xmm1), r(xmm_m32)]).flags(W), rex([0x0F, 0x2E]).r(), _64b | compat | sse).alt(avx, "vucomiss_a"), - inst("ucomisd", fmt("A", [r(xmm1), r(xmm_m64)]).flags(W), rex([0x66, 0x0F, 0x2E]).r(), _64b | compat | sse2).alt(avx, "vucomisd_a"), - inst("vucomiss", fmt("A", [r(xmm2), r(xmm_m32)]).flags(W), vex(LIG)._0f().op(0x2E).r(), _64b | compat | avx), - inst("vucomisd", fmt("A", [r(xmm2), r(xmm_m64)]).flags(W), vex(LIG)._66()._0f().op(0x2E).r(), _64b | compat | avx), + inst("ucomiss", fmt("A", [r(xmm1), r(xmm_m32)]).flags(W), rex([0x0F, 0x2E]).r(), (_64b | compat) & sse).alt(avx, "vucomiss_a"), + inst("ucomisd", fmt("A", [r(xmm1), r(xmm_m64)]).flags(W), rex([0x66, 0x0F, 0x2E]).r(), (_64b | compat) & sse2).alt(avx, "vucomisd_a"), + inst("vucomiss", fmt("A", [r(xmm2), r(xmm_m32)]).flags(W), vex(LIG)._0f().op(0x2E).r(), (_64b | compat) & avx), + inst("vucomisd", fmt("A", [r(xmm2), r(xmm_m64)]).flags(W), vex(LIG)._66()._0f().op(0x2E).r(), (_64b | compat) & avx), // Floating-point comparisons. - inst("cmpss", fmt("A", [rw(xmm1), r(xmm_m32), r(imm8)]), rex([0xF3, 0x0F, 0xC2]).r().ib(), _64b | compat | sse).custom(Display), - inst("cmpsd", fmt("A", [rw(xmm1), r(xmm_m64), r(imm8)]), rex([0xF2, 0x0F, 0xC2]).r().ib(), _64b | compat | sse2).custom(Display), - inst("cmpps", fmt("A", [rw(xmm1), r(xmm_m128), r(imm8)]), rex([0x0F, 0xC2]).r().ib(), _64b | compat | sse).custom(Display), - inst("cmppd", fmt("A", [rw(xmm1), r(xmm_m128), r(imm8)]), rex([0x66, 0x0F, 0xC2]).r().ib(), _64b | compat | sse2).custom(Display), - inst("vcmpss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32), r(imm8)]), vex(LIG)._f3()._0f().op(0xC2).r().ib(), _64b | compat | avx).custom(Display), - inst("vcmpsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64), r(imm8)]), vex(LIG)._f2()._0f().op(0xC2).r().ib(), _64b | compat | avx).custom(Display), - inst("vcmpps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._0f().op(0xC2).r().ib(), _64b | compat | avx).custom(Display), - inst("vcmppd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f().op(0xC2).r().ib(), _64b | compat | avx).custom(Display), + inst("cmpss", fmt("A", [rw(xmm1), r(xmm_m32), r(imm8)]), rex([0xF3, 0x0F, 0xC2]).r().ib(), (_64b | compat) & sse).custom(Display), + inst("cmpsd", fmt("A", [rw(xmm1), r(xmm_m64), r(imm8)]), rex([0xF2, 0x0F, 0xC2]).r().ib(), (_64b | compat) & sse2).custom(Display), + inst("cmpps", fmt("A", [rw(xmm1), r(xmm_m128), r(imm8)]), rex([0x0F, 0xC2]).r().ib(), (_64b | compat) & sse).custom(Display), + inst("cmppd", fmt("A", [rw(xmm1), r(xmm_m128), r(imm8)]), rex([0x66, 0x0F, 0xC2]).r().ib(), (_64b | compat) & sse2).custom(Display), + inst("vcmpss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32), r(imm8)]), vex(LIG)._f3()._0f().op(0xC2).r().ib(), (_64b | compat) & avx).custom(Display), + inst("vcmpsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64), r(imm8)]), vex(LIG)._f2()._0f().op(0xC2).r().ib(), (_64b | compat) & avx).custom(Display), + inst("vcmpps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._0f().op(0xC2).r().ib(), (_64b | compat) & avx).custom(Display), + inst("vcmppd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f().op(0xC2).r().ib(), (_64b | compat) & avx).custom(Display), // Packed lane-by-lane comparisons. - inst("pcmpeqb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x74]), _64b | compat | sse2).alt(avx, "vpcmpeqb_b"), - inst("pcmpeqw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x75]), _64b | compat | sse2).alt(avx, "vpcmpeqw_b"), - inst("pcmpeqd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x76]), _64b | compat | sse2).alt(avx, "vpcmpeqd_b"), - inst("pcmpeqq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x29]), _64b | compat | sse41).alt(avx, "vpcmpeqq_b"), - inst("pcmpgtb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x64]), _64b | compat | sse2).alt(avx, "vpcmpgtb_b"), - inst("pcmpgtw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x65]), _64b | compat | sse2).alt(avx, "vpcmpgtw_b"), - inst("pcmpgtd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x66]), _64b | compat | sse2).alt(avx, "vpcmpgtd_b"), - inst("pcmpgtq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x37]), _64b | compat | sse42).alt(avx, "vpcmpgtq_b"), - inst("vpcmpeqb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x74), _64b | compat | avx), - inst("vpcmpeqw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x75), _64b | compat | avx), - inst("vpcmpeqd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x76), _64b | compat | avx), - inst("vpcmpeqq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x29), _64b | compat | avx), - inst("vpcmpgtb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x64), _64b | compat | avx), - inst("vpcmpgtw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x65), _64b | compat | avx), - inst("vpcmpgtd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x66), _64b | compat | avx), - inst("vpcmpgtq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x37), _64b | compat | avx), + inst("pcmpeqb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x74]), (_64b | compat) & sse2).alt(avx, "vpcmpeqb_b"), + inst("pcmpeqw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x75]), (_64b | compat) & sse2).alt(avx, "vpcmpeqw_b"), + inst("pcmpeqd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x76]), (_64b | compat) & sse2).alt(avx, "vpcmpeqd_b"), + inst("pcmpeqq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x29]), (_64b | compat) & sse41).alt(avx, "vpcmpeqq_b"), + inst("pcmpgtb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x64]), (_64b | compat) & sse2).alt(avx, "vpcmpgtb_b"), + inst("pcmpgtw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x65]), (_64b | compat) & sse2).alt(avx, "vpcmpgtw_b"), + inst("pcmpgtd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x66]), (_64b | compat) & sse2).alt(avx, "vpcmpgtd_b"), + inst("pcmpgtq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x37]), (_64b | compat) & sse42).alt(avx, "vpcmpgtq_b"), + inst("vpcmpeqb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x74), (_64b | compat) & avx), + inst("vpcmpeqw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x75), (_64b | compat) & avx), + inst("vpcmpeqd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x76), (_64b | compat) & avx), + inst("vpcmpeqq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x29), (_64b | compat) & avx), + inst("vpcmpgtb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x64), (_64b | compat) & avx), + inst("vpcmpgtw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x65), (_64b | compat) & avx), + inst("vpcmpgtd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x66), (_64b | compat) & avx), + inst("vpcmpgtq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x37), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/cvt.rs b/cranelift/assembler-x64/meta/src/instructions/cvt.rs index e700d22414..9f03f4c984 100644 --- a/cranelift/assembler-x64/meta/src/instructions/cvt.rs +++ b/cranelift/assembler-x64/meta/src/instructions/cvt.rs @@ -5,53 +5,53 @@ use crate::dsl::{align, evex, fmt, inst, r, rex, rw, vex, w}; pub fn list() -> Vec { vec![ // From 32-bit floating point. - inst("cvtps2pd", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x0F, 0x5A]).r(), _64b | compat | sse2), - inst("cvttps2dq", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0xF3, 0x0F, 0x5B]).r(), _64b | compat | sse2), - inst("cvtss2sd", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x5A]).r(), _64b | compat | sse2), - inst("cvtss2si", fmt("A", [w(r32), r(xmm_m32)]), rex([0xF3, 0x0F, 0x2D]).r(), _64b | compat | sse), - inst("cvtss2si", fmt("AQ", [w(r64), r(xmm_m32)]), rex([0xF3, 0x0F, 0x2D]).w().r(), _64b | sse), - inst("cvttss2si", fmt("A", [w(r32), r(xmm_m32)]), rex([0xF3, 0x0F, 0x2C]).r(), _64b | compat | sse), - inst("cvttss2si", fmt("AQ", [w(r64), r(xmm_m32)]), rex([0xF3, 0x0F, 0x2C]).w().r(), _64b | sse), + inst("cvtps2pd", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x0F, 0x5A]).r(), (_64b | compat) & sse2), + inst("cvttps2dq", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0xF3, 0x0F, 0x5B]).r(), (_64b | compat) & sse2), + inst("cvtss2sd", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x5A]).r(), (_64b | compat) & sse2), + inst("cvtss2si", fmt("A", [w(r32), r(xmm_m32)]), rex([0xF3, 0x0F, 0x2D]).r(), (_64b | compat) & sse), + inst("cvtss2si", fmt("AQ", [w(r64), r(xmm_m32)]), rex([0xF3, 0x0F, 0x2D]).w().r(), _64b & sse), + inst("cvttss2si", fmt("A", [w(r32), r(xmm_m32)]), rex([0xF3, 0x0F, 0x2C]).r(), (_64b | compat) & sse), + inst("cvttss2si", fmt("AQ", [w(r64), r(xmm_m32)]), rex([0xF3, 0x0F, 0x2C]).w().r(), _64b & sse), - inst("vcvtps2pd", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._0f().op(0x5A).r(), _64b | compat | avx), - inst("vcvttps2dq", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._f3()._0f().op(0x5B).r(), _64b | compat | avx), - inst("vcvtss2sd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x5A).r(), _64b | compat | avx), - inst("vcvtss2si", fmt("A", [w(r32), r(xmm_m32)]), vex(LIG)._f3()._0f().w0().op(0x2D).r(), _64b | compat | avx), - inst("vcvtss2si", fmt("AQ", [w(r64), r(xmm_m32)]), vex(LIG)._f3()._0f().w1().op(0x2D).r(), _64b | avx), - inst("vcvttss2si", fmt("A", [w(r32), r(xmm_m32)]), vex(LIG)._f3()._0f().w0().op(0x2C).r(), _64b | compat | avx), - inst("vcvttss2si", fmt("AQ", [w(r64), r(xmm_m32)]), vex(LIG)._f3()._0f().w1().op(0x2C).r(), _64b | avx), + inst("vcvtps2pd", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._0f().op(0x5A).r(), (_64b | compat) & avx), + inst("vcvttps2dq", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._f3()._0f().op(0x5B).r(), (_64b | compat) & avx), + inst("vcvtss2sd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x5A).r(), (_64b | compat) & avx), + inst("vcvtss2si", fmt("A", [w(r32), r(xmm_m32)]), vex(LIG)._f3()._0f().w0().op(0x2D).r(), (_64b | compat) & avx), + inst("vcvtss2si", fmt("AQ", [w(r64), r(xmm_m32)]), vex(LIG)._f3()._0f().w1().op(0x2D).r(), _64b & avx), + inst("vcvttss2si", fmt("A", [w(r32), r(xmm_m32)]), vex(LIG)._f3()._0f().w0().op(0x2C).r(), (_64b | compat) & avx), + inst("vcvttss2si", fmt("AQ", [w(r64), r(xmm_m32)]), vex(LIG)._f3()._0f().w1().op(0x2C).r(), _64b & avx), // From 64-bit floating point. - inst("cvtpd2ps", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x5A]).r(), _64b | compat | sse2), - inst("cvttpd2dq", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE6]).r(), _64b | compat | sse2), - inst("cvtsd2ss", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x5A]).r(), _64b | compat | sse2), - inst("cvtsd2si", fmt("A", [w(r32), r(xmm_m64)]), rex([0xF2, 0x0F, 0x2D]).r(), _64b | compat | sse2), - inst("cvtsd2si", fmt("AQ", [w(r64), r(xmm_m64)]), rex([0xF2, 0x0F, 0x2D]).w().r(), _64b | sse2), - inst("cvttsd2si", fmt("A", [w(r32), r(xmm_m64)]), rex([0xF2, 0x0F, 0x2C]).r(), _64b | compat | sse2), - inst("cvttsd2si", fmt("AQ", [w(r64), r(xmm_m64)]), rex([0xF2, 0x0F, 0x2C]).w().r(), _64b | sse2), + inst("cvtpd2ps", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x5A]).r(), (_64b | compat) & sse2), + inst("cvttpd2dq", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE6]).r(), (_64b | compat) & sse2), + inst("cvtsd2ss", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x5A]).r(), (_64b | compat) & sse2), + inst("cvtsd2si", fmt("A", [w(r32), r(xmm_m64)]), rex([0xF2, 0x0F, 0x2D]).r(), (_64b | compat) & sse2), + inst("cvtsd2si", fmt("AQ", [w(r64), r(xmm_m64)]), rex([0xF2, 0x0F, 0x2D]).w().r(), _64b & sse2), + inst("cvttsd2si", fmt("A", [w(r32), r(xmm_m64)]), rex([0xF2, 0x0F, 0x2C]).r(), (_64b | compat) & sse2), + inst("cvttsd2si", fmt("AQ", [w(r64), r(xmm_m64)]), rex([0xF2, 0x0F, 0x2C]).w().r(), _64b & sse2), - inst("vcvtpd2ps", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f().op(0x5A).r(), _64b | compat | avx).custom(Mnemonic), - inst("vcvttpd2dq", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE6).r(), _64b | compat | avx).custom(Mnemonic), - inst("vcvtsd2ss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x5A).r(), _64b | compat | avx), - inst("vcvtsd2si", fmt("A", [w(r32), r(xmm_m64)]), vex(LIG)._f2()._0f().w0().op(0x2D).r(), _64b | compat | avx), - inst("vcvtsd2si", fmt("AQ", [w(r64), r(xmm_m64)]), vex(LIG)._f2()._0f().w1().op(0x2D).r(), _64b | avx), - inst("vcvttsd2si", fmt("A", [w(r32), r(xmm_m64)]), vex(LIG)._f2()._0f().w0().op(0x2C).r(), _64b | compat | avx), - inst("vcvttsd2si", fmt("AQ", [w(r64), r(xmm_m64)]), vex(LIG)._f2()._0f().w1().op(0x2C).r(), _64b | avx), + inst("vcvtpd2ps", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f().op(0x5A).r(), (_64b | compat) & avx).custom(Mnemonic), + inst("vcvttpd2dq", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE6).r(), (_64b | compat) & avx).custom(Mnemonic), + inst("vcvtsd2ss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x5A).r(), (_64b | compat) & avx), + inst("vcvtsd2si", fmt("A", [w(r32), r(xmm_m64)]), vex(LIG)._f2()._0f().w0().op(0x2D).r(), (_64b | compat) & avx), + inst("vcvtsd2si", fmt("AQ", [w(r64), r(xmm_m64)]), vex(LIG)._f2()._0f().w1().op(0x2D).r(), _64b & avx), + inst("vcvttsd2si", fmt("A", [w(r32), r(xmm_m64)]), vex(LIG)._f2()._0f().w0().op(0x2C).r(), (_64b | compat) & avx), + inst("vcvttsd2si", fmt("AQ", [w(r64), r(xmm_m64)]), vex(LIG)._f2()._0f().w1().op(0x2C).r(), _64b & avx), // From signed 32-bit integer. - inst("cvtdq2ps", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x5B]).r(), _64b | compat | sse2), - inst("cvtdq2pd", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0xF3, 0x0F, 0xE6]).r(), _64b | compat | sse2), - inst("cvtsi2ssl", fmt("A", [rw(xmm1), r(rm32)]), rex([0xF3, 0x0F, 0x2A]).r(), _64b | compat | sse), - inst("cvtsi2ssq", fmt("A", [rw(xmm1), r(rm64)]), rex([0xF3, 0x0F, 0x2A]).w().r(), _64b | sse), - inst("cvtsi2sdl", fmt("A", [rw(xmm1), r(rm32)]), rex([0xF2, 0x0F, 0x2A]).r(), _64b | compat | sse2), - inst("cvtsi2sdq", fmt("A", [rw(xmm1), r(rm64)]), rex([0xF2, 0x0F, 0x2A]).w().r(), _64b | sse2), + inst("cvtdq2ps", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x5B]).r(), (_64b | compat) & sse2), + inst("cvtdq2pd", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0xF3, 0x0F, 0xE6]).r(), (_64b | compat) & sse2), + inst("cvtsi2ssl", fmt("A", [rw(xmm1), r(rm32)]), rex([0xF3, 0x0F, 0x2A]).r(), (_64b | compat) & sse), + inst("cvtsi2ssq", fmt("A", [rw(xmm1), r(rm64)]), rex([0xF3, 0x0F, 0x2A]).w().r(), _64b & sse), + inst("cvtsi2sdl", fmt("A", [rw(xmm1), r(rm32)]), rex([0xF2, 0x0F, 0x2A]).r(), (_64b | compat) & sse2), + inst("cvtsi2sdq", fmt("A", [rw(xmm1), r(rm64)]), rex([0xF2, 0x0F, 0x2A]).w().r(), _64b & sse2), - inst("vcvtdq2pd", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._f3()._0f().op(0xE6).r(), _64b | compat | avx), - inst("vcvtdq2ps", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._0f().op(0x5B).r(), _64b | compat | avx), - inst("vcvtsi2sdl", fmt("B", [w(xmm1), r(xmm2), r(rm32)]), vex(LIG)._f2()._0f().w0().op(0x2A).r(), _64b | compat | avx), - inst("vcvtsi2sdq", fmt("B", [w(xmm1), r(xmm2), r(rm64)]), vex(LIG)._f2()._0f().w1().op(0x2A).r(), _64b | avx), - inst("vcvtsi2ssl", fmt("B", [w(xmm1), r(xmm2), r(rm32)]), vex(LIG)._f3()._0f().w0().op(0x2A).r(), _64b | compat | avx), - inst("vcvtsi2ssq", fmt("B", [w(xmm1), r(xmm2), r(rm64)]), vex(LIG)._f3()._0f().w1().op(0x2A).r(), _64b | avx), + inst("vcvtdq2pd", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._f3()._0f().op(0xE6).r(), (_64b | compat) & avx), + inst("vcvtdq2ps", fmt("A", [w(xmm1), r(xmm_m128)]), vex(L128)._0f().op(0x5B).r(), (_64b | compat) & avx), + inst("vcvtsi2sdl", fmt("B", [w(xmm1), r(xmm2), r(rm32)]), vex(LIG)._f2()._0f().w0().op(0x2A).r(), (_64b | compat) & avx), + inst("vcvtsi2sdq", fmt("B", [w(xmm1), r(xmm2), r(rm64)]), vex(LIG)._f2()._0f().w1().op(0x2A).r(), _64b & avx), + inst("vcvtsi2ssl", fmt("B", [w(xmm1), r(xmm2), r(rm32)]), vex(LIG)._f3()._0f().w0().op(0x2A).r(), (_64b | compat) & avx), + inst("vcvtsi2ssq", fmt("B", [w(xmm1), r(xmm2), r(rm64)]), vex(LIG)._f3()._0f().w1().op(0x2A).r(), _64b & avx), // Currently omitted as Cranelift doesn't need them but could be added // in the future: diff --git a/cranelift/assembler-x64/meta/src/instructions/div.rs b/cranelift/assembler-x64/meta/src/instructions/div.rs index 3d483696a9..e7cf39ba31 100644 --- a/cranelift/assembler-x64/meta/src/instructions/div.rs +++ b/cranelift/assembler-x64/meta/src/instructions/div.rs @@ -13,13 +13,13 @@ pub fn list() -> Vec { inst("idivl", fmt("M", [rw(implicit(eax)), rw(implicit(edx)), r(rm32)]), rex([0xF7]).digit(7), _64b | compat).has_trap(), inst("idivq", fmt("M", [rw(implicit(rax)), rw(implicit(rdx)), r(rm64)]), rex([0xF7]).digit(7).w(), _64b).has_trap(), // Vector instructions. - inst("divss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0xF, 0x5E]).r(), _64b | compat | sse).alt(avx, "vdivss_b"), - inst("divsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0xF, 0x5E]).r(), _64b | compat | sse2).alt(avx, "vdivsd_b"), - inst("divps", fmt("A", [rw(xmm1), align(xmm_m128)]), rex([0xF, 0x5E]).r(), _64b | compat | sse).alt(avx, "vdivps_b"), - inst("divpd", fmt("A", [rw(xmm1), align(xmm_m128)]), rex([0x66, 0x0F, 0x5E]).r(), _64b | compat | sse2).alt(avx, "vdivpd_b"), - inst("vdivss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x5E).r(), _64b | compat | avx), - inst("vdivsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x5E).r(), _64b | compat | avx), - inst("vdivps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x5E).r(), _64b | compat | avx), - inst("vdivpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x5E).r(), _64b | compat | avx), + inst("divss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0xF, 0x5E]).r(), (_64b | compat) & sse).alt(avx, "vdivss_b"), + inst("divsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0xF, 0x5E]).r(), (_64b | compat) & sse2).alt(avx, "vdivsd_b"), + inst("divps", fmt("A", [rw(xmm1), align(xmm_m128)]), rex([0xF, 0x5E]).r(), (_64b | compat) & sse).alt(avx, "vdivps_b"), + inst("divpd", fmt("A", [rw(xmm1), align(xmm_m128)]), rex([0x66, 0x0F, 0x5E]).r(), (_64b | compat) & sse2).alt(avx, "vdivpd_b"), + inst("vdivss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x5E).r(), (_64b | compat) & avx), + inst("vdivsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x5E).r(), (_64b | compat) & avx), + inst("vdivps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x5E).r(), (_64b | compat) & avx), + inst("vdivpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x5E).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/fma.rs b/cranelift/assembler-x64/meta/src/instructions/fma.rs index f476ab9767..f177a1e73b 100644 --- a/cranelift/assembler-x64/meta/src/instructions/fma.rs +++ b/cranelift/assembler-x64/meta/src/instructions/fma.rs @@ -13,58 +13,58 @@ pub fn list() -> Vec { // - `132` means `op1 * op2 + op3`, // - `213` means `op2 * op1 + op3`, and // - `231` means `op2 * op3 + op1`. - inst("vfmadd132ss", fmt("A", single_ops), enc().w0().op(0x99).r(), _64b | compat | fma), - inst("vfmadd213ss", fmt("A", single_ops), enc().w0().op(0xA9).r(), _64b | compat | fma), - inst("vfmadd231ss", fmt("A", single_ops), enc().w0().op(0xB9).r(), _64b | compat | fma), - inst("vfmadd132sd", fmt("A", double_ops), enc().w1().op(0x99).r(), _64b | compat | fma), - inst("vfmadd213sd", fmt("A", double_ops), enc().w1().op(0xA9).r(), _64b | compat | fma), - inst("vfmadd231sd", fmt("A", double_ops), enc().w1().op(0xB9).r(), _64b | compat | fma), - inst("vfmadd132ps", fmt("A", packed_ops), enc().w0().op(0x98).r(), _64b | compat | fma), - inst("vfmadd213ps", fmt("A", packed_ops), enc().w0().op(0xA8).r(), _64b | compat | fma), - inst("vfmadd231ps", fmt("A", packed_ops), enc().w0().op(0xB8).r(), _64b | compat | fma), - inst("vfmadd132pd", fmt("A", packed_ops), enc().w1().op(0x98).r(), _64b | compat | fma), - inst("vfmadd213pd", fmt("A", packed_ops), enc().w1().op(0xA8).r(), _64b | compat | fma), - inst("vfmadd231pd", fmt("A", packed_ops), enc().w1().op(0xB8).r(), _64b | compat | fma), + inst("vfmadd132ss", fmt("A", single_ops), enc().w0().op(0x99).r(), (_64b | compat) & fma), + inst("vfmadd213ss", fmt("A", single_ops), enc().w0().op(0xA9).r(), (_64b | compat) & fma), + inst("vfmadd231ss", fmt("A", single_ops), enc().w0().op(0xB9).r(), (_64b | compat) & fma), + inst("vfmadd132sd", fmt("A", double_ops), enc().w1().op(0x99).r(), (_64b | compat) & fma), + inst("vfmadd213sd", fmt("A", double_ops), enc().w1().op(0xA9).r(), (_64b | compat) & fma), + inst("vfmadd231sd", fmt("A", double_ops), enc().w1().op(0xB9).r(), (_64b | compat) & fma), + inst("vfmadd132ps", fmt("A", packed_ops), enc().w0().op(0x98).r(), (_64b | compat) & fma), + inst("vfmadd213ps", fmt("A", packed_ops), enc().w0().op(0xA8).r(), (_64b | compat) & fma), + inst("vfmadd231ps", fmt("A", packed_ops), enc().w0().op(0xB8).r(), (_64b | compat) & fma), + inst("vfmadd132pd", fmt("A", packed_ops), enc().w1().op(0x98).r(), (_64b | compat) & fma), + inst("vfmadd213pd", fmt("A", packed_ops), enc().w1().op(0xA8).r(), (_64b | compat) & fma), + inst("vfmadd231pd", fmt("A", packed_ops), enc().w1().op(0xB8).r(), (_64b | compat) & fma), // Fused Negative Multiply-Add (FNMA); like FMA, but with the // multiplication result negated. - inst("vfnmadd132ss", fmt("A", single_ops), enc().w0().op(0x9D).r(), _64b | compat | fma), - inst("vfnmadd213ss", fmt("A", single_ops), enc().w0().op(0xAD).r(), _64b | compat | fma), - inst("vfnmadd231ss", fmt("A", single_ops), enc().w0().op(0xBD).r(), _64b | compat | fma), - inst("vfnmadd132sd", fmt("A", double_ops), enc().w1().op(0x9D).r(), _64b | compat | fma), - inst("vfnmadd213sd", fmt("A", double_ops), enc().w1().op(0xAD).r(), _64b | compat | fma), - inst("vfnmadd231sd", fmt("A", double_ops), enc().w1().op(0xBD).r(), _64b | compat | fma), - inst("vfnmadd132ps", fmt("A", packed_ops), enc().w0().op(0x9C).r(), _64b | compat | fma), - inst("vfnmadd213ps", fmt("A", packed_ops), enc().w0().op(0xAC).r(), _64b | compat | fma), - inst("vfnmadd231ps", fmt("A", packed_ops), enc().w0().op(0xBC).r(), _64b | compat | fma), - inst("vfnmadd132pd", fmt("A", packed_ops), enc().w1().op(0x9C).r(), _64b | compat | fma), - inst("vfnmadd213pd", fmt("A", packed_ops), enc().w1().op(0xAC).r(), _64b | compat | fma), - inst("vfnmadd231pd", fmt("A", packed_ops), enc().w1().op(0xBC).r(), _64b | compat | fma), + inst("vfnmadd132ss", fmt("A", single_ops), enc().w0().op(0x9D).r(), (_64b | compat) & fma), + inst("vfnmadd213ss", fmt("A", single_ops), enc().w0().op(0xAD).r(), (_64b | compat) & fma), + inst("vfnmadd231ss", fmt("A", single_ops), enc().w0().op(0xBD).r(), (_64b | compat) & fma), + inst("vfnmadd132sd", fmt("A", double_ops), enc().w1().op(0x9D).r(), (_64b | compat) & fma), + inst("vfnmadd213sd", fmt("A", double_ops), enc().w1().op(0xAD).r(), (_64b | compat) & fma), + inst("vfnmadd231sd", fmt("A", double_ops), enc().w1().op(0xBD).r(), (_64b | compat) & fma), + inst("vfnmadd132ps", fmt("A", packed_ops), enc().w0().op(0x9C).r(), (_64b | compat) & fma), + inst("vfnmadd213ps", fmt("A", packed_ops), enc().w0().op(0xAC).r(), (_64b | compat) & fma), + inst("vfnmadd231ps", fmt("A", packed_ops), enc().w0().op(0xBC).r(), (_64b | compat) & fma), + inst("vfnmadd132pd", fmt("A", packed_ops), enc().w1().op(0x9C).r(), (_64b | compat) & fma), + inst("vfnmadd213pd", fmt("A", packed_ops), enc().w1().op(0xAC).r(), (_64b | compat) & fma), + inst("vfnmadd231pd", fmt("A", packed_ops), enc().w1().op(0xBC).r(), (_64b | compat) & fma), // Fused Multiply-Subtract (FMS); like FMA, but subtracting // from the multiplication result. - inst("vfmsub132ss", fmt("A", single_ops), enc().w0().op(0x9B).r(), _64b | compat | fma), - inst("vfmsub213ss", fmt("A", single_ops), enc().w0().op(0xAB).r(), _64b | compat | fma), - inst("vfmsub231ss", fmt("A", single_ops), enc().w0().op(0xBB).r(), _64b | compat | fma), - inst("vfmsub132sd", fmt("A", double_ops), enc().w1().op(0x9B).r(), _64b | compat | fma), - inst("vfmsub213sd", fmt("A", double_ops), enc().w1().op(0xAB).r(), _64b | compat | fma), - inst("vfmsub231sd", fmt("A", double_ops), enc().w1().op(0xBB).r(), _64b | compat | fma), - inst("vfmsub132ps", fmt("A", packed_ops), enc().w0().op(0x9A).r(), _64b | compat | fma), - inst("vfmsub213ps", fmt("A", packed_ops), enc().w0().op(0xAA).r(), _64b | compat | fma), - inst("vfmsub231ps", fmt("A", packed_ops), enc().w0().op(0xBA).r(), _64b | compat | fma), - inst("vfmsub132pd", fmt("A", packed_ops), enc().w1().op(0x9A).r(), _64b | compat | fma), - inst("vfmsub213pd", fmt("A", packed_ops), enc().w1().op(0xAA).r(), _64b | compat | fma), - inst("vfmsub231pd", fmt("A", packed_ops), enc().w1().op(0xBA).r(), _64b | compat | fma), + inst("vfmsub132ss", fmt("A", single_ops), enc().w0().op(0x9B).r(), (_64b | compat) & fma), + inst("vfmsub213ss", fmt("A", single_ops), enc().w0().op(0xAB).r(), (_64b | compat) & fma), + inst("vfmsub231ss", fmt("A", single_ops), enc().w0().op(0xBB).r(), (_64b | compat) & fma), + inst("vfmsub132sd", fmt("A", double_ops), enc().w1().op(0x9B).r(), (_64b | compat) & fma), + inst("vfmsub213sd", fmt("A", double_ops), enc().w1().op(0xAB).r(), (_64b | compat) & fma), + inst("vfmsub231sd", fmt("A", double_ops), enc().w1().op(0xBB).r(), (_64b | compat) & fma), + inst("vfmsub132ps", fmt("A", packed_ops), enc().w0().op(0x9A).r(), (_64b | compat) & fma), + inst("vfmsub213ps", fmt("A", packed_ops), enc().w0().op(0xAA).r(), (_64b | compat) & fma), + inst("vfmsub231ps", fmt("A", packed_ops), enc().w0().op(0xBA).r(), (_64b | compat) & fma), + inst("vfmsub132pd", fmt("A", packed_ops), enc().w1().op(0x9A).r(), (_64b | compat) & fma), + inst("vfmsub213pd", fmt("A", packed_ops), enc().w1().op(0xAA).r(), (_64b | compat) & fma), + inst("vfmsub231pd", fmt("A", packed_ops), enc().w1().op(0xBA).r(), (_64b | compat) & fma), // Fused Negative Multiply-Subtract (FNMS). - inst("vfnmsub132ss", fmt("A", single_ops), enc().w0().op(0x9F).r(), _64b | compat | fma), - inst("vfnmsub213ss", fmt("A", single_ops), enc().w0().op(0xAF).r(), _64b | compat | fma), - inst("vfnmsub231ss", fmt("A", single_ops), enc().w0().op(0xBF).r(), _64b | compat | fma), - inst("vfnmsub132sd", fmt("A", double_ops), enc().w1().op(0x9F).r(), _64b | compat | fma), - inst("vfnmsub213sd", fmt("A", double_ops), enc().w1().op(0xAF).r(), _64b | compat | fma), - inst("vfnmsub231sd", fmt("A", double_ops), enc().w1().op(0xBF).r(), _64b | compat | fma), - inst("vfnmsub132ps", fmt("A", packed_ops), enc().w0().op(0x9E).r(), _64b | compat | fma), - inst("vfnmsub213ps", fmt("A", packed_ops), enc().w0().op(0xAE).r(), _64b | compat | fma), - inst("vfnmsub231ps", fmt("A", packed_ops), enc().w0().op(0xBE).r(), _64b | compat | fma), - inst("vfnmsub132pd", fmt("A", packed_ops), enc().w1().op(0x9E).r(), _64b | compat | fma), - inst("vfnmsub213pd", fmt("A", packed_ops), enc().w1().op(0xAE).r(), _64b | compat | fma), - inst("vfnmsub231pd", fmt("A", packed_ops), enc().w1().op(0xBE).r(), _64b | compat | fma), + inst("vfnmsub132ss", fmt("A", single_ops), enc().w0().op(0x9F).r(), (_64b | compat) & fma), + inst("vfnmsub213ss", fmt("A", single_ops), enc().w0().op(0xAF).r(), (_64b | compat) & fma), + inst("vfnmsub231ss", fmt("A", single_ops), enc().w0().op(0xBF).r(), (_64b | compat) & fma), + inst("vfnmsub132sd", fmt("A", double_ops), enc().w1().op(0x9F).r(), (_64b | compat) & fma), + inst("vfnmsub213sd", fmt("A", double_ops), enc().w1().op(0xAF).r(), (_64b | compat) & fma), + inst("vfnmsub231sd", fmt("A", double_ops), enc().w1().op(0xBF).r(), (_64b | compat) & fma), + inst("vfnmsub132ps", fmt("A", packed_ops), enc().w0().op(0x9E).r(), (_64b | compat) & fma), + inst("vfnmsub213ps", fmt("A", packed_ops), enc().w0().op(0xAE).r(), (_64b | compat) & fma), + inst("vfnmsub231ps", fmt("A", packed_ops), enc().w0().op(0xBE).r(), (_64b | compat) & fma), + inst("vfnmsub132pd", fmt("A", packed_ops), enc().w1().op(0x9E).r(), (_64b | compat) & fma), + inst("vfnmsub213pd", fmt("A", packed_ops), enc().w1().op(0xAE).r(), (_64b | compat) & fma), + inst("vfnmsub231pd", fmt("A", packed_ops), enc().w1().op(0xBE).r(), (_64b | compat) & fma), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/lanes.rs b/cranelift/assembler-x64/meta/src/instructions/lanes.rs index d85f3c9b2c..47d3cea7e3 100644 --- a/cranelift/assembler-x64/meta/src/instructions/lanes.rs +++ b/cranelift/assembler-x64/meta/src/instructions/lanes.rs @@ -12,82 +12,82 @@ pub fn list() -> Vec { vec![ // Extract from a single XMM lane. - inst("extractps", fmt("A", [w(rm32), r(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x17]).r().ib(), _64b | compat | sse41).alt(avx, "vextractps_b"), - inst("pextrb", fmt("A", [w(r32m8), r(xmm2), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x14]).r().ib(), _64b | compat | sse41).alt(avx, "vpextrb_a"), - inst("pextrw", fmt("A", [w(r32), r(xmm2), r(imm8)]), rex([0x66, 0x0F, 0xC5]).r().ib(), _64b | compat | sse2).alt(avx, "vpextrw_a"), - inst("pextrw", fmt("B", [w(r32m16), r(xmm2), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x15]).r().ib(), _64b | compat | sse41).alt(avx, "vpextrw_b"), - inst("pextrd", fmt("A", [w(rm32), r(xmm2), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x16]).r().ib(), _64b | compat | sse41).alt(avx, "vpextrd_a"), - inst("pextrq", fmt("A", [w(rm64), r(xmm2), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x16]).w().r().ib(), _64b | sse41).alt(avx, "vpextrq_a"), - inst("vextractps", fmt("B", [w(rm32), r(xmm1), r(imm8)]), vex(L128)._66()._0f3a().wig().op(0x17).r().ib(), _64b | compat | avx), - inst("vpextrb", fmt("A", [w(r32m8), r(xmm2), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x14).r().ib(), _64b | compat | avx), - inst("vpextrw", fmt("A", [w(r32), r(xmm2), r(imm8)]), vex(L128)._66()._0f().w0().op(0xC5).r().ib(), _64b | compat | avx), - inst("vpextrw", fmt("B", [w(r32m16), r(xmm2), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x15).r().ib(), _64b | compat | avx), - inst("vpextrd", fmt("A", [w(rm32), r(xmm2), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x16).r().ib(), _64b | compat | avx), - inst("vpextrq", fmt("A", [w(rm64), r(xmm2), r(imm8)]), vex(L128)._66()._0f3a().w1().op(0x16).r().ib(), _64b | compat | avx), + inst("extractps", fmt("A", [w(rm32), r(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x17]).r().ib(), (_64b | compat) & sse41).alt(avx, "vextractps_b"), + inst("pextrb", fmt("A", [w(r32m8), r(xmm2), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x14]).r().ib(), (_64b | compat) & sse41).alt(avx, "vpextrb_a"), + inst("pextrw", fmt("A", [w(r32), r(xmm2), r(imm8)]), rex([0x66, 0x0F, 0xC5]).r().ib(), (_64b | compat) & sse2).alt(avx, "vpextrw_a"), + inst("pextrw", fmt("B", [w(r32m16), r(xmm2), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x15]).r().ib(), (_64b | compat) & sse41).alt(avx, "vpextrw_b"), + inst("pextrd", fmt("A", [w(rm32), r(xmm2), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x16]).r().ib(), (_64b | compat) & sse41).alt(avx, "vpextrd_a"), + inst("pextrq", fmt("A", [w(rm64), r(xmm2), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x16]).w().r().ib(), _64b & sse41).alt(avx, "vpextrq_a"), + inst("vextractps", fmt("B", [w(rm32), r(xmm1), r(imm8)]), vex(L128)._66()._0f3a().wig().op(0x17).r().ib(), (_64b | compat) & avx), + inst("vpextrb", fmt("A", [w(r32m8), r(xmm2), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x14).r().ib(), (_64b | compat) & avx), + inst("vpextrw", fmt("A", [w(r32), r(xmm2), r(imm8)]), vex(L128)._66()._0f().w0().op(0xC5).r().ib(), (_64b | compat) & avx), + inst("vpextrw", fmt("B", [w(r32m16), r(xmm2), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x15).r().ib(), (_64b | compat) & avx), + inst("vpextrd", fmt("A", [w(rm32), r(xmm2), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x16).r().ib(), (_64b | compat) & avx), + inst("vpextrq", fmt("A", [w(rm64), r(xmm2), r(imm8)]), vex(L128)._66()._0f3a().w1().op(0x16).r().ib(), (_64b | compat) & avx), // Insert into a single XMM lane. - inst("insertps", fmt("A", [rw(xmm1), r(xmm_m32), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x21]).r().ib(), _64b | compat | sse41).alt(avx, "vinsertps_b"), - inst("pinsrb", fmt("A", [rw(xmm1), r(r32m8), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x20]).r().ib(), _64b | compat | sse41), - inst("pinsrw", fmt("A", [rw(xmm1), r(r32m16), r(imm8)]), rex([0x66, 0x0F, 0xC4]).r().ib(), _64b | compat | sse2), - inst("pinsrd", fmt("A", [rw(xmm1), r(rm32), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x22]).r().ib(), _64b | compat | sse41), - inst("pinsrq", fmt("A", [rw(xmm1), r(rm64), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x22]).r().ib().w(), _64b | sse41), - inst("vinsertps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32), r(imm8)]), vex(L128)._66()._0f3a().wig().op(0x21).r().ib(), _64b | compat | avx), - inst("vpinsrb", fmt("B", [w(xmm1), r(xmm2), r(r32m8), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x20).r().ib(), _64b | compat | avx), - inst("vpinsrw", fmt("B", [w(xmm1), r(xmm2), r(r32m16), r(imm8)]), vex(L128)._66()._0f().w0().op(0xC4).r().ib(), _64b | compat | avx), - inst("vpinsrd", fmt("B", [w(xmm1), r(xmm2), r(rm32), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x22).r().ib(), _64b | compat | avx), - inst("vpinsrq", fmt("B", [w(xmm1), r(xmm2), r(rm64), r(imm8)]), vex(L128)._66()._0f3a().w1().op(0x22).r().ib(), _64b | avx), + inst("insertps", fmt("A", [rw(xmm1), r(xmm_m32), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x21]).r().ib(), (_64b | compat) & sse41).alt(avx, "vinsertps_b"), + inst("pinsrb", fmt("A", [rw(xmm1), r(r32m8), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x20]).r().ib(), (_64b | compat) & sse41), + inst("pinsrw", fmt("A", [rw(xmm1), r(r32m16), r(imm8)]), rex([0x66, 0x0F, 0xC4]).r().ib(), (_64b | compat) & sse2), + inst("pinsrd", fmt("A", [rw(xmm1), r(rm32), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x22]).r().ib(), (_64b | compat) & sse41), + inst("pinsrq", fmt("A", [rw(xmm1), r(rm64), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x22]).r().ib().w(), _64b & sse41), + inst("vinsertps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32), r(imm8)]), vex(L128)._66()._0f3a().wig().op(0x21).r().ib(), (_64b | compat) & avx), + inst("vpinsrb", fmt("B", [w(xmm1), r(xmm2), r(r32m8), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x20).r().ib(), (_64b | compat) & avx), + inst("vpinsrw", fmt("B", [w(xmm1), r(xmm2), r(r32m16), r(imm8)]), vex(L128)._66()._0f().w0().op(0xC4).r().ib(), (_64b | compat) & avx), + inst("vpinsrd", fmt("B", [w(xmm1), r(xmm2), r(rm32), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x22).r().ib(), (_64b | compat) & avx), + inst("vpinsrq", fmt("B", [w(xmm1), r(xmm2), r(rm64), r(imm8)]), vex(L128)._66()._0f3a().w1().op(0x22).r().ib(), _64b & avx), // Extract sign masks from the floating-point lanes. - inst("movmskps", fmt("RM", [w(r32), r(xmm2)]), rex([0x0F, 0x50]).r(), _64b | compat | sse).alt(avx, "vmovmskps_rm"), - inst("movmskpd", fmt("RM", [w(r32), r(xmm2)]), rex([0x66, 0x0F, 0x50]).r(), _64b | compat | sse2).alt(avx, "vmovmskpd_rm"), - inst("pmovmskb", fmt("RM", [w(r32), r(xmm2)]), rex([0x66, 0x0F, 0xD7]).r(), _64b | compat | sse2).alt(avx, "vpmovmskb_rm"), - inst("vmovmskps", fmt("RM", [w(r32), r(xmm2)]), vex(L128)._0f().op(0x50).r(), _64b | compat | avx), - inst("vmovmskpd", fmt("RM", [w(r32), r(xmm2)]), vex(L128)._66()._0f().op(0x50).r(), _64b | compat | avx), - inst("vpmovmskb", fmt("RM", [w(r32), r(xmm2)]), vex(L128)._66()._0f().op(0xD7).r(), _64b | compat | avx), + inst("movmskps", fmt("RM", [w(r32), r(xmm2)]), rex([0x0F, 0x50]).r(), (_64b | compat) & sse).alt(avx, "vmovmskps_rm"), + inst("movmskpd", fmt("RM", [w(r32), r(xmm2)]), rex([0x66, 0x0F, 0x50]).r(), (_64b | compat) & sse2).alt(avx, "vmovmskpd_rm"), + inst("pmovmskb", fmt("RM", [w(r32), r(xmm2)]), rex([0x66, 0x0F, 0xD7]).r(), (_64b | compat) & sse2).alt(avx, "vpmovmskb_rm"), + inst("vmovmskps", fmt("RM", [w(r32), r(xmm2)]), vex(L128)._0f().op(0x50).r(), (_64b | compat) & avx), + inst("vmovmskpd", fmt("RM", [w(r32), r(xmm2)]), vex(L128)._66()._0f().op(0x50).r(), (_64b | compat) & avx), + inst("vpmovmskb", fmt("RM", [w(r32), r(xmm2)]), vex(L128)._66()._0f().op(0xD7).r(), (_64b | compat) & avx), // Move two lower 32-bit floats to the high two lanes. - inst("movhps", fmt("A", [rw(xmm1), r(m64)]), rex([0x0F, 0x16]).r(), _64b | compat | sse).alt(avx, "vmovhps_b"), - inst("movlhps", fmt("RM", [rw(xmm1), r(xmm2)]), rex([0x0F, 0x16]).r(), _64b | compat | sse).alt(avx, "vmovlhps_rvm"), - inst("vmovhps", fmt("B", [w(xmm2), r(xmm1), r(m64)]), vex(L128)._0f().op(0x16).r(), _64b | compat | avx), - inst("vmovlhps", fmt("RVM", [w(xmm1), r(xmm2), r(xmm3)]), vex(L128)._0f().op(0x16).r(), _64b | compat | avx), + inst("movhps", fmt("A", [rw(xmm1), r(m64)]), rex([0x0F, 0x16]).r(), (_64b | compat) & sse).alt(avx, "vmovhps_b"), + inst("movlhps", fmt("RM", [rw(xmm1), r(xmm2)]), rex([0x0F, 0x16]).r(), (_64b | compat) & sse).alt(avx, "vmovlhps_rvm"), + inst("vmovhps", fmt("B", [w(xmm2), r(xmm1), r(m64)]), vex(L128)._0f().op(0x16).r(), (_64b | compat) & avx), + inst("vmovlhps", fmt("RVM", [w(xmm1), r(xmm2), r(xmm3)]), vex(L128)._0f().op(0x16).r(), (_64b | compat) & avx), // Duplicate the lower 64 bits of the source into 128 bits of the destination. - inst("movddup", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x12]).r(), _64b | compat | sse3).alt(avx, "vmovddup_a"), - inst("vmovddup", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._f2()._0f().op(0x12).r(), _64b | compat | avx), + inst("movddup", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x12]).r(), (_64b | compat) & sse3).alt(avx, "vmovddup_a"), + inst("vmovddup", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._f2()._0f().op(0x12).r(), (_64b | compat) & avx), // Blend lanes in various ways. - inst("pblendw", fmt("RMI", [rw(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x0E]).r().ib(), _64b | compat | sse41).alt(avx, "vpblendw_rvmi"), - inst("pblendvb", fmt("RM", [rw(xmm1), r(align(xmm_m128)), r(xmm0)]), rex([0x66, 0x0F, 0x38, 0x10]).r(), _64b | compat | sse41), - inst("blendvps", fmt("RM0", [rw(xmm1), r(align(xmm_m128)), r(xmm0)]), rex([0x66, 0x0F, 0x38, 0x14]).r(), _64b | compat | sse41), - inst("blendvpd", fmt("RM0", [rw(xmm1), r(align(xmm_m128)), r(xmm0)]), rex([0x66, 0x0F, 0x38, 0x15]).r(), _64b | compat | sse41), - inst("vpblendw", fmt("RVMI", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x0E).r().ib(), _64b | compat | avx), - inst("vpblendvb", fmt("RVMR", [w(xmm1), r(xmm2), r(xmm_m128), r(xmm3)]), vex(L128)._66()._0f3a().w0().op(0x4C).r().is4(), _64b | compat | avx), - inst("vblendvps", fmt("RVMR", [w(xmm1), r(xmm2), r(xmm_m128), r(xmm3)]), vex(L128)._66()._0f3a().w0().op(0x4A).r().is4(), _64b | compat | avx), - inst("vblendvpd", fmt("RVMR", [w(xmm1), r(xmm2), r(xmm_m128), r(xmm3)]), vex(L128)._66()._0f3a().w0().op(0x4B).r().is4(), _64b | compat | avx), + inst("pblendw", fmt("RMI", [rw(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x0E]).r().ib(), (_64b | compat) & sse41).alt(avx, "vpblendw_rvmi"), + inst("pblendvb", fmt("RM", [rw(xmm1), r(align(xmm_m128)), r(xmm0)]), rex([0x66, 0x0F, 0x38, 0x10]).r(), (_64b | compat) & sse41), + inst("blendvps", fmt("RM0", [rw(xmm1), r(align(xmm_m128)), r(xmm0)]), rex([0x66, 0x0F, 0x38, 0x14]).r(), (_64b | compat) & sse41), + inst("blendvpd", fmt("RM0", [rw(xmm1), r(align(xmm_m128)), r(xmm0)]), rex([0x66, 0x0F, 0x38, 0x15]).r(), (_64b | compat) & sse41), + inst("vpblendw", fmt("RVMI", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f3a().w0().op(0x0E).r().ib(), (_64b | compat) & avx), + inst("vpblendvb", fmt("RVMR", [w(xmm1), r(xmm2), r(xmm_m128), r(xmm3)]), vex(L128)._66()._0f3a().w0().op(0x4C).r().is4(), (_64b | compat) & avx), + inst("vblendvps", fmt("RVMR", [w(xmm1), r(xmm2), r(xmm_m128), r(xmm3)]), vex(L128)._66()._0f3a().w0().op(0x4A).r().is4(), (_64b | compat) & avx), + inst("vblendvpd", fmt("RVMR", [w(xmm1), r(xmm2), r(xmm_m128), r(xmm3)]), vex(L128)._66()._0f3a().w0().op(0x4B).r().is4(), (_64b | compat) & avx), // Shuffle lanes in various ways. - inst("shufpd", fmt("A", [rw(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0xC6]).ib(), _64b | compat | sse2).alt(avx, "vshufpd_b"), - inst("vshufpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f().ib().op(0xC6), _64b | compat | avx), - inst("shufps", fmt("A", [rw(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x0F, 0xC6]).ib(), _64b | compat | sse).alt(avx, "vshufps_b"), - inst("vshufps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._0f().ib().op(0xC6), _64b | compat | avx), - inst("pshufb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x00]), _64b | compat | ssse3).alt(avx, "vpshufb_b"), - inst("pshufd", fmt("A", [w(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0x70]).r().ib(), _64b | compat | sse2).alt(avx, "vpshufd_a"), - inst("pshuflw", fmt("A", [w(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0xF2, 0x0F, 0x70]).r().ib(), _64b | compat | sse2).alt(avx, "vpshuflw_a"), - inst("pshufhw", fmt("A", [w(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0xF3, 0x0F, 0x70]).r().ib(), _64b | compat | sse2).alt(avx, "vpshufhw_a"), - inst("vpshufb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x00), _64b | compat | avx), - inst("vpshufd", fmt("A", [w(xmm1), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f().op(0x70).r().ib(), _64b | compat | avx), - inst("vpshuflw", fmt("A", [w(xmm1), r(xmm_m128), r(imm8)]), vex(L128)._f2()._0f().op(0x70).r().ib(), _64b | compat | avx), - inst("vpshufhw", fmt("A", [w(xmm1), r(xmm_m128), r(imm8)]), vex(L128)._f3()._0f().op(0x70).r().ib(), _64b | compat | avx), + inst("shufpd", fmt("A", [rw(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0xC6]).ib(), (_64b | compat) & sse2).alt(avx, "vshufpd_b"), + inst("vshufpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f().ib().op(0xC6), (_64b | compat) & avx), + inst("shufps", fmt("A", [rw(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x0F, 0xC6]).ib(), (_64b | compat) & sse).alt(avx, "vshufps_b"), + inst("vshufps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128), r(imm8)]), vex(L128)._0f().ib().op(0xC6), (_64b | compat) & avx), + inst("pshufb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x00]), (_64b | compat) & ssse3).alt(avx, "vpshufb_b"), + inst("pshufd", fmt("A", [w(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0x70]).r().ib(), (_64b | compat) & sse2).alt(avx, "vpshufd_a"), + inst("pshuflw", fmt("A", [w(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0xF2, 0x0F, 0x70]).r().ib(), (_64b | compat) & sse2).alt(avx, "vpshuflw_a"), + inst("pshufhw", fmt("A", [w(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0xF3, 0x0F, 0x70]).r().ib(), (_64b | compat) & sse2).alt(avx, "vpshufhw_a"), + inst("vpshufb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x00), (_64b | compat) & avx), + inst("vpshufd", fmt("A", [w(xmm1), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f().op(0x70).r().ib(), (_64b | compat) & avx), + inst("vpshuflw", fmt("A", [w(xmm1), r(xmm_m128), r(imm8)]), vex(L128)._f2()._0f().op(0x70).r().ib(), (_64b | compat) & avx), + inst("vpshufhw", fmt("A", [w(xmm1), r(xmm_m128), r(imm8)]), vex(L128)._f3()._0f().op(0x70).r().ib(), (_64b | compat) & avx), // Broadcast a single lane to all lanes of the destination. - inst("vbroadcastss", fmt("A_M", [w(xmm1), r(m32)]), vex(L128)._66()._0f38().w0().op(0x18).r(), _64b | compat | avx), - inst("vbroadcastss", fmt("A_R", [w(xmm1), r(xmm2)]), vex(L128)._66()._0f38().w0().op(0x18).r(), _64b | compat | avx2), - inst("vpbroadcastb", fmt("A", [w(xmm1), r(xmm_m8)]), vex(L128)._66()._0f38().w0().op(0x78).r(), _64b | compat | avx2), - inst("vpbroadcastw", fmt("A", [w(xmm1), r(xmm_m16)]), vex(L128)._66()._0f38().w0().op(0x79).r(), _64b | compat | avx2), - inst("vpbroadcastd", fmt("A", [w(xmm1), r(xmm_m32)]), vex(L128)._66()._0f38().w0().op(0x58).r(), _64b | compat | avx2), - inst("vpbroadcastq", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().w0().op(0x59).r(), _64b | compat | avx2), + inst("vbroadcastss", fmt("A_M", [w(xmm1), r(m32)]), vex(L128)._66()._0f38().w0().op(0x18).r(), (_64b | compat) & avx), + inst("vbroadcastss", fmt("A_R", [w(xmm1), r(xmm2)]), vex(L128)._66()._0f38().w0().op(0x18).r(), (_64b | compat) & avx2), + inst("vpbroadcastb", fmt("A", [w(xmm1), r(xmm_m8)]), vex(L128)._66()._0f38().w0().op(0x78).r(), (_64b | compat) & avx2), + inst("vpbroadcastw", fmt("A", [w(xmm1), r(xmm_m16)]), vex(L128)._66()._0f38().w0().op(0x79).r(), (_64b | compat) & avx2), + inst("vpbroadcastd", fmt("A", [w(xmm1), r(xmm_m32)]), vex(L128)._66()._0f38().w0().op(0x58).r(), (_64b | compat) & avx2), + inst("vpbroadcastq", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().w0().op(0x59).r(), (_64b | compat) & avx2), // AVX-512 permutations - inst("vpermi2b", fmt("A", [rw(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().w0().op(0x75).r(), _64b | compat | avx512vl | avx512vbmi), + inst("vpermi2b", fmt("A", [rw(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, FullMem)._66()._0f38().w0().op(0x75).r(), (_64b | compat) & avx512vl & avx512vbmi), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/max.rs b/cranelift/assembler-x64/meta/src/instructions/max.rs index b653beb6c5..26dd9bc7c3 100644 --- a/cranelift/assembler-x64/meta/src/instructions/max.rs +++ b/cranelift/assembler-x64/meta/src/instructions/max.rs @@ -16,26 +16,26 @@ pub fn list() -> Vec { // first or second operand) be returned, the action of MAXPS can be // emulated using a sequence of instructions, such as, a comparison // followed by AND, ANDN, and OR." - inst("maxss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x5F]).r(), _64b | compat | sse).alt(avx, "vmaxss_b"), - inst("maxsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x5F]).r(), _64b | compat | sse2).alt(avx, "vmaxsd_b"), - inst("maxps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x5F]).r(), _64b | compat | sse).alt(avx, "vmaxps_b"), - inst("maxpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x5F]).r(), _64b | compat | sse2).alt(avx, "vmaxpd_b"), - inst("vmaxss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x5F).r(), _64b | compat | avx), - inst("vmaxsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x5F).r(), _64b | compat | avx), - inst("vmaxps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x5F).r(), _64b | compat | avx), - inst("vmaxpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x5F).r(), _64b | compat | avx), + inst("maxss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x5F]).r(), (_64b | compat) & sse).alt(avx, "vmaxss_b"), + inst("maxsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x5F]).r(), (_64b | compat) & sse2).alt(avx, "vmaxsd_b"), + inst("maxps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x5F]).r(), (_64b | compat) & sse).alt(avx, "vmaxps_b"), + inst("maxpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x5F]).r(), (_64b | compat) & sse2).alt(avx, "vmaxpd_b"), + inst("vmaxss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x5F).r(), (_64b | compat) & avx), + inst("vmaxsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x5F).r(), (_64b | compat) & avx), + inst("vmaxps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x5F).r(), (_64b | compat) & avx), + inst("vmaxpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x5F).r(), (_64b | compat) & avx), // Packed integer maximum. - inst("pmaxsb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3C]).r(), _64b | compat | sse41).alt(avx, "vpmaxsb_b"), - inst("pmaxsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xEE]).r(), _64b | compat | sse2).alt(avx, "vpmaxsw_b"), - inst("pmaxsd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3D]).r(), _64b | compat | sse41).alt(avx, "vpmaxsd_b"), - inst("pmaxub", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDE]).r(), _64b | compat | sse2).alt(avx, "vpmaxub_b"), - inst("pmaxuw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3E]).r(), _64b | compat | sse41).alt(avx, "vpmaxuw_b"), - inst("pmaxud", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3F]).r(), _64b | compat | sse41).alt(avx, "vpmaxud_b"), - inst("vpmaxsb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3C).r(), _64b | compat | avx), - inst("vpmaxsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xEE).r(), _64b | compat | avx), - inst("vpmaxsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3D).r(), _64b | compat | avx), - inst("vpmaxub", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDE).r(), _64b | compat | avx), - inst("vpmaxuw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3E).r(), _64b | compat | avx), - inst("vpmaxud", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3F).r(), _64b | compat | avx), + inst("pmaxsb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3C]).r(), (_64b | compat) & sse41).alt(avx, "vpmaxsb_b"), + inst("pmaxsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xEE]).r(), (_64b | compat) & sse2).alt(avx, "vpmaxsw_b"), + inst("pmaxsd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3D]).r(), (_64b | compat) & sse41).alt(avx, "vpmaxsd_b"), + inst("pmaxub", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDE]).r(), (_64b | compat) & sse2).alt(avx, "vpmaxub_b"), + inst("pmaxuw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3E]).r(), (_64b | compat) & sse41).alt(avx, "vpmaxuw_b"), + inst("pmaxud", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3F]).r(), (_64b | compat) & sse41).alt(avx, "vpmaxud_b"), + inst("vpmaxsb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3C).r(), (_64b | compat) & avx), + inst("vpmaxsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xEE).r(), (_64b | compat) & avx), + inst("vpmaxsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3D).r(), (_64b | compat) & avx), + inst("vpmaxub", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDE).r(), (_64b | compat) & avx), + inst("vpmaxuw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3E).r(), (_64b | compat) & avx), + inst("vpmaxud", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3F).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/min.rs b/cranelift/assembler-x64/meta/src/instructions/min.rs index eec3384d61..5fcd9440b9 100644 --- a/cranelift/assembler-x64/meta/src/instructions/min.rs +++ b/cranelift/assembler-x64/meta/src/instructions/min.rs @@ -6,26 +6,26 @@ pub fn list() -> Vec { vec![ // Floating-point minimum. Note, this has some tricky NaN and sign bit // behavior; see `max.rs`. - inst("minss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x5D]).r(), _64b | compat | sse).alt(avx, "vminss_b"), - inst("minsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x5D]).r(), _64b | compat | sse2).alt(avx, "vminsd_b"), - inst("minps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x5D]).r(), _64b | compat | sse).alt(avx, "vminps_b"), - inst("minpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x5D]).r(), _64b | compat | sse2).alt(avx, "vminpd_b"), - inst("vminss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x5D).r(), _64b | compat | avx), - inst("vminsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x5D).r(), _64b | compat | avx), - inst("vminps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x5D).r(), _64b | compat | avx), - inst("vminpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x5D).r(), _64b | compat | avx), + inst("minss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x5D]).r(), (_64b | compat) & sse).alt(avx, "vminss_b"), + inst("minsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x5D]).r(), (_64b | compat) & sse2).alt(avx, "vminsd_b"), + inst("minps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x5D]).r(), (_64b | compat) & sse).alt(avx, "vminps_b"), + inst("minpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x5D]).r(), (_64b | compat) & sse2).alt(avx, "vminpd_b"), + inst("vminss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x5D).r(), (_64b | compat) & avx), + inst("vminsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x5D).r(), (_64b | compat) & avx), + inst("vminps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x5D).r(), (_64b | compat) & avx), + inst("vminpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x5D).r(), (_64b | compat) & avx), // Packed integer minimum. - inst("pminsb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x38]).r(), _64b | compat | sse41).alt(avx, "vpminsb_b"), - inst("pminsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xEA]).r(), _64b | compat | sse2).alt(avx, "vpminsw_b"), - inst("pminsd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x39]).r(), _64b | compat | sse41).alt(avx, "vpminsd_b"), - inst("pminub", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDA]).r(), _64b | compat | sse2).alt(avx, "vpminub_b"), - inst("pminuw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3A]).r(), _64b | compat | sse41).alt(avx, "vpminuw_b"), - inst("pminud", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3B]).r(), _64b | compat | sse41).alt(avx, "vpminud_b"), - inst("vpminsb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x38).r(), _64b | compat | avx), - inst("vpminsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xEA).r(), _64b | compat | avx), - inst("vpminsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x39).r(), _64b | compat | avx), - inst("vpminub", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDA).r(), _64b | compat | avx), - inst("vpminuw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3A).r(), _64b | compat | avx), - inst("vpminud", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3B).r(), _64b | compat | avx), + inst("pminsb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x38]).r(), (_64b | compat) & sse41).alt(avx, "vpminsb_b"), + inst("pminsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xEA]).r(), (_64b | compat) & sse2).alt(avx, "vpminsw_b"), + inst("pminsd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x39]).r(), (_64b | compat) & sse41).alt(avx, "vpminsd_b"), + inst("pminub", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xDA]).r(), (_64b | compat) & sse2).alt(avx, "vpminub_b"), + inst("pminuw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3A]).r(), (_64b | compat) & sse41).alt(avx, "vpminuw_b"), + inst("pminud", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x3B]).r(), (_64b | compat) & sse41).alt(avx, "vpminud_b"), + inst("vpminsb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x38).r(), (_64b | compat) & avx), + inst("vpminsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xEA).r(), (_64b | compat) & avx), + inst("vpminsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x39).r(), (_64b | compat) & avx), + inst("vpminub", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xDA).r(), (_64b | compat) & avx), + inst("vpminuw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3A).r(), (_64b | compat) & avx), + inst("vpminud", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x3B).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/misc.rs b/cranelift/assembler-x64/meta/src/instructions/misc.rs index 8037b8aa93..18c8f6db3e 100644 --- a/cranelift/assembler-x64/meta/src/instructions/misc.rs +++ b/cranelift/assembler-x64/meta/src/instructions/misc.rs @@ -4,9 +4,9 @@ use crate::dsl::{fmt, inst, r, rex, sxl, w}; #[rustfmt::skip] // Keeps instructions on a single line. pub fn list() -> Vec { vec![ - inst("mfence", fmt("ZO", []), rex([0x0f, 0xae, 0xf0]), _64b | compat | sse2), + inst("mfence", fmt("ZO", []), rex([0x0f, 0xae, 0xf0]), (_64b | compat) & sse2), inst("sfence", fmt("ZO", []), rex([0x0f, 0xae, 0xf8]), _64b | compat), - inst("lfence", fmt("ZO", []), rex([0x0f, 0xae, 0xe8]), _64b | compat | sse2), + inst("lfence", fmt("ZO", []), rex([0x0f, 0xae, 0xe8]), (_64b | compat) & sse2), inst("hlt", fmt("ZO", []), rex([0xf4]), _64b | compat), inst("ud2", fmt("ZO", []), rex([0x0f, 0x0b]), _64b | compat).has_trap(), diff --git a/cranelift/assembler-x64/meta/src/instructions/mov.rs b/cranelift/assembler-x64/meta/src/instructions/mov.rs index 7753efada9..79746be00b 100644 --- a/cranelift/assembler-x64/meta/src/instructions/mov.rs +++ b/cranelift/assembler-x64/meta/src/instructions/mov.rs @@ -52,14 +52,14 @@ pub fn list() -> Vec { // manual: "when the destination operand is an XMM register, the source // operand is written to the low doubleword of the register, and the // register is zero-extended to 128 bits." - inst("movd", fmt("A", [w(xmm1), r(rm32)]), rex([0x66, 0x0F, 0x6E]).r(), _64b | compat | sse2), - inst("movq", fmt("A", [w(xmm1), r(rm64)]), rex([0x66, 0x0F, 0x6E]).r().w(), _64b | sse2), - inst("movd", fmt("B", [w(rm32), r(xmm2)]), rex([0x66, 0x0F, 0x7E]).r(), _64b | compat | sse2), - inst("movq", fmt("B", [w(rm64), r(xmm2)]), rex([0x66, 0x0F, 0x7E]).r().w(), _64b | sse2), - inst("vmovd", fmt("A", [w(xmm1), r(rm32)]), vex(L128)._66()._0f().w0().op(0x6E).r(), _64b | compat | avx), - inst("vmovq", fmt("A", [w(xmm1), r(rm64)]), vex(L128)._66()._0f().w1().op(0x6E).r(), _64b | avx), - inst("vmovd", fmt("B", [w(rm32), r(xmm2)]), vex(L128)._66()._0f().w0().op(0x7E).r(), _64b | compat | avx), - inst("vmovq", fmt("B", [w(rm64), r(xmm2)]), vex(L128)._66()._0f().w1().op(0x7E).r(), _64b | avx), + inst("movd", fmt("A", [w(xmm1), r(rm32)]), rex([0x66, 0x0F, 0x6E]).r(), (_64b | compat) & sse2), + inst("movq", fmt("A", [w(xmm1), r(rm64)]), rex([0x66, 0x0F, 0x6E]).r().w(), _64b & sse2), + inst("movd", fmt("B", [w(rm32), r(xmm2)]), rex([0x66, 0x0F, 0x7E]).r(), (_64b | compat) & sse2), + inst("movq", fmt("B", [w(rm64), r(xmm2)]), rex([0x66, 0x0F, 0x7E]).r().w(), _64b & sse2), + inst("vmovd", fmt("A", [w(xmm1), r(rm32)]), vex(L128)._66()._0f().w0().op(0x6E).r(), (_64b | compat) & avx), + inst("vmovq", fmt("A", [w(xmm1), r(rm64)]), vex(L128)._66()._0f().w1().op(0x6E).r(), _64b & avx), + inst("vmovd", fmt("B", [w(rm32), r(xmm2)]), vex(L128)._66()._0f().w0().op(0x7E).r(), (_64b | compat) & avx), + inst("vmovq", fmt("B", [w(rm64), r(xmm2)]), vex(L128)._66()._0f().w1().op(0x7E).r(), _64b & avx), // Move floating-point values to and from XMM locations. Some // memory-loading versions of `movs*` clear the upper bits of the XMM @@ -119,31 +119,31 @@ pub fn list() -> Vec { inst("vmovdqu", fmt("B", [w(xmm_m128), r(xmm1)]), vex(L128)._f3()._0f().op(0x7F).r(), compat | _64b | avx), // Move and extend packed integers to and from XMM locations with sign extension. - inst("pmovsxbw", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x20]).r(), _64b | compat | sse41).alt(avx, "vpmovsxbw_a"), - inst("pmovsxbd", fmt("A", [w(xmm1), r(xmm_m32)]), rex([0x66, 0x0F, 0x38, 0x21]).r(), _64b | compat | sse41).alt(avx, "vpmovsxbd_a"), - inst("pmovsxbq", fmt("A", [w(xmm1), r(xmm_m16)]), rex([0x66, 0x0F, 0x38, 0x22]).r(), _64b | compat | sse41).alt(avx, "vpmovsxbq_a"), - inst("pmovsxwd", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x23]).r(), _64b | compat | sse41).alt(avx, "vpmovsxwd_a"), - inst("pmovsxwq", fmt("A", [w(xmm1), r(xmm_m32)]), rex([0x66, 0x0F, 0x38, 0x24]).r(), _64b | compat | sse41).alt(avx, "vpmovsxwq_a"), - inst("pmovsxdq", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x25]).r(), _64b | compat | sse41).alt(avx, "vpmovsxdq_a"), - inst("vpmovsxbw", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x20).r(), _64b | compat | avx), - inst("vpmovsxbd", fmt("A", [w(xmm1), r(xmm_m32)]), vex(L128)._66()._0f38().op(0x21).r(), _64b | compat | avx), - inst("vpmovsxbq", fmt("A", [w(xmm1), r(xmm_m16)]), vex(L128)._66()._0f38().op(0x22).r(), _64b | compat | avx), - inst("vpmovsxwd", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x23).r(), _64b | compat | avx), - inst("vpmovsxwq", fmt("A", [w(xmm1), r(xmm_m32)]), vex(L128)._66()._0f38().op(0x24).r(), _64b | compat | avx), - inst("vpmovsxdq", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x25).r(), _64b | compat | avx), + inst("pmovsxbw", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x20]).r(), (_64b | compat) & sse41).alt(avx, "vpmovsxbw_a"), + inst("pmovsxbd", fmt("A", [w(xmm1), r(xmm_m32)]), rex([0x66, 0x0F, 0x38, 0x21]).r(), (_64b | compat) & sse41).alt(avx, "vpmovsxbd_a"), + inst("pmovsxbq", fmt("A", [w(xmm1), r(xmm_m16)]), rex([0x66, 0x0F, 0x38, 0x22]).r(), (_64b | compat) & sse41).alt(avx, "vpmovsxbq_a"), + inst("pmovsxwd", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x23]).r(), (_64b | compat) & sse41).alt(avx, "vpmovsxwd_a"), + inst("pmovsxwq", fmt("A", [w(xmm1), r(xmm_m32)]), rex([0x66, 0x0F, 0x38, 0x24]).r(), (_64b | compat) & sse41).alt(avx, "vpmovsxwq_a"), + inst("pmovsxdq", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x25]).r(), (_64b | compat) & sse41).alt(avx, "vpmovsxdq_a"), + inst("vpmovsxbw", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x20).r(), (_64b | compat) & avx), + inst("vpmovsxbd", fmt("A", [w(xmm1), r(xmm_m32)]), vex(L128)._66()._0f38().op(0x21).r(), (_64b | compat) & avx), + inst("vpmovsxbq", fmt("A", [w(xmm1), r(xmm_m16)]), vex(L128)._66()._0f38().op(0x22).r(), (_64b | compat) & avx), + inst("vpmovsxwd", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x23).r(), (_64b | compat) & avx), + inst("vpmovsxwq", fmt("A", [w(xmm1), r(xmm_m32)]), vex(L128)._66()._0f38().op(0x24).r(), (_64b | compat) & avx), + inst("vpmovsxdq", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x25).r(), (_64b | compat) & avx), // Move and extend packed integers to and from XMM locations with zero extension. - inst("pmovzxbw", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x30]).r(), _64b | compat | sse41).alt(avx, "vpmovzxbw_a"), - inst("pmovzxbd", fmt("A", [w(xmm1), r(xmm_m32)]), rex([0x66, 0x0F, 0x38, 0x31]).r(), _64b | compat | sse41).alt(avx, "vpmovzxbd_a"), - inst("pmovzxbq", fmt("A", [w(xmm1), r(xmm_m16)]), rex([0x66, 0x0F, 0x38, 0x32]).r(), _64b | compat | sse41).alt(avx, "vpmovzxbq_a"), - inst("pmovzxwd", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x33]).r(), _64b | compat | sse41).alt(avx, "vpmovzxwd_a"), - inst("pmovzxwq", fmt("A", [w(xmm1), r(xmm_m32)]), rex([0x66, 0x0F, 0x38, 0x34]).r(), _64b | compat | sse41).alt(avx, "vpmovzxwq_a"), - inst("pmovzxdq", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x35]).r(), _64b | compat | sse41).alt(avx, "vpmovzxdq_a"), - inst("vpmovzxbw", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x30).r(), _64b | compat | avx), - inst("vpmovzxbd", fmt("A", [w(xmm1), r(xmm_m32)]), vex(L128)._66()._0f38().op(0x31).r(), _64b | compat | avx), - inst("vpmovzxbq", fmt("A", [w(xmm1), r(xmm_m16)]), vex(L128)._66()._0f38().op(0x32).r(), _64b | compat | avx), - inst("vpmovzxwd", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x33).r(), _64b | compat | avx), - inst("vpmovzxwq", fmt("A", [w(xmm1), r(xmm_m32)]), vex(L128)._66()._0f38().op(0x34).r(), _64b | compat | avx), - inst("vpmovzxdq", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x35).r(), _64b | compat | avx), + inst("pmovzxbw", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x30]).r(), (_64b | compat) & sse41).alt(avx, "vpmovzxbw_a"), + inst("pmovzxbd", fmt("A", [w(xmm1), r(xmm_m32)]), rex([0x66, 0x0F, 0x38, 0x31]).r(), (_64b | compat) & sse41).alt(avx, "vpmovzxbd_a"), + inst("pmovzxbq", fmt("A", [w(xmm1), r(xmm_m16)]), rex([0x66, 0x0F, 0x38, 0x32]).r(), (_64b | compat) & sse41).alt(avx, "vpmovzxbq_a"), + inst("pmovzxwd", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x33]).r(), (_64b | compat) & sse41).alt(avx, "vpmovzxwd_a"), + inst("pmovzxwq", fmt("A", [w(xmm1), r(xmm_m32)]), rex([0x66, 0x0F, 0x38, 0x34]).r(), (_64b | compat) & sse41).alt(avx, "vpmovzxwq_a"), + inst("pmovzxdq", fmt("A", [w(xmm1), r(xmm_m64)]), rex([0x66, 0x0F, 0x38, 0x35]).r(), (_64b | compat) & sse41).alt(avx, "vpmovzxdq_a"), + inst("vpmovzxbw", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x30).r(), (_64b | compat) & avx), + inst("vpmovzxbd", fmt("A", [w(xmm1), r(xmm_m32)]), vex(L128)._66()._0f38().op(0x31).r(), (_64b | compat) & avx), + inst("vpmovzxbq", fmt("A", [w(xmm1), r(xmm_m16)]), vex(L128)._66()._0f38().op(0x32).r(), (_64b | compat) & avx), + inst("vpmovzxwd", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x33).r(), (_64b | compat) & avx), + inst("vpmovzxwq", fmt("A", [w(xmm1), r(xmm_m32)]), vex(L128)._66()._0f38().op(0x34).r(), (_64b | compat) & avx), + inst("vpmovzxdq", fmt("A", [w(xmm1), r(xmm_m64)]), vex(L128)._66()._0f38().op(0x35).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/mul.rs b/cranelift/assembler-x64/meta/src/instructions/mul.rs index 9f82eb3d06..d25f87c8ad 100644 --- a/cranelift/assembler-x64/meta/src/instructions/mul.rs +++ b/cranelift/assembler-x64/meta/src/instructions/mul.rs @@ -30,33 +30,33 @@ pub fn list() -> Vec { // instruction only calculates the high half of the multiplication, as // opposed to when the operands are different to calculate both the high // and low halves. - inst("mulxl", fmt("RVM", [w(r32a), w(r32b), r(rm32), r(implicit(edx))]), vex(LZ)._f2()._0f38().w0().op(0xF6), _64b | compat | bmi2).custom(Visit), - inst("mulxq", fmt("RVM", [w(r64a), w(r64b), r(rm64), r(implicit(rdx))]), vex(LZ)._f2()._0f38().w1().op(0xF6), _64b | bmi2).custom(Visit), + inst("mulxl", fmt("RVM", [w(r32a), w(r32b), r(rm32), r(implicit(edx))]), vex(LZ)._f2()._0f38().w0().op(0xF6), (_64b | compat) & bmi2).custom(Visit), + inst("mulxq", fmt("RVM", [w(r64a), w(r64b), r(rm64), r(implicit(rdx))]), vex(LZ)._f2()._0f38().w1().op(0xF6), _64b & bmi2).custom(Visit), // Vector instructions. - inst("mulss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x59]).r(), _64b | compat | sse).alt(avx, "vmulss_b"), - inst("mulsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x59]).r(), _64b | compat | sse2).alt(avx, "vmulsd_b"), - inst("mulps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x59]).r(), _64b | compat | sse).alt(avx, "vmulps_b"), - inst("mulpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x59]).r(), _64b | compat | sse2).alt(avx, "vmulpd_b"), - inst("pmuldq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x28]).r(), _64b | compat | sse41).alt(avx, "vpmuldq_b"), - inst("pmulhrsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x0B]).r(), _64b | compat | ssse3).alt(avx, "vpmulhrsw_b"), - inst("pmulhuw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE4]).r(), _64b | compat | sse2).alt(avx, "vpmulhuw_b"), - inst("pmulhw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE5]).r(), _64b | compat | sse2).alt(avx, "vpmulhw_b"), - inst("pmulld", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x40]).r(), _64b | compat | sse41).alt(avx, "vpmulld_b"), - inst("pmullw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD5]).r(), _64b | compat | sse2).alt(avx, "vpmullw_b"), - inst("pmuludq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF4]).r(), _64b | compat | sse2).alt(avx, "vpmuludq_b"), - inst("vmulss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x59), _64b | compat | avx), - inst("vmulsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x59), _64b | compat | avx), - inst("vmulps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x59), _64b | compat | avx), - inst("vmulpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x59), _64b | compat | avx), - inst("vpmuldq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x28), _64b | compat | avx), - inst("vpmulhrsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x0B), _64b | compat | avx), - inst("vpmulhuw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE4), _64b | compat | avx), - inst("vpmulhw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE5), _64b | compat | avx), - inst("vpmulld", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x40), _64b | compat | avx), - inst("vpmullw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD5), _64b | compat | avx), - inst("vpmuludq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF4), _64b | compat | avx), - - inst("vpmulld", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w0().op(0x40).r(), _64b | compat | avx512vl | avx512f), - inst("vpmullq", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w1().op(0x40).r(), _64b | compat | avx512vl | avx512dq), + inst("mulss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x59]).r(), (_64b | compat) & sse).alt(avx, "vmulss_b"), + inst("mulsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x59]).r(), (_64b | compat) & sse2).alt(avx, "vmulsd_b"), + inst("mulps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x59]).r(), (_64b | compat) & sse).alt(avx, "vmulps_b"), + inst("mulpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x59]).r(), (_64b | compat) & sse2).alt(avx, "vmulpd_b"), + inst("pmuldq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x28]).r(), (_64b | compat) & sse41).alt(avx, "vpmuldq_b"), + inst("pmulhrsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x0B]).r(), (_64b | compat) & ssse3).alt(avx, "vpmulhrsw_b"), + inst("pmulhuw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE4]).r(), (_64b | compat) & sse2).alt(avx, "vpmulhuw_b"), + inst("pmulhw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE5]).r(), (_64b | compat) & sse2).alt(avx, "vpmulhw_b"), + inst("pmulld", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x40]).r(), (_64b | compat) & sse41).alt(avx, "vpmulld_b"), + inst("pmullw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD5]).r(), (_64b | compat) & sse2).alt(avx, "vpmullw_b"), + inst("pmuludq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF4]).r(), (_64b | compat) & sse2).alt(avx, "vpmuludq_b"), + inst("vmulss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x59), (_64b | compat) & avx), + inst("vmulsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x59), (_64b | compat) & avx), + inst("vmulps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x59), (_64b | compat) & avx), + inst("vmulpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x59), (_64b | compat) & avx), + inst("vpmuldq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x28), (_64b | compat) & avx), + inst("vpmulhrsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x0B), (_64b | compat) & avx), + inst("vpmulhuw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE4), (_64b | compat) & avx), + inst("vpmulhw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE5), (_64b | compat) & avx), + inst("vpmulld", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x40), (_64b | compat) & avx), + inst("vpmullw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD5), (_64b | compat) & avx), + inst("vpmuludq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF4), (_64b | compat) & avx), + // AVX-512 instructions. + inst("vpmulld", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w0().op(0x40).r(), (_64b | compat) & avx512vl & avx512f), + inst("vpmullq", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Full)._66()._0f38().w1().op(0x40).r(), (_64b | compat) & avx512vl & avx512dq), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/or.rs b/cranelift/assembler-x64/meta/src/instructions/or.rs index b3aedc406e..a99ba36a98 100644 --- a/cranelift/assembler-x64/meta/src/instructions/or.rs +++ b/cranelift/assembler-x64/meta/src/instructions/or.rs @@ -34,11 +34,11 @@ pub fn list() -> Vec { inst("lock_orl", fmt("MR", [rw(m32), r(r32)]), rex([0xf0, 0x09]).r(), _64b | compat).custom(Mnemonic), inst("lock_orq", fmt("MR", [rw(m64), r(r64)]), rex([0xf0, 0x09]).w().r(), _64b).custom(Mnemonic), // Vector instructions. - inst("orps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x56]).r(), _64b | compat | sse).alt(avx, "vorps_b"), - inst("orpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x56]).r(), _64b | compat | sse2).alt(avx, "vorpd_b"), - inst("por", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xEB]).r(), _64b | compat | sse2).alt(avx, "vpor_b"), - inst("vorps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x56).r(), _64b | compat | avx), - inst("vorpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x56).r(), _64b | compat | avx), - inst("vpor", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xEB).r(), _64b | compat | avx), + inst("orps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x56]).r(), (_64b | compat) & sse).alt(avx, "vorps_b"), + inst("orpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x56]).r(), (_64b | compat) & sse2).alt(avx, "vorpd_b"), + inst("por", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xEB]).r(), (_64b | compat) & sse2).alt(avx, "vpor_b"), + inst("vorps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x56).r(), (_64b | compat) & avx), + inst("vorpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x56).r(), (_64b | compat) & avx), + inst("vpor", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xEB).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/pack.rs b/cranelift/assembler-x64/meta/src/instructions/pack.rs index 7f4451c1bf..0b9223180a 100644 --- a/cranelift/assembler-x64/meta/src/instructions/pack.rs +++ b/cranelift/assembler-x64/meta/src/instructions/pack.rs @@ -7,16 +7,16 @@ pub fn list() -> Vec { // Convert packed signed integers into smaller signed integers using // saturation to handle overflow (e.g., `0x7F` or `0x80` when converting // from word to byte). - inst("packsswb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x63]), _64b | compat | sse2).alt(avx, "vpacksswb_b"), - inst("packssdw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x6B]), _64b | compat | sse2).alt(avx, "vpackssdw_b"), - inst("vpacksswb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x63), _64b | compat | avx), - inst("vpackssdw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x6B), _64b | compat | avx), + inst("packsswb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x63]), (_64b | compat) & sse2).alt(avx, "vpacksswb_b"), + inst("packssdw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x6B]), (_64b | compat) & sse2).alt(avx, "vpackssdw_b"), + inst("vpacksswb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x63), (_64b | compat) & avx), + inst("vpackssdw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x6B), (_64b | compat) & avx), // Convert packed signed integers into smaller unsigned integers using // unsigned saturation to handle overflow (e.g., `0xFF` or `0x00` when // converting from word to byte). - inst("packuswb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x67]), _64b | compat | sse2).alt(avx, "vpackuswb_b"), - inst("packusdw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x2B]), _64b | compat | sse41).alt(avx, "vpackusdw_b"), - inst("vpackuswb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x67), _64b | compat | avx), - inst("vpackusdw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x2B), _64b | compat | avx), + inst("packuswb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x67]), (_64b | compat) & sse2).alt(avx, "vpackuswb_b"), + inst("packusdw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x2B]), (_64b | compat) & sse41).alt(avx, "vpackusdw_b"), + inst("vpackuswb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x67), (_64b | compat) & avx), + inst("vpackusdw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x2B), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/pma.rs b/cranelift/assembler-x64/meta/src/instructions/pma.rs index b3c89875ca..1bd25d0ccf 100644 --- a/cranelift/assembler-x64/meta/src/instructions/pma.rs +++ b/cranelift/assembler-x64/meta/src/instructions/pma.rs @@ -15,14 +15,14 @@ pub fn list() -> Vec { // together and stored in the low doubleword of the destination register // (31-0). The same operation is performed on the other pairs of // adjacent words." - inst("pmaddwd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF5]), _64b | compat | sse2).alt(avx, "vpmaddwd_b"), - inst("vpmaddwd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF5), _64b | compat | avx), + inst("pmaddwd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF5]), (_64b | compat) & sse2).alt(avx, "vpmaddwd_b"), + inst("vpmaddwd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF5), (_64b | compat) & avx), // Like `pmaddwd`, but this "multiplies vertically each unsigned byte of // the destination operand (first operand) with the corresponding signed // byte of the source operand (second operand), producing intermediate // signed 16-bit integers. Each adjacent pair of signed words is added // and the saturated result is packed to the destination operand." - inst("pmaddubsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x04]), _64b | compat | ssse3).alt(avx, "vpmaddubsw_b"), - inst("vpmaddubsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x04), _64b | compat | avx), + inst("pmaddubsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x38, 0x04]), (_64b | compat) & ssse3).alt(avx, "vpmaddubsw_b"), + inst("vpmaddubsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f38().op(0x04), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/recip.rs b/cranelift/assembler-x64/meta/src/instructions/recip.rs index 2bee2c685a..97e56e09e1 100644 --- a/cranelift/assembler-x64/meta/src/instructions/recip.rs +++ b/cranelift/assembler-x64/meta/src/instructions/recip.rs @@ -4,14 +4,14 @@ use crate::dsl::{align, fmt, inst, r, rex, vex, w}; #[rustfmt::skip] // Keeps instructions on a single line. pub fn list() -> Vec { vec![ - inst("rcpps", fmt("RM", [w(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x53]).r(), _64b | compat | sse).alt(avx, "vrcpps_rm"), - inst("rcpss", fmt("RM", [w(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x53]).r(), _64b | compat | sse), - inst("rsqrtps", fmt("RM", [w(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x52]).r(), _64b | compat | sse).alt(avx, "vrsqrtps_rm"), - inst("rsqrtss", fmt("RM", [w(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x52]).r(), _64b | compat | sse), + inst("rcpps", fmt("RM", [w(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x53]).r(), (_64b | compat) & sse).alt(avx, "vrcpps_rm"), + inst("rcpss", fmt("RM", [w(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x53]).r(), (_64b | compat) & sse), + inst("rsqrtps", fmt("RM", [w(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x52]).r(), (_64b | compat) & sse).alt(avx, "vrsqrtps_rm"), + inst("rsqrtss", fmt("RM", [w(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x52]).r(), (_64b | compat) & sse), - inst("vrcpps", fmt("RM", [w(xmm1), r(xmm_m128)]), vex(L128)._0f().op(0x53).r(), _64b | compat | avx), - inst("vrcpss", fmt("RVM", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x53).r(), _64b | compat | avx), - inst("vrsqrtps", fmt("RM", [w(xmm1), r(xmm_m128)]), vex(L128)._0f().op(0x52).r(), _64b | compat | avx), - inst("vrsqrtss", fmt("RVM", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x52).r(), _64b | compat | avx), + inst("vrcpps", fmt("RM", [w(xmm1), r(xmm_m128)]), vex(L128)._0f().op(0x53).r(), (_64b | compat) & avx), + inst("vrcpss", fmt("RVM", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x53).r(), (_64b | compat) & avx), + inst("vrsqrtps", fmt("RM", [w(xmm1), r(xmm_m128)]), vex(L128)._0f().op(0x52).r(), (_64b | compat) & avx), + inst("vrsqrtss", fmt("RVM", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x52).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/round.rs b/cranelift/assembler-x64/meta/src/instructions/round.rs index 86b537224e..d809c406cf 100644 --- a/cranelift/assembler-x64/meta/src/instructions/round.rs +++ b/cranelift/assembler-x64/meta/src/instructions/round.rs @@ -4,15 +4,15 @@ use crate::dsl::{align, fmt, inst, r, rex, vex, w}; #[rustfmt::skip] // Keeps instructions on a single line. pub fn list() -> Vec { vec![ - inst("roundpd", fmt("RMI", [w(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x09]).ib(), _64b | compat | sse41), - inst("roundps", fmt("RMI", [w(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x08]).ib(), _64b | compat | sse41), - inst("roundsd", fmt("RMI", [w(xmm1), r(xmm_m64), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x0B]).ib(), _64b | compat | sse41), - inst("roundss", fmt("RMI", [w(xmm1), r(xmm_m32), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x0A]).ib(), _64b | compat | sse41), + inst("roundpd", fmt("RMI", [w(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x09]).ib(), (_64b | compat) & sse41), + inst("roundps", fmt("RMI", [w(xmm1), r(align(xmm_m128)), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x08]).ib(), (_64b | compat) & sse41), + inst("roundsd", fmt("RMI", [w(xmm1), r(xmm_m64), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x0B]).ib(), (_64b | compat) & sse41), + inst("roundss", fmt("RMI", [w(xmm1), r(xmm_m32), r(imm8)]), rex([0x66, 0x0F, 0x3A, 0x0A]).ib(), (_64b | compat) & sse41), // AVX versions - inst("vroundpd", fmt("RMI", [w(xmm1), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f3a().ib().op(0x09), _64b | compat | avx), - inst("vroundps", fmt("RMI", [w(xmm1), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f3a().ib().op(0x08), _64b | compat | avx), - inst("vroundsd", fmt("RVMI", [w(xmm1), r(xmm2), r(xmm_m64), r(imm8)]), vex(LIG)._66()._0f3a().ib().op(0x0b), _64b | compat | avx), - inst("vroundss", fmt("RVMI", [w(xmm1), r(xmm2), r(xmm_m32), r(imm8)]), vex(LIG)._66()._0f3a().ib().op(0x0a), _64b | compat | avx), + inst("vroundpd", fmt("RMI", [w(xmm1), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f3a().ib().op(0x09), (_64b | compat) & avx), + inst("vroundps", fmt("RMI", [w(xmm1), r(xmm_m128), r(imm8)]), vex(L128)._66()._0f3a().ib().op(0x08), (_64b | compat) & avx), + inst("vroundsd", fmt("RVMI", [w(xmm1), r(xmm2), r(xmm_m64), r(imm8)]), vex(LIG)._66()._0f3a().ib().op(0x0b), (_64b | compat) & avx), + inst("vroundss", fmt("RVMI", [w(xmm1), r(xmm2), r(xmm_m32), r(imm8)]), vex(LIG)._66()._0f3a().ib().op(0x0a), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/shift.rs b/cranelift/assembler-x64/meta/src/instructions/shift.rs index 1d1c561711..eccd59cfc1 100644 --- a/cranelift/assembler-x64/meta/src/instructions/shift.rs +++ b/cranelift/assembler-x64/meta/src/instructions/shift.rs @@ -76,70 +76,70 @@ pub fn list() -> Vec { inst("shldq", fmt("MRC", [rw(rm64), r(r64), r(cl)]), rex([0x0F, 0xA5]).ib().w(), _64b), // BMI2 shifts - inst("sarxl", fmt("RMV", [w(r32a), r(rm32), r(r32b)]), vex(LZ)._f3()._0f38().w0().op(0xF7), _64b | compat | bmi2), - inst("shlxl", fmt("RMV", [w(r32a), r(rm32), r(r32b)]), vex(LZ)._66()._0f38().w0().op(0xF7), _64b | compat | bmi2), - inst("shrxl", fmt("RMV", [w(r32a), r(rm32), r(r32b)]), vex(LZ)._f2()._0f38().w0().op(0xF7), _64b | compat | bmi2), - inst("sarxq", fmt("RMV", [w(r64a), r(rm64), r(r64b)]), vex(LZ)._f3()._0f38().w1().op(0xF7), _64b | bmi2), - inst("shlxq", fmt("RMV", [w(r64a), r(rm64), r(r64b)]), vex(LZ)._66()._0f38().w1().op(0xF7), _64b | bmi2), - inst("shrxq", fmt("RMV", [w(r64a), r(rm64), r(r64b)]), vex(LZ)._f2()._0f38().w1().op(0xF7), _64b | bmi2), - inst("rorxl", fmt("RMI", [w(r32), r(rm32), r(imm8)]), vex(LZ)._f2()._0f3a().w0().op(0xF0).r().ib(), _64b | compat | bmi2), - inst("rorxq", fmt("RMI", [w(r64), r(rm64), r(imm8)]), vex(LZ)._f2()._0f3a().w1().op(0xF0).r().ib(), _64b | bmi2), + inst("sarxl", fmt("RMV", [w(r32a), r(rm32), r(r32b)]), vex(LZ)._f3()._0f38().w0().op(0xF7), (_64b | compat) & bmi2), + inst("shlxl", fmt("RMV", [w(r32a), r(rm32), r(r32b)]), vex(LZ)._66()._0f38().w0().op(0xF7), (_64b | compat) & bmi2), + inst("shrxl", fmt("RMV", [w(r32a), r(rm32), r(r32b)]), vex(LZ)._f2()._0f38().w0().op(0xF7), (_64b | compat) & bmi2), + inst("sarxq", fmt("RMV", [w(r64a), r(rm64), r(r64b)]), vex(LZ)._f3()._0f38().w1().op(0xF7), _64b & bmi2), + inst("shlxq", fmt("RMV", [w(r64a), r(rm64), r(r64b)]), vex(LZ)._66()._0f38().w1().op(0xF7), _64b & bmi2), + inst("shrxq", fmt("RMV", [w(r64a), r(rm64), r(r64b)]), vex(LZ)._f2()._0f38().w1().op(0xF7), _64b & bmi2), + inst("rorxl", fmt("RMI", [w(r32), r(rm32), r(imm8)]), vex(LZ)._f2()._0f3a().w0().op(0xF0).r().ib(), (_64b | compat) & bmi2), + inst("rorxq", fmt("RMI", [w(r64), r(rm64), r(imm8)]), vex(LZ)._f2()._0f3a().w1().op(0xF0).r().ib(), _64b & bmi2), // Vector instructions (shift left). - inst("psllw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF1]).r(), _64b | compat | sse2).alt(avx, "vpsllw_c"), - inst("psllw", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x71]).digit(6).ib(), _64b | compat | sse2).alt(avx, "vpsllw_d"), - inst("pslld", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF2]).r(), _64b | compat | sse2).alt(avx, "vpslld_c"), - inst("pslld", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x72]).digit(6).ib(), _64b | compat | sse2).alt(avx, "vpslld_d"), - inst("psllq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF3]).r(), _64b | compat | sse2).alt(avx, "vpsllq_c"), - inst("psllq", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x73]).digit(6).ib(), _64b | compat | sse2).alt(avx, "vpsllq_d"), - inst("vpsllw", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF1).r(), _64b | compat | avx), - inst("vpsllw", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x71).digit(6).ib(), _64b | compat | avx), - inst("vpslld", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF2).r(), _64b | compat | avx), - inst("vpslld", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x72).digit(6).ib(), _64b | compat | avx), - inst("vpsllq", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF3).r(), _64b | compat | avx), - inst("vpsllq", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x73).digit(6).ib(), _64b | compat | avx), + inst("psllw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF1]).r(), (_64b | compat) & sse2).alt(avx, "vpsllw_c"), + inst("psllw", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x71]).digit(6).ib(), (_64b | compat) & sse2).alt(avx, "vpsllw_d"), + inst("pslld", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF2]).r(), (_64b | compat) & sse2).alt(avx, "vpslld_c"), + inst("pslld", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x72]).digit(6).ib(), (_64b | compat) & sse2).alt(avx, "vpslld_d"), + inst("psllq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF3]).r(), (_64b | compat) & sse2).alt(avx, "vpsllq_c"), + inst("psllq", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x73]).digit(6).ib(), (_64b | compat) & sse2).alt(avx, "vpsllq_d"), + inst("vpsllw", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF1).r(), (_64b | compat) & avx), + inst("vpsllw", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x71).digit(6).ib(), (_64b | compat) & avx), + inst("vpslld", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF2).r(), (_64b | compat) & avx), + inst("vpslld", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x72).digit(6).ib(), (_64b | compat) & avx), + inst("vpsllq", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF3).r(), (_64b | compat) & avx), + inst("vpsllq", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x73).digit(6).ib(), (_64b | compat) & avx), // FIXME: uncomment once the avx512bw feature is bound - // inst("vpsllw", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().wig().op(0xF1).r(), _64b | compat | avx512vl | avx512bw), - // inst("vpsllw", fmt("E", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, FullMem)._66()._0f().wig().op(0x71).digit(6).ib(), _64b | compat | avx512vl | avx512bw), - inst("vpslld", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w0().op(0xF2).r(), _64b | compat | avx512vl | avx512f), - inst("vpslld", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w0().op(0x72).digit(6).ib(), _64b | compat | avx512vl | avx512f), - inst("vpsllq", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w1().op(0xF3).r(), _64b | compat | avx512vl | avx512f), - inst("vpsllq", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w1().op(0x73).digit(6).ib(), _64b | compat | avx512vl | avx512f), + // inst("vpsllw", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().wig().op(0xF1).r(), (_64b | compat) & avx512vl & avx512bw), + // inst("vpsllw", fmt("E", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, FullMem)._66()._0f().wig().op(0x71).digit(6).ib(), (_64b | compat) & avx512vl & avx512bw), + inst("vpslld", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w0().op(0xF2).r(), (_64b | compat) & avx512vl & avx512f), + inst("vpslld", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w0().op(0x72).digit(6).ib(), (_64b | compat) & avx512vl & avx512f), + inst("vpsllq", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w1().op(0xF3).r(), (_64b | compat) & avx512vl & avx512f), + inst("vpsllq", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w1().op(0x73).digit(6).ib(), (_64b | compat) & avx512vl & avx512f), // Vector instructions (shift right). - inst("psraw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE1]).r(), _64b | compat | sse2).alt(avx, "vpsraw_c"), - inst("psraw", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x71]).digit(4).ib(), _64b | compat | sse2).alt(avx, "vpsraw_d"), - inst("psrad", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE2]).r(), _64b | compat | sse2).alt(avx, "vpsrad_c"), - inst("psrad", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x72]).digit(4).ib(), _64b | compat | sse2).alt(avx, "vpsrad_d"), - inst("psrlw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD1]).r(), _64b | compat | sse2).alt(avx, "vpsrlw_c"), - inst("psrlw", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x71]).digit(2).ib(), _64b | compat | sse2).alt(avx, "vpsrlw_d"), - inst("psrld", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD2]).r(), _64b | compat | sse2).alt(avx, "vpsrld_c"), - inst("psrld", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x72]).digit(2).ib(), _64b | compat | sse2).alt(avx, "vpsrld_d"), - inst("psrlq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD3]).r(), _64b | compat | sse2).alt(avx, "vpsrlq_c"), - inst("psrlq", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x73]).digit(2).ib(), _64b | compat | sse2).alt(avx, "vpsrlq_d"), - inst("vpsraw", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE1).r(), _64b | compat | avx), - inst("vpsraw", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x71).digit(4).ib(), _64b | compat | avx), - inst("vpsrad", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE2).r(), _64b | compat | avx), - inst("vpsrad", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x72).digit(4).ib(), _64b | compat | avx), - inst("vpsrlw", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD1).r(), _64b | compat | avx), - inst("vpsrlw", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x71).digit(2).ib(), _64b | compat | avx), - inst("vpsrld", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD2).r(), _64b | compat | avx), - inst("vpsrld", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x72).digit(2).ib(), _64b | compat | avx), - inst("vpsrlq", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD3).r(), _64b | compat | avx), - inst("vpsrlq", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x73).digit(2).ib(), _64b | compat | avx), + inst("psraw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE1]).r(), (_64b | compat) & sse2).alt(avx, "vpsraw_c"), + inst("psraw", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x71]).digit(4).ib(), (_64b | compat) & sse2).alt(avx, "vpsraw_d"), + inst("psrad", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE2]).r(), (_64b | compat) & sse2).alt(avx, "vpsrad_c"), + inst("psrad", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x72]).digit(4).ib(), (_64b | compat) & sse2).alt(avx, "vpsrad_d"), + inst("psrlw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD1]).r(), (_64b | compat) & sse2).alt(avx, "vpsrlw_c"), + inst("psrlw", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x71]).digit(2).ib(), (_64b | compat) & sse2).alt(avx, "vpsrlw_d"), + inst("psrld", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD2]).r(), (_64b | compat) & sse2).alt(avx, "vpsrld_c"), + inst("psrld", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x72]).digit(2).ib(), (_64b | compat) & sse2).alt(avx, "vpsrld_d"), + inst("psrlq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD3]).r(), (_64b | compat) & sse2).alt(avx, "vpsrlq_c"), + inst("psrlq", fmt("B", [rw(xmm1), r(imm8)]), rex([0x66, 0x0F, 0x73]).digit(2).ib(), (_64b | compat) & sse2).alt(avx, "vpsrlq_d"), + inst("vpsraw", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE1).r(), (_64b | compat) & avx), + inst("vpsraw", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x71).digit(4).ib(), (_64b | compat) & avx), + inst("vpsrad", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE2).r(), (_64b | compat) & avx), + inst("vpsrad", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x72).digit(4).ib(), (_64b | compat) & avx), + inst("vpsrlw", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD1).r(), (_64b | compat) & avx), + inst("vpsrlw", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x71).digit(2).ib(), (_64b | compat) & avx), + inst("vpsrld", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD2).r(), (_64b | compat) & avx), + inst("vpsrld", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x72).digit(2).ib(), (_64b | compat) & avx), + inst("vpsrlq", fmt("C", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD3).r(), (_64b | compat) & avx), + inst("vpsrlq", fmt("D", [w(xmm1), r(xmm2), r(imm8)]), vex(L128)._66()._0f().op(0x73).digit(2).ib(), (_64b | compat) & avx), // FIXME: uncomment once the avx512bw feature is bound - // inst("vpsraw", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().wig().op(0xE1).r(), _64b | compat | avx512vl | avx512bw), - // inst("vpsraw", fmt("E", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, FullMem)._66()._0f().wig().op(0x71).digit(4).ib(), _64b | compat | avx512vl | avx512bw), - inst("vpsrad", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w0().op(0xE2).r(), _64b | compat | avx512vl | avx512f), - inst("vpsrad", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w0().op(0x72).digit(4).ib(), _64b | compat | avx512vl | avx512f), - inst("vpsraq", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w1().op(0xE2).r(), _64b | compat | avx512vl | avx512f), - inst("vpsraq", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w1().op(0x72).digit(4).ib(), _64b | compat | avx512vl | avx512f), + // inst("vpsraw", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().wig().op(0xE1).r(), (_64b | compat) & avx512vl & avx512bw), + // inst("vpsraw", fmt("E", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, FullMem)._66()._0f().wig().op(0x71).digit(4).ib(), (_64b | compat) & avx512vl & avx512bw), + inst("vpsrad", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w0().op(0xE2).r(), (_64b | compat) & avx512vl & avx512f), + inst("vpsrad", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w0().op(0x72).digit(4).ib(), (_64b | compat) & avx512vl & avx512f), + inst("vpsraq", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w1().op(0xE2).r(), (_64b | compat) & avx512vl & avx512f), + inst("vpsraq", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w1().op(0x72).digit(4).ib(), (_64b | compat) & avx512vl & avx512f), // FIXME: uncomment once the avx512bw feature is bound - // inst("vpsrlw", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().wig().op(0xD1).r(), _64b | compat | avx512vl | avx512bw), - // inst("vpsrlw", fmt("E", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, FullMem)._66()._0f().wig().op(0x71).digit(2).ib(), _64b | compat | avx512vl | avx512bw), - inst("vpsrld", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w0().op(0xD2).r(), _64b | compat | avx512vl | avx512f), - inst("vpsrld", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w0().op(0x72).digit(2).ib(), _64b | compat | avx512vl | avx512f), - inst("vpsrlq", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w1().op(0xD3).r(), _64b | compat | avx512vl | avx512f), - inst("vpsrlq", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w1().op(0x73).digit(2).ib(), _64b | compat | avx512vl | avx512f), + // inst("vpsrlw", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().wig().op(0xD1).r(), (_64b | compat) & avx512vl & avx512bw), + // inst("vpsrlw", fmt("E", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, FullMem)._66()._0f().wig().op(0x71).digit(2).ib(), (_64b | compat) & avx512vl & avx512bw), + inst("vpsrld", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w0().op(0xD2).r(), (_64b | compat) & avx512vl & avx512f), + inst("vpsrld", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w0().op(0x72).digit(2).ib(), (_64b | compat) & avx512vl & avx512f), + inst("vpsrlq", fmt("G", [w(xmm1), r(xmm2), r(xmm_m128)]), evex(L128, Mem128)._66()._0f().w1().op(0xD3).r(), (_64b | compat) & avx512vl & avx512f), + inst("vpsrlq", fmt("F", [w(xmm1), r(xmm_m128), r(imm8)]), evex(L128, Full)._66()._0f().w1().op(0x73).digit(2).ib(), (_64b | compat) & avx512vl & avx512f), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/sqrt.rs b/cranelift/assembler-x64/meta/src/instructions/sqrt.rs index 30f6f6b4ec..fd4f19ce44 100644 --- a/cranelift/assembler-x64/meta/src/instructions/sqrt.rs +++ b/cranelift/assembler-x64/meta/src/instructions/sqrt.rs @@ -4,13 +4,13 @@ use crate::dsl::{align, fmt, inst, r, rex, rw, vex, w}; #[rustfmt::skip] // Keeps instructions on a single line. pub fn list() -> Vec { vec![ - inst("sqrtss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x51]).r(), _64b | compat | sse).alt(avx, "vsqrtss_b"), - inst("sqrtsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x51]).r(), _64b | compat | sse2).alt(avx, "vsqrtsd_b"), - inst("sqrtps", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x51]).r(), _64b | compat | sse).alt(avx, "vsqrtps_b"), - inst("sqrtpd", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x51]).r(), _64b | compat | sse2).alt(avx, "vsqrtpd_b"), - inst("vsqrtss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x51).r(), _64b | compat | avx), - inst("vsqrtsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x51).r(), _64b | compat | avx), - inst("vsqrtps", fmt("B", [w(xmm1), r(xmm_m128)]), vex(L128)._0f().op(0x51).r(), _64b | compat | avx), - inst("vsqrtpd", fmt("B", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f().op(0x51).r(), _64b | compat | avx), + inst("sqrtss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x51]).r(), (_64b | compat) & sse).alt(avx, "vsqrtss_b"), + inst("sqrtsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x51]).r(), (_64b | compat) & sse2).alt(avx, "vsqrtsd_b"), + inst("sqrtps", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x51]).r(), (_64b | compat) & sse).alt(avx, "vsqrtps_b"), + inst("sqrtpd", fmt("A", [w(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x51]).r(), (_64b | compat) & sse2).alt(avx, "vsqrtpd_b"), + inst("vsqrtss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(LIG)._f3()._0f().op(0x51).r(), (_64b | compat) & avx), + inst("vsqrtsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(LIG)._f2()._0f().op(0x51).r(), (_64b | compat) & avx), + inst("vsqrtps", fmt("B", [w(xmm1), r(xmm_m128)]), vex(L128)._0f().op(0x51).r(), (_64b | compat) & avx), + inst("vsqrtpd", fmt("B", [w(xmm1), r(xmm_m128)]), vex(L128)._66()._0f().op(0x51).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/sub.rs b/cranelift/assembler-x64/meta/src/instructions/sub.rs index 80884f041c..e476f7292a 100644 --- a/cranelift/assembler-x64/meta/src/instructions/sub.rs +++ b/cranelift/assembler-x64/meta/src/instructions/sub.rs @@ -63,29 +63,29 @@ pub fn list() -> Vec { inst("lock_sbbl", fmt("MR", [rw(m32), r(r32)]), rex([0xf0, 0x19]).r(), _64b | compat).custom(Mnemonic), inst("lock_sbbq", fmt("MR", [rw(m64), r(r64)]), rex([0xf0, 0x19]).w().r(), _64b).custom(Mnemonic), // Vector instructions. - inst("subss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x5C]).r(), _64b | compat | sse).alt(avx, "vsubss_b"), - inst("subsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x5C]).r(), _64b | compat | sse2).alt(avx, "vsubsd_b"), - inst("subps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x5C]).r(), _64b | compat | sse).alt(avx, "vsubps_b"), - inst("subpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x5C]).r(), _64b | compat | sse2).alt(avx, "vsubpd_b"), - inst("psubb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF8]).r(), _64b | compat | sse2).alt(avx, "vpsubb_b"), - inst("psubw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF9]).r(), _64b | compat | sse2).alt(avx, "vpsubw_b"), - inst("psubd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xFA]).r(), _64b | compat | sse2).alt(avx, "vpsubd_b"), - inst("psubq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xFB]).r(), _64b | compat | sse2).alt(avx, "vpsubq_b"), - inst("psubsb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE8]).r(), _64b | compat | sse2).alt(avx, "vpsubsb_b"), - inst("psubsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE9]).r(), _64b | compat | sse2).alt(avx, "vpsubsw_b"), - inst("psubusb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD8]).r(), _64b | compat | sse2).alt(avx, "vpsubusb_b"), - inst("psubusw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD9]).r(), _64b | compat | sse2).alt(avx, "vpsubusw_b"), - inst("vsubss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(L128)._f3()._0f().op(0x5C).r(), _64b | compat | avx), - inst("vsubsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(L128)._f2()._0f().op(0x5C).r(), _64b | compat | avx), - inst("vsubps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x5C).r(), _64b | compat | avx), - inst("vsubpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x5C).r(), _64b | compat | avx), - inst("vpsubb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF8).r(), _64b | compat | avx), - inst("vpsubw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF9).r(), _64b | compat | avx), - inst("vpsubd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xFA).r(), _64b | compat | avx), - inst("vpsubq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xFB).r(), _64b | compat | avx), - inst("vpsubsb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE8).r(), _64b | compat | avx), - inst("vpsubsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE9).r(), _64b | compat | avx), - inst("vpsubusb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD8).r(), _64b | compat | avx), - inst("vpsubusw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD9).r(), _64b | compat | avx), + inst("subss", fmt("A", [rw(xmm1), r(xmm_m32)]), rex([0xF3, 0x0F, 0x5C]).r(), (_64b | compat) & sse).alt(avx, "vsubss_b"), + inst("subsd", fmt("A", [rw(xmm1), r(xmm_m64)]), rex([0xF2, 0x0F, 0x5C]).r(), (_64b | compat) & sse2).alt(avx, "vsubsd_b"), + inst("subps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x5C]).r(), (_64b | compat) & sse).alt(avx, "vsubps_b"), + inst("subpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x5C]).r(), (_64b | compat) & sse2).alt(avx, "vsubpd_b"), + inst("psubb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF8]).r(), (_64b | compat) & sse2).alt(avx, "vpsubb_b"), + inst("psubw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xF9]).r(), (_64b | compat) & sse2).alt(avx, "vpsubw_b"), + inst("psubd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xFA]).r(), (_64b | compat) & sse2).alt(avx, "vpsubd_b"), + inst("psubq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xFB]).r(), (_64b | compat) & sse2).alt(avx, "vpsubq_b"), + inst("psubsb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE8]).r(), (_64b | compat) & sse2).alt(avx, "vpsubsb_b"), + inst("psubsw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xE9]).r(), (_64b | compat) & sse2).alt(avx, "vpsubsw_b"), + inst("psubusb", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD8]).r(), (_64b | compat) & sse2).alt(avx, "vpsubusb_b"), + inst("psubusw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xD9]).r(), (_64b | compat) & sse2).alt(avx, "vpsubusw_b"), + inst("vsubss", fmt("B", [w(xmm1), r(xmm2), r(xmm_m32)]), vex(L128)._f3()._0f().op(0x5C).r(), (_64b | compat) & avx), + inst("vsubsd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m64)]), vex(L128)._f2()._0f().op(0x5C).r(), (_64b | compat) & avx), + inst("vsubps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x5C).r(), (_64b | compat) & avx), + inst("vsubpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x5C).r(), (_64b | compat) & avx), + inst("vpsubb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF8).r(), (_64b | compat) & avx), + inst("vpsubw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xF9).r(), (_64b | compat) & avx), + inst("vpsubd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xFA).r(), (_64b | compat) & avx), + inst("vpsubq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xFB).r(), (_64b | compat) & avx), + inst("vpsubsb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE8).r(), (_64b | compat) & avx), + inst("vpsubsw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xE9).r(), (_64b | compat) & avx), + inst("vpsubusb", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD8).r(), (_64b | compat) & avx), + inst("vpsubusw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xD9).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/unpack.rs b/cranelift/assembler-x64/meta/src/instructions/unpack.rs index a6051d1c56..278cd8a1f9 100644 --- a/cranelift/assembler-x64/meta/src/instructions/unpack.rs +++ b/cranelift/assembler-x64/meta/src/instructions/unpack.rs @@ -5,28 +5,28 @@ use crate::dsl::{align, fmt, inst, r, rex, rw, vex, w}; pub fn list() -> Vec { vec![ // Unpack floating-point. - inst("unpcklps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x14]).r(), _64b | compat | sse).alt(avx, "vunpcklps_b"), - inst("unpcklpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x14]).r(), _64b | compat | sse2).alt(avx, "vunpcklpd_b"), - inst("unpckhps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x15]).r(), _64b | compat | sse).alt(avx, "vunpckhps_b"), - inst("vunpcklps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x14).r(), _64b | compat | avx), - inst("vunpcklpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x14).r(), _64b | compat | avx), - inst("vunpckhps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x15).r(), _64b | compat | avx), + inst("unpcklps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x14]).r(), (_64b | compat) & sse).alt(avx, "vunpcklps_b"), + inst("unpcklpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x14]).r(), (_64b | compat) & sse2).alt(avx, "vunpcklpd_b"), + inst("unpckhps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x15]).r(), (_64b | compat) & sse).alt(avx, "vunpckhps_b"), + inst("vunpcklps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x14).r(), (_64b | compat) & avx), + inst("vunpcklpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x14).r(), (_64b | compat) & avx), + inst("vunpckhps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x15).r(), (_64b | compat) & avx), // Unpack packed integers. - inst("punpckhbw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x68]).r(), _64b | compat | sse2).alt(avx, "vpunpckhbw_b"), - inst("punpckhwd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x69]).r(), _64b | compat | sse2).alt(avx, "vpunpckhwd_b"), - inst("punpckhdq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x6A]).r(), _64b | compat | sse2).alt(avx, "vpunpckhdq_b"), - inst("punpckhqdq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x6D]).r(), _64b | compat | sse2).alt(avx, "vpunpckhqdq_b"), - inst("punpcklwd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x61]).r(), _64b | compat | sse2).alt(avx, "vpunpcklwd_b"), - inst("punpcklbw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x60]).r(), _64b | compat | sse2).alt(avx, "vpunpcklbw_b"), - inst("punpckldq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x62]).r(), _64b | compat | sse2).alt(avx, "vpunpckldq_b"), - inst("punpcklqdq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x6C]).r(), _64b | compat | sse2).alt(avx, "vpunpcklqdq_b"), - inst("vpunpckhbw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x68).r(), _64b | compat | avx), - inst("vpunpckhwd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x69).r(), _64b | compat | avx), - inst("vpunpckhdq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x6A).r(), _64b | compat | avx), - inst("vpunpckhqdq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x6D).r(), _64b | compat | avx), - inst("vpunpcklwd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x61).r(), _64b | compat | avx), - inst("vpunpcklbw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x60).r(), _64b | compat | avx), - inst("vpunpckldq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x62).r(), _64b | compat | avx), - inst("vpunpcklqdq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x6C).r(), _64b | compat | avx), + inst("punpckhbw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x68]).r(), (_64b | compat) & sse2).alt(avx, "vpunpckhbw_b"), + inst("punpckhwd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x69]).r(), (_64b | compat) & sse2).alt(avx, "vpunpckhwd_b"), + inst("punpckhdq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x6A]).r(), (_64b | compat) & sse2).alt(avx, "vpunpckhdq_b"), + inst("punpckhqdq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x6D]).r(), (_64b | compat) & sse2).alt(avx, "vpunpckhqdq_b"), + inst("punpcklwd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x61]).r(), (_64b | compat) & sse2).alt(avx, "vpunpcklwd_b"), + inst("punpcklbw", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x60]).r(), (_64b | compat) & sse2).alt(avx, "vpunpcklbw_b"), + inst("punpckldq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x62]).r(), (_64b | compat) & sse2).alt(avx, "vpunpckldq_b"), + inst("punpcklqdq", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x6C]).r(), (_64b | compat) & sse2).alt(avx, "vpunpcklqdq_b"), + inst("vpunpckhbw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x68).r(), (_64b | compat) & avx), + inst("vpunpckhwd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x69).r(), (_64b | compat) & avx), + inst("vpunpckhdq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x6A).r(), (_64b | compat) & avx), + inst("vpunpckhqdq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x6D).r(), (_64b | compat) & avx), + inst("vpunpcklwd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x61).r(), (_64b | compat) & avx), + inst("vpunpcklbw", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x60).r(), (_64b | compat) & avx), + inst("vpunpckldq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x62).r(), (_64b | compat) & avx), + inst("vpunpcklqdq", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x6C).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/meta/src/instructions/xor.rs b/cranelift/assembler-x64/meta/src/instructions/xor.rs index 30e5ce395c..08ac5a9f0f 100644 --- a/cranelift/assembler-x64/meta/src/instructions/xor.rs +++ b/cranelift/assembler-x64/meta/src/instructions/xor.rs @@ -34,11 +34,11 @@ pub fn list() -> Vec { inst("lock_xorl", fmt("MR", [rw(m32), r(r32)]), rex([0xf0, 0x31]).r(), _64b | compat).custom(Mnemonic), inst("lock_xorq", fmt("MR", [rw(m64), r(r64)]), rex([0xf0, 0x31]).w().r(), _64b).custom(Mnemonic), // Vector instructions. - inst("xorps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x57]).r(), _64b | compat | sse).alt(avx, "vxorps_b"), - inst("xorpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x57]).r(), _64b | compat | sse2).alt(avx, "vxorpd_b"), - inst("pxor", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xEF]).r(), _64b | compat | sse2).alt(avx, "vpxor_b"), - inst("vxorps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x57).r(), _64b | compat | avx), - inst("vxorpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x57).r(), _64b | compat | avx), - inst("vpxor", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xEF).r(), _64b | compat | avx), + inst("xorps", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x0F, 0x57]).r(), (_64b | compat) & sse).alt(avx, "vxorps_b"), + inst("xorpd", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0x57]).r(), (_64b | compat) & sse2).alt(avx, "vxorpd_b"), + inst("pxor", fmt("A", [rw(xmm1), r(align(xmm_m128))]), rex([0x66, 0x0F, 0xEF]).r(), (_64b | compat) & sse2).alt(avx, "vpxor_b"), + inst("vxorps", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._0f().op(0x57).r(), (_64b | compat) & avx), + inst("vxorpd", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0x57).r(), (_64b | compat) & avx), + inst("vpxor", fmt("B", [w(xmm1), r(xmm2), r(xmm_m128)]), vex(L128)._66()._0f().op(0xEF).r(), (_64b | compat) & avx), ] } diff --git a/cranelift/assembler-x64/src/features.rs b/cranelift/assembler-x64/src/features.rs new file mode 100644 index 0000000000..3559b97556 --- /dev/null +++ b/cranelift/assembler-x64/src/features.rs @@ -0,0 +1,98 @@ +//! CPU features. +//! +//! Across generations, CPUs add features that include new instructions, e.g., +//! [`Feature::sse`], [`Feature::avx`], etc. x64 instructions are governed by +//! boolean terms of these CPU features: e.g., `(_64b | compat) & ssse3`. These +//! terms are defined per instruction in the `meta` crate and are exposed to +//! users in two ways: +//! - via the [`Inst::is_available`] function, which uses the +//! [`AvailableFeatures`] trait below to query "is this instruction currently +//! allowed?"; use this for fast checks during compilation +//! - via the [`Inst::features`] function, which returns a fully-constructed +//! [`Features`] term; use this for time-insensitive analysis or +//! pretty-printing. +//! +//! ```rust +//! # use cranelift_assembler_x64::{Registers, inst}; +//! # pub struct Regs; +//! # impl Registers for Regs { +//! # type ReadGpr = u8; +//! # type ReadWriteGpr = u8; +//! # type WriteGpr = u8; +//! # type ReadXmm = u8; +//! # type ReadWriteXmm = u8; +//! # type WriteXmm = u8; +//! # } +//! let xmm0: u8 = 0; +//! let andps = inst::andps_a::::new(xmm0, xmm0); +//! assert_eq!(andps.features().to_string(), "((_64b | compat) & sse)"); +//! ``` +//! +//! [`Inst::is_available`]: crate::inst::Inst::is_available +//! [`Inst::features`]: crate::inst::Inst::features + +use crate::inst::for_each_feature; +use std::fmt; + +// Helpfully generate `enum Feature`. +macro_rules! create_feature_enum { + ($($f:ident)+) => { + /// A CPU feature. + /// + /// IA-32e mode is the typical mode of operation for modern 64-bit x86 + /// processors. It consists of two sub-modes: + /// - __64-bit mode__: uses the full 64-bit address space + /// - __compatibility mode__: allows use of legacy 32-bit code + /// + /// Other features listed here should match the __CPUID Feature Flags__ + /// column of the instruction tables of the x64 reference manual. + /// + /// This is generated from the `dsl::Feature` enumeration defined in the + /// `meta` crate; see [`for_each_feature`]. + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub enum Feature { + $($f,)+ + } + }; +} +for_each_feature!(create_feature_enum); + +// Helpfully generate trait functions in `AvailableFeatures`. +macro_rules! add_func { + ($($f:ident)+) => { + $(fn $f(&self) -> bool;)+ + }; +} + +/// A trait for querying CPU features. +/// +/// This is generated from the `dsl::Feature` enumeration defined in the `meta` +/// crate. It allows querying the CPUID features required by an instruction; see +/// [`Inst::is_available`] and [`for_each_feature`]. +/// +/// [`Inst::is_available`]: crate::inst::Inst::is_available +pub trait AvailableFeatures { + for_each_feature!(add_func); +} + +/// A boolean term of CPU features. +/// +/// An instruction is valid when the boolean term (a recursive tree of `AND` and +/// `OR` terms) is satisfied; see [`Inst::features`]. +/// +/// [`Inst::features`]: crate::inst::Inst::features +pub enum Features { + And(&'static Features, &'static Features), + Or(&'static Features, &'static Features), + Feature(Feature), +} + +impl fmt::Display for Features { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Features::And(lhs, rhs) => write!(f, "({lhs} & {rhs})"), + Features::Or(lhs, rhs) => write!(f, "({lhs} | {rhs})"), + Features::Feature(feature) => write!(f, "{feature:#?}"), + } + } +} diff --git a/cranelift/assembler-x64/src/inst.rs b/cranelift/assembler-x64/src/inst.rs index 5acc4b51c7..173336a737 100644 --- a/cranelift/assembler-x64/src/inst.rs +++ b/cranelift/assembler-x64/src/inst.rs @@ -6,6 +6,7 @@ use crate::Fixed; use crate::api::{AsReg, CodeSink, RegisterVisitor, Registers, TrapCode}; use crate::evex::EvexPrefix; +use crate::features::{AvailableFeatures, Feature, Features}; use crate::gpr::{self, Gpr, Size}; use crate::imm::{Extension, Imm8, Imm16, Imm32, Imm64, Simm8, Simm32}; use crate::mem::{Amode, GprMem, XmmMem}; @@ -15,3 +16,45 @@ use crate::xmm::{self, Xmm}; // Include code generated by the `meta` crate. include!(concat!(env!("OUT_DIR"), "/assembler.rs")); + +/// A macro listing all available CPU features. +/// +/// This is generated from the `dsl::Feature` enumeration defined in the `meta` +/// crate. It makes it easier to generate code based on the available features. +/// +/// ``` +/// # use cranelift_assembler_x64::{AvailableFeatures, Fixed, for_each_feature, Imm8, inst, Registers}; +/// // Tell the assembler the type of registers we're using; we can always +/// // encode a HW register as a `u8` (e.g., `eax = 0`). +/// pub struct Regs; +/// impl Registers for Regs { +/// type ReadGpr = u8; +/// type ReadWriteGpr = u8; +/// type WriteGpr = u8; +/// type ReadXmm = u8; +/// type ReadWriteXmm = u8; +/// type WriteXmm = u8; +/// } +/// +/// // Define a target that says all CPU features are available. +/// macro_rules! return_true { ($($f:ident)+) => { $(fn $f(&self) -> bool { true })+ }; } +/// struct AllFeatures; +/// impl AvailableFeatures for AllFeatures { +/// for_each_feature!(return_true); +/// } +/// +/// // Define a target that says no CPU features are available. +/// macro_rules! return_false { ($($f:ident)+) => { $(fn $f(&self) -> bool { false })+ }; } +/// struct NoFeatures; +/// impl AvailableFeatures for NoFeatures { +/// for_each_feature!(return_false); +/// } +/// +/// let rax: u8 = 0; +/// let and = inst::andb_i::::new(Fixed(rax), Imm8::new(0b10101010)); +/// +/// assert!(and.is_available(&AllFeatures)); +/// assert!(!and.is_available(&NoFeatures)); +/// ``` +#[doc(inline)] +pub use for_each_feature; diff --git a/cranelift/assembler-x64/src/lib.rs b/cranelift/assembler-x64/src/lib.rs index 6ee5cd35cd..7b5b6db7fc 100644 --- a/cranelift/assembler-x64/src/lib.rs +++ b/cranelift/assembler-x64/src/lib.rs @@ -6,7 +6,7 @@ //! trait, allowing users of this assembler to plug in their own register types. //! //! ``` -//! # use cranelift_assembler_x64::{Feature, Fixed, Imm8, inst, Inst, Registers}; +//! # use cranelift_assembler_x64::{Fixed, Imm8, inst, Inst, Registers}; //! // Tell the assembler the type of registers we're using; we can always //! // encode a HW register as a `u8` (e.g., `eax = 0`). //! pub struct Regs; @@ -26,13 +26,10 @@ //! let and = inst::andb_i::new(Fixed(rax), Imm8::new(0b10101010)); //! let seq: Vec> = vec![and.into()]; //! -//! // Now we can encode this sequence into a code buffer, checking that each -//! // instruction is valid in 64-bit mode. +//! // Now we can encode this sequence into a code buffer. //! let mut buffer = vec![]; //! for inst in seq { -//! if inst.features().contains(&Feature::_64b) { -//! inst.encode(&mut buffer); -//! } +//! inst.encode(&mut buffer); //! } //! assert_eq!(buffer, vec![0x24, 0b10101010]); //! ``` @@ -48,6 +45,7 @@ mod api; mod custom; mod evex; +mod features; mod fixed; pub mod gpr; mod imm; @@ -69,18 +67,10 @@ pub mod fuzz; // the library top-level. pub use inst::Inst; -/// A CPU feature. -/// -/// This is generated from the `dsl::Feature` enumeration defined in the `meta` -/// crate (i.e., an exact replica). It describes the CPUID features required by -/// an instruction; see [`Inst::features`]. -#[doc(inline)] -// Like `Inst` above, a convenient re-export. -pub use inst::Feature; - pub use api::{ AsReg, CodeSink, Constant, KnownOffset, Label, RegisterVisitor, Registers, TrapCode, }; +pub use features::{AvailableFeatures, Feature, Features}; pub use fixed::Fixed; pub use gpr::{Gpr, NonRspGpr, Size}; pub use imm::{Extension, Imm8, Imm16, Imm32, Imm64, Simm8, Simm16, Simm32}; diff --git a/cranelift/bforest/Cargo.toml b/cranelift/bforest/Cargo.toml index 505438720a..24cd69c79f 100644 --- a/cranelift/bforest/Cargo.toml +++ b/cranelift/bforest/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-bforest" -version = "0.123.0" +version = "0.124.0" description = "A forest of B+-trees" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://docs.rs/cranelift-bforest" diff --git a/cranelift/bitset/Cargo.toml b/cranelift/bitset/Cargo.toml index b6d4c05083..aa28ad5c82 100644 --- a/cranelift/bitset/Cargo.toml +++ b/cranelift/bitset/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-bitset" -version = "0.123.0" +version = "0.124.0" description = "Various bitset stuff for use inside Cranelift" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://docs.rs/cranelift-bitset" diff --git a/cranelift/codegen/Cargo.toml b/cranelift/codegen/Cargo.toml index 3c3e018a2c..809a666301 100644 --- a/cranelift/codegen/Cargo.toml +++ b/cranelift/codegen/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-codegen" -version = "0.123.0" +version = "0.124.0" description = "Low-level code generator library" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://docs.rs/cranelift-codegen" @@ -25,7 +25,7 @@ anyhow = { workspace = true, optional = true, features = ['std'] } bumpalo = "3" capstone = { workspace = true, optional = true } cranelift-assembler-x64 = { workspace = true } -cranelift-codegen-shared = { path = "./shared", version = "0.123.0" } +cranelift-codegen-shared = { path = "./shared", version = "0.124.0" } cranelift-entity = { workspace = true } cranelift-bforest = { workspace = true } cranelift-bitset = { workspace = true } @@ -56,8 +56,8 @@ env_logger = { workspace = true } proptest = { workspace = true } [build-dependencies] -cranelift-codegen-meta = { path = "meta", version = "0.123.0" } -cranelift-isle = { path = "../isle/isle", version = "=0.123.0" } +cranelift-codegen-meta = { path = "meta", version = "0.124.0" } +cranelift-isle = { path = "../isle/isle", version = "=0.124.0" } [features] default = ["std", "unwind", "host-arch", "timing"] diff --git a/cranelift/codegen/meta/Cargo.toml b/cranelift/codegen/meta/Cargo.toml index 858fd5e6eb..424a942623 100644 --- a/cranelift/codegen/meta/Cargo.toml +++ b/cranelift/codegen/meta/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cranelift-codegen-meta" authors = ["The Cranelift Project Developers"] -version = "0.123.0" +version = "0.124.0" description = "Metaprogram for cranelift-codegen code generator library" license = "Apache-2.0 WITH LLVM-exception" repository = "https://github.com/bytecodealliance/wasmtime" @@ -17,8 +17,8 @@ rustdoc-args = ["--document-private-items"] [dependencies] cranelift-srcgen = { workspace = true } -cranelift-assembler-x64-meta = { path = "../../assembler-x64/meta", version = "0.123.0" } -cranelift-codegen-shared = { path = "../shared", version = "0.123.0" } +cranelift-assembler-x64-meta = { path = "../../assembler-x64/meta", version = "0.124.0" } +cranelift-codegen-shared = { path = "../shared", version = "0.124.0" } pulley-interpreter = { workspace = true, optional = true } heck = "0.5.0" diff --git a/cranelift/codegen/shared/Cargo.toml b/cranelift/codegen/shared/Cargo.toml index 9789b4cb94..feebd2ab5d 100644 --- a/cranelift/codegen/shared/Cargo.toml +++ b/cranelift/codegen/shared/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-codegen-shared" -version = "0.123.0" +version = "0.124.0" description = "For code shared between cranelift-codegen-meta and cranelift-codegen" license = "Apache-2.0 WITH LLVM-exception" repository = "https://github.com/bytecodealliance/wasmtime" diff --git a/cranelift/codegen/src/inline.rs b/cranelift/codegen/src/inline.rs index 321af3fa7e..1d3f21c9a4 100644 --- a/cranelift/codegen/src/inline.rs +++ b/cranelift/codegen/src/inline.rs @@ -132,8 +132,15 @@ pub(crate) fn do_inlining( func_ref, } => { let args = cursor.func.dfg.inst_args(inst); + trace!( + "considering call site for inlining: {inst}: {}", + cursor.func.dfg.display_inst(inst), + ); match inliner.inline(&cursor.func, inst, opcode, func_ref, args) { - InlineCommand::KeepCall => continue, + InlineCommand::KeepCall => { + trace!(" --> keeping call"); + continue; + } InlineCommand::Inline(callee) => { inline_one( &mut allocs, @@ -157,8 +164,15 @@ pub(crate) fn do_inlining( exception, } => { let args = cursor.func.dfg.inst_args(inst); + trace!( + "considering call site for inlining: {inst}: {}", + cursor.func.dfg.display_inst(inst), + ); match inliner.inline(&cursor.func, inst, opcode, func_ref, args) { - InlineCommand::KeepCall => continue, + InlineCommand::KeepCall => { + trace!(" --> keeping call"); + continue; + } InlineCommand::Inline(callee) => { inline_one( &mut allocs, @@ -195,8 +209,18 @@ struct InliningAllocs { values: SecondaryMap>, /// Map from callee constant to inlined caller constant. + /// + /// Not in `EntityMap` because these are hash-consed inside the + /// `ir::Function`. constants: SecondaryMap>, + /// Map from callee to inlined caller external name refs. + /// + /// Not in `EntityMap` because these are hash-consed inside the + /// `ir::Function`. + user_external_name_refs: + SecondaryMap>, + /// The set of _caller_ inlined call instructions that need exception table /// fixups at the end of inlining. /// @@ -220,6 +244,7 @@ impl InliningAllocs { let InliningAllocs { values, constants, + user_external_name_refs, calls_needing_exception_table_fixup, } = self; @@ -229,6 +254,9 @@ impl InliningAllocs { constants.clear(); constants.resize(callee.dfg.constants.len()); + user_external_name_refs.clear(); + user_external_name_refs.resize(callee.params.user_named_funcs().len()); + // Note: We do not reserve capacity for // `calls_needing_exception_table_fixup` because it is a sparse set and // we don't know how large it needs to be ahead of time. @@ -1205,7 +1233,8 @@ fn create_entities( entity_map.block_offset = Some(create_blocks(allocs, func, callee)); entity_map.global_value_offset = Some(create_global_values(func, callee)); entity_map.sig_ref_offset = Some(create_sig_refs(func, callee)); - entity_map.func_ref_offset = Some(create_func_refs(func, callee, &entity_map)); + create_user_external_name_refs(allocs, func, callee); + entity_map.func_ref_offset = Some(create_func_refs(allocs, func, callee, &entity_map)); entity_map.stack_slot_offset = Some(create_stack_slots(func, callee)); entity_map.dynamic_type_offset = Some(create_dynamic_types(func, callee, &entity_map)); entity_map.dynamic_stack_slot_offset = @@ -1309,8 +1338,24 @@ fn create_sig_refs(func: &mut ir::Function, callee: &ir::Function) -> u32 { offset } +fn create_user_external_name_refs( + allocs: &mut InliningAllocs, + func: &mut ir::Function, + callee: &ir::Function, +) { + for (callee_named_func_ref, name) in callee.params.user_named_funcs().iter() { + let caller_named_func_ref = func.declare_imported_user_function(name.clone()); + allocs.user_external_name_refs[callee_named_func_ref] = Some(caller_named_func_ref).into(); + } +} + /// Translate `ir::FuncRef`s from the callee into the caller. -fn create_func_refs(func: &mut ir::Function, callee: &ir::Function, entity_map: &EntityMap) -> u32 { +fn create_func_refs( + allocs: &InliningAllocs, + func: &mut ir::Function, + callee: &ir::Function, + entity_map: &EntityMap, +) -> u32 { let offset = func.dfg.ext_funcs.len(); let offset = u32::try_from(offset).unwrap(); @@ -1322,7 +1367,17 @@ fn create_func_refs(func: &mut ir::Function, callee: &ir::Function, entity_map: } in callee.dfg.ext_funcs.values() { func.dfg.ext_funcs.push(ir::ExtFuncData { - name: name.clone(), + name: match name { + ir::ExternalName::User(name_ref) => { + ir::ExternalName::User(allocs.user_external_name_refs[*name_ref].expect( + "should have translated all `ir::UserExternalNameRef`s before translating \ + `ir::FuncRef`s", + )) + } + ir::ExternalName::TestCase(_) + | ir::ExternalName::LibCall(_) + | ir::ExternalName::KnownSymbol(_) => name.clone(), + }, signature: entity_map.inlined_sig_ref(*signature), colocated: *colocated, }); diff --git a/cranelift/codegen/src/ir/immediates.rs b/cranelift/codegen/src/ir/immediates.rs index 82ca114bee..79b9432960 100644 --- a/cranelift/codegen/src/ir/immediates.rs +++ b/cranelift/codegen/src/ir/immediates.rs @@ -10,7 +10,6 @@ use core::fmt::{self, Display, Formatter}; use core::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Sub}; use core::str::FromStr; use core::{i32, u32}; -use cranelift_entity::{Signed, Unsigned}; #[cfg(feature = "enable-serde")] use serde_derive::{Deserialize, Serialize}; @@ -124,8 +123,8 @@ impl Imm64 { let bit_width = u64::from(bit_width); let delta = 64 - bit_width; - let zero_extended = (self.0.unsigned() << delta) >> delta; - Imm64(zero_extended.signed()) + let zero_extended = (self.0.cast_unsigned() << delta) >> delta; + Imm64(zero_extended.cast_signed()) } } diff --git a/cranelift/codegen/src/isa/pulley_shared/settings.rs b/cranelift/codegen/src/isa/pulley_shared/settings.rs index ed9287d851..5d4fd19ed1 100644 --- a/cranelift/codegen/src/isa/pulley_shared/settings.rs +++ b/cranelift/codegen/src/isa/pulley_shared/settings.rs @@ -8,8 +8,9 @@ use crate::{ }; use core::fmt; -// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a +// Include code generated by `cranelift/codegen/meta/src/gen_settings.rs:`. This file contains a // public `Flags` struct with an impl for all of the settings defined in +// `cranelift/codegen/meta/src/isa/pulley_shared/settings.rs`. include!(concat!(env!("OUT_DIR"), "/settings-pulley.rs")); impl IsaFlags for Flags {} diff --git a/cranelift/codegen/src/isa/riscv64/settings.rs b/cranelift/codegen/src/isa/riscv64/settings.rs index 119190f9a9..c896dd5fe9 100644 --- a/cranelift/codegen/src/isa/riscv64/settings.rs +++ b/cranelift/codegen/src/isa/riscv64/settings.rs @@ -3,6 +3,7 @@ use crate::settings::{self, Builder, Value, detail}; use core::fmt; -// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a +// Include code generated by `cranelift/codegen/meta/src/gen_settings.rs:`. This file contains a // public `Flags` struct with an impl for all of the settings defined in +// `cranelift/codegen/meta/src/isa/riscv64/settings.rs`. include!(concat!(env!("OUT_DIR"), "/settings-riscv64.rs")); diff --git a/cranelift/codegen/src/isa/s390x/settings.rs b/cranelift/codegen/src/isa/s390x/settings.rs index 457aca49d3..5bf89030f1 100644 --- a/cranelift/codegen/src/isa/s390x/settings.rs +++ b/cranelift/codegen/src/isa/s390x/settings.rs @@ -3,7 +3,7 @@ use crate::settings::{self, Builder, Value, detail}; use core::fmt; -// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a +// Include code generated by `cranelift/codegen/meta/src/gen_settings.rs:`. This file contains a // public `Flags` struct with an impl for all of the settings defined in -// `cranelift-codegen/meta/src/isa/s390x/settings.rs`. +// `cranelift/codegen/meta/src/isa/s390x/settings.rs`. include!(concat!(env!("OUT_DIR"), "/settings-s390x.rs")); diff --git a/cranelift/codegen/src/isa/x64/inst/args.rs b/cranelift/codegen/src/isa/x64/inst/args.rs index 516eb2cfb4..ad71ae4da3 100644 --- a/cranelift/codegen/src/isa/x64/inst/args.rs +++ b/cranelift/codegen/src/isa/x64/inst/args.rs @@ -741,29 +741,6 @@ impl PrettyPrint for RegMem { } } -#[derive(Debug)] -pub(crate) enum InstructionSet { - SSE, - SSE2, - CMPXCHG16b, - SSE3, - SSSE3, - SSE41, - SSE42, - Popcnt, - Lzcnt, - BMI1, - BMI2, - FMA, - AVX, - AVX2, - AVX512BITALG, - AVX512DQ, - AVX512F, - AVX512VBMI, - AVX512VL, -} - /// This defines the ways a value can be extended: either signed- or zero-extension, or none for /// types that are not extended. Contrast with [ExtMode], which defines the widths from and to which /// values can be extended. diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs index 2df7a426a8..081596ed3c 100644 --- a/cranelift/codegen/src/isa/x64/inst/emit.rs +++ b/cranelift/codegen/src/isa/x64/inst/emit.rs @@ -165,38 +165,17 @@ pub(crate) fn emit( info: &EmitInfo, state: &mut EmitState, ) { - let matches_isa_flags = |iset_requirement: &InstructionSet| -> bool { - match iset_requirement { - // Cranelift assumes SSE2 at least. - InstructionSet::SSE | InstructionSet::SSE2 => true, - InstructionSet::CMPXCHG16b => info.isa_flags.use_cmpxchg16b(), - InstructionSet::SSE3 => info.isa_flags.use_sse3(), - InstructionSet::SSSE3 => info.isa_flags.use_ssse3(), - InstructionSet::SSE41 => info.isa_flags.use_sse41(), - InstructionSet::SSE42 => info.isa_flags.use_sse42(), - InstructionSet::Popcnt => info.isa_flags.use_popcnt(), - InstructionSet::Lzcnt => info.isa_flags.use_lzcnt(), - InstructionSet::BMI1 => info.isa_flags.use_bmi1(), - InstructionSet::BMI2 => info.isa_flags.has_bmi2(), - InstructionSet::FMA => info.isa_flags.has_fma(), - InstructionSet::AVX => info.isa_flags.has_avx(), - InstructionSet::AVX2 => info.isa_flags.has_avx2(), - InstructionSet::AVX512BITALG => info.isa_flags.has_avx512bitalg(), - InstructionSet::AVX512DQ => info.isa_flags.has_avx512dq(), - InstructionSet::AVX512F => info.isa_flags.has_avx512f(), - InstructionSet::AVX512VBMI => info.isa_flags.has_avx512vbmi(), - InstructionSet::AVX512VL => info.isa_flags.has_avx512vl(), - } - }; - - // Certain instructions may be present in more than one ISA feature set; we must at least match - // one of them in the target CPU. - let isa_requirements = inst.available_in_any_isa(); - if !isa_requirements.is_empty() && !isa_requirements.iter().all(matches_isa_flags) { + if !inst.is_available(&info) { + let features = if let Inst::External { inst } = inst { + inst.features().to_string() + } else { + "see `is_available` source for feature term".to_string() + }; panic!( - "Cannot emit inst '{inst:?}' for target; failed to match ISA requirements: {isa_requirements:?}" - ) + "Cannot emit inst '{inst:?}' for target; failed to match ISA requirements: {features}" + ); } + match inst { Inst::CheckedSRemSeq { divisor, .. } | Inst::CheckedSRemSeq8 { divisor, .. } => { // Validate that the register constraints of the dividend and the @@ -2096,7 +2075,7 @@ fn emit_maybe_shrink(inst: &AsmInst, sink: &mut impl asm::CodeSink) { m32, sink, |dst, amode, s| leal_rm::::new(dst, amode).encode(s), - |dst, simm32, s| addl_mi::::new(dst, simm32.unsigned()).encode(s), + |dst, simm32, s| addl_mi::::new(dst, simm32.cast_unsigned()).encode(s), |dst, reg, s| addl_rm::::new(dst, reg).encode(s), ), Inst::leaq_rm(leaq_rm { r64, m64 }) => emit_lea( diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs index f2addf2354..a19df9a09a 100644 --- a/cranelift/codegen/src/isa/x64/inst/mod.rs +++ b/cranelift/codegen/src/isa/x64/inst/mod.rs @@ -13,7 +13,6 @@ use crate::{machinst::*, trace}; use alloc::boxed::Box; use core::slice; use cranelift_assembler_x64 as asm; -use cranelift_entity::{Signed, Unsigned}; use smallvec::{SmallVec, smallvec}; use std::fmt::{self, Write}; use std::string::{String, ToString}; @@ -63,14 +62,16 @@ fn inst_size_test() { } impl Inst { - /// Retrieve a list of ISA feature sets in which the instruction is available. An empty list - /// indicates that the instruction is available in the baseline feature set (i.e. SSE2 and - /// below); more than one `InstructionSet` in the list indicates that the instruction is present - /// *any* of the included ISA feature sets. - fn available_in_any_isa(&self) -> SmallVec<[InstructionSet; 2]> { + /// Check if the instruction (or pseudo-instruction) can be emitted given + /// the current target architecture given by `emit_info`. For non-assembler + /// instructions, this assumes a baseline feature set (i.e., 64-bit AND SSE2 + /// and below). + fn is_available(&self, emit_info: &EmitInfo) -> bool { + use asm::AvailableFeatures; + match self { - // These instructions are part of SSE2, which is a basic requirement in Cranelift, and - // don't have to be checked. + // These instructions are part of SSE2, which is a basic requirement + // in Cranelift, and don't have to be checked. Inst::AtomicRmwSeq { .. } | Inst::CallKnown { .. } | Inst::CallUnknown { .. } @@ -104,41 +105,11 @@ impl Inst { | Inst::MachOTlsGetAddr { .. } | Inst::CoffTlsGetAddr { .. } | Inst::Unwind { .. } - | Inst::DummyUse { .. } => smallvec![], + | Inst::DummyUse { .. } => true, - Inst::Atomic128RmwSeq { .. } | Inst::Atomic128XchgSeq { .. } => { - smallvec![InstructionSet::CMPXCHG16b] - } + Inst::Atomic128RmwSeq { .. } | Inst::Atomic128XchgSeq { .. } => emit_info.cmpxchg16b(), - Inst::External { inst } => { - use cranelift_assembler_x64::Feature::*; - let mut features = smallvec![]; - for f in inst.features() { - match f { - _64b | compat => {} - sse => features.push(InstructionSet::SSE), - sse2 => features.push(InstructionSet::SSE2), - sse3 => features.push(InstructionSet::SSE3), - ssse3 => features.push(InstructionSet::SSSE3), - sse41 => features.push(InstructionSet::SSE41), - sse42 => features.push(InstructionSet::SSE42), - bmi1 => features.push(InstructionSet::BMI1), - bmi2 => features.push(InstructionSet::BMI2), - lzcnt => features.push(InstructionSet::Lzcnt), - popcnt => features.push(InstructionSet::Popcnt), - avx => features.push(InstructionSet::AVX), - avx2 => features.push(InstructionSet::AVX2), - avx512f => features.push(InstructionSet::AVX512F), - avx512vl => features.push(InstructionSet::AVX512VL), - avx512dq => features.push(InstructionSet::AVX512DQ), - avx512bitalg => features.push(InstructionSet::AVX512BITALG), - avx512vbmi => features.push(InstructionSet::AVX512VBMI), - cmpxchg16b => features.push(InstructionSet::CMPXCHG16b), - fma => features.push(InstructionSet::FMA), - } - } - features - } + Inst::External { inst } => inst.is_available(&emit_info), } } } @@ -194,7 +165,7 @@ impl Inst { // If `simm64` is zero-extended use `movl` which zeros the // upper bits. Ok(imm32) => asm::inst::movl_oi::new(dst, imm32).into(), - _ => match i32::try_from(simm64.signed()) { + _ => match i32::try_from(simm64.cast_signed()) { // If `simm64` is sign-extended use `movq` which sign the // upper bits. Ok(simm32) => asm::inst::movq_mi_sxl::new(dst, simm32).into(), @@ -263,7 +234,7 @@ impl Inst { /// Compares `src1` against `src2` pub(crate) fn cmp_mi_sxb(size: OperandSize, src1: Gpr, src2: i8) -> Inst { let inst = match size { - OperandSize::Size8 => asm::inst::cmpb_mi::new(src1, src2.unsigned()).into(), + OperandSize::Size8 => asm::inst::cmpb_mi::new(src1, src2.cast_unsigned()).into(), OperandSize::Size16 => asm::inst::cmpw_mi_sxb::new(src1, src2).into(), OperandSize::Size32 => asm::inst::cmpl_mi_sxb::new(src1, src2).into(), OperandSize::Size64 => asm::inst::cmpq_mi_sxb::new(src1, src2).into(), @@ -1478,6 +1449,97 @@ impl EmitInfo { } } +impl asm::AvailableFeatures for &EmitInfo { + fn _64b(&self) -> bool { + // Currently, this x64 backend always assumes 64-bit mode. + true + } + + fn compat(&self) -> bool { + // For 32-bit compatibility mode, see + // https://github.com/bytecodealliance/wasmtime/issues/1980 (TODO). + false + } + + fn sse(&self) -> bool { + // Currently, this x64 backend always assumes SSE. + true + } + + fn sse2(&self) -> bool { + // Currently, this x64 backend always assumes SSE2. + true + } + + fn sse3(&self) -> bool { + self.isa_flags.has_sse3() + } + + fn ssse3(&self) -> bool { + self.isa_flags.has_ssse3() + } + + fn sse41(&self) -> bool { + self.isa_flags.has_sse41() + } + + fn sse42(&self) -> bool { + self.isa_flags.has_sse42() + } + + fn bmi1(&self) -> bool { + self.isa_flags.has_bmi1() + } + + fn bmi2(&self) -> bool { + self.isa_flags.has_bmi2() + } + + fn lzcnt(&self) -> bool { + self.isa_flags.has_lzcnt() + } + + fn popcnt(&self) -> bool { + self.isa_flags.has_popcnt() + } + + fn avx(&self) -> bool { + self.isa_flags.has_avx() + } + + fn avx2(&self) -> bool { + self.isa_flags.has_avx2() + } + + fn avx512f(&self) -> bool { + self.isa_flags.has_avx512f() + } + + fn avx512vl(&self) -> bool { + self.isa_flags.has_avx512vl() + } + + fn cmpxchg16b(&self) -> bool { + self.isa_flags.has_cmpxchg16b() + } + + fn fma(&self) -> bool { + self.isa_flags.has_fma() + } + + fn avx512dq(&self) -> bool { + self.isa_flags.has_avx512dq() + } + + fn avx512bitalg(&self) -> bool { + self.isa_flags.has_avx512bitalg() + } + + fn avx512vbmi(&self) -> bool { + self.isa_flags.has_avx512vbmi() + } +} + impl MachInstEmit for Inst { type State = EmitState; type Info = EmitInfo; diff --git a/cranelift/codegen/src/isa/x64/lower/isle.rs b/cranelift/codegen/src/isa/x64/lower/isle.rs index 60a5ccd3d9..823656d890 100644 --- a/cranelift/codegen/src/isa/x64/lower/isle.rs +++ b/cranelift/codegen/src/isa/x64/lower/isle.rs @@ -24,7 +24,6 @@ use crate::machinst::{ }; use alloc::vec::Vec; use cranelift_assembler_x64 as asm; -use cranelift_entity::{Signed, Unsigned}; use regalloc2::PReg; use std::boxed::Box; @@ -173,7 +172,7 @@ impl Context for IsleContext<'_, '_, MInst, X64Backend> { if let Some(imm) = self.i64_from_iconst(val) { if let Ok(imm) = i32::try_from(imm) { return RegMemImm::Imm { - simm32: imm.unsigned(), + simm32: imm.cast_unsigned(), }; } } @@ -185,7 +184,7 @@ impl Context for IsleContext<'_, '_, MInst, X64Backend> { if let Some(imm) = self.i64_from_iconst(val) { if let Ok(imm) = i32::try_from(imm) { return XmmMemImm::unwrap_new(RegMemImm::Imm { - simm32: imm.unsigned(), + simm32: imm.cast_unsigned(), }); } } @@ -344,7 +343,7 @@ impl Context for IsleContext<'_, '_, MInst, X64Backend> { fn simm32_from_value(&mut self, val: Value) -> Option { let imm = self.i64_from_iconst(val)?; Some(GprMemImm::unwrap_new(RegMemImm::Imm { - simm32: i32::try_from(imm).ok()?.unsigned(), + simm32: i32::try_from(imm).ok()?.cast_unsigned(), })) } @@ -975,35 +974,41 @@ impl Context for IsleContext<'_, '_, MInst, X64Backend> { fn is_imm8(&mut self, src: &GprMemImm) -> Option { match src.clone().to_reg_mem_imm() { - RegMemImm::Imm { simm32 } => Some(i8::try_from(simm32.signed()).ok()?.unsigned()), + RegMemImm::Imm { simm32 } => { + Some(i8::try_from(simm32.cast_signed()).ok()?.cast_unsigned()) + } _ => None, } } fn is_imm8_xmm(&mut self, src: &XmmMemImm) -> Option { match src.clone().to_reg_mem_imm() { - RegMemImm::Imm { simm32 } => Some(i8::try_from(simm32.signed()).ok()?.unsigned()), + RegMemImm::Imm { simm32 } => { + Some(i8::try_from(simm32.cast_signed()).ok()?.cast_unsigned()) + } _ => None, } } fn is_simm8(&mut self, src: &GprMemImm) -> Option { match src.clone().to_reg_mem_imm() { - RegMemImm::Imm { simm32 } => Some(i8::try_from(simm32.signed()).ok()?), + RegMemImm::Imm { simm32 } => Some(i8::try_from(simm32.cast_signed()).ok()?), _ => None, } } fn is_imm16(&mut self, src: &GprMemImm) -> Option { match src.clone().to_reg_mem_imm() { - RegMemImm::Imm { simm32 } => Some(i16::try_from(simm32.signed()).ok()?.unsigned()), + RegMemImm::Imm { simm32 } => { + Some(i16::try_from(simm32.cast_signed()).ok()?.cast_unsigned()) + } _ => None, } } fn is_simm16(&mut self, src: &GprMemImm) -> Option { match src.clone().to_reg_mem_imm() { - RegMemImm::Imm { simm32 } => Some(i16::try_from(simm32.signed()).ok()?), + RegMemImm::Imm { simm32 } => Some(i16::try_from(simm32.cast_signed()).ok()?), _ => None, } } diff --git a/cranelift/codegen/src/isa/x64/settings.rs b/cranelift/codegen/src/isa/x64/settings.rs index 98a64ddb6e..12420311b3 100644 --- a/cranelift/codegen/src/isa/x64/settings.rs +++ b/cranelift/codegen/src/isa/x64/settings.rs @@ -3,7 +3,7 @@ use crate::settings::{self, Builder, Value, detail}; use core::fmt; -// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a +// Include code generated by `cranelift/codegen/meta/src/gen_settings.rs:`. This file contains a // public `Flags` struct with an impl for all of the settings defined in -// `cranelift-codegen/meta/src/isa/x86/settings.rs`. +// `cranelift/codegen/meta/src/isa/x86/settings.rs`. include!(concat!(env!("OUT_DIR"), "/settings-x86.rs")); diff --git a/cranelift/control/Cargo.toml b/cranelift/control/Cargo.toml index cfec3369f0..ca40afe89c 100644 --- a/cranelift/control/Cargo.toml +++ b/cranelift/control/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-control" -version = "0.123.0" +version = "0.124.0" description = "White-box fuzz testing framework" license = "Apache-2.0 WITH LLVM-exception" repository = "https://github.com/bytecodealliance/wasmtime" diff --git a/cranelift/entity/Cargo.toml b/cranelift/entity/Cargo.toml index db3119844d..6bae4b8082 100644 --- a/cranelift/entity/Cargo.toml +++ b/cranelift/entity/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-entity" -version = "0.123.0" +version = "0.124.0" description = "Data structures using entity references as mapping keys" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://docs.rs/cranelift-entity" diff --git a/cranelift/entity/src/lib.rs b/cranelift/entity/src/lib.rs index 5701d22989..f412b277be 100644 --- a/cranelift/entity/src/lib.rs +++ b/cranelift/entity/src/lib.rs @@ -277,9 +277,7 @@ mod list; mod map; mod primary; mod set; -mod signed; mod sparse; -mod unsigned; pub use self::boxed_slice::BoxedSlice; pub use self::iter::{Iter, IterMut}; @@ -288,9 +286,7 @@ pub use self::list::{EntityList, ListPool}; pub use self::map::SecondaryMap; pub use self::primary::PrimaryMap; pub use self::set::{EntitySet, SetIter}; -pub use self::signed::Signed; pub use self::sparse::{SparseMap, SparseMapValue, SparseSet}; -pub use self::unsigned::Unsigned; /// A collection of tests to ensure that use of the different `entity_impl!` forms will generate /// `EntityRef` implementations that behave the same way. diff --git a/cranelift/entity/src/set.rs b/cranelift/entity/src/set.rs index 8129fef4de..f46fb95148 100644 --- a/cranelift/entity/src/set.rs +++ b/cranelift/entity/src/set.rs @@ -23,9 +23,9 @@ where unused: PhantomData, } -impl fmt::Debug for EntitySet +impl fmt::Debug for EntitySet where - K: EntityRef, + K: fmt::Debug + EntityRef, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_set().entries(self.keys()).finish() diff --git a/cranelift/entity/src/signed.rs b/cranelift/entity/src/signed.rs deleted file mode 100644 index 3c136d5901..0000000000 --- a/cranelift/entity/src/signed.rs +++ /dev/null @@ -1,40 +0,0 @@ -/// Helper trait used to add `signed()` methods to primitive unsigned integer -/// types. -/// -/// The purpose of this trait is to signal the intent that the sign bit of an -/// unsigned integer is intended to be discarded and the value is instead -/// understood to be a "bag of bits" where the conversion to a signed number -/// is intended to be lossless bit-wise. This can be used for example when -/// converting an unsigned integer into a signed integer for constrained reasons -/// outside the scope of the code in question. -pub trait Signed { - /// The signed integer for this type which has the same width. - type Signed; - - /// View this unsigned integer as a signed integer of the same width. - /// - /// All bits are preserved. - fn signed(self) -> Self::Signed; -} - -macro_rules! impls { - ($($unsigned:ident => $signed:ident)*) => {$( - impl Signed for $unsigned { - type Signed = $signed; - - #[inline] - fn signed(self) -> $signed { - self as $signed - } - } - )*} -} - -impls! { - u8 => i8 - u16 => i16 - u32 => i32 - u64 => i64 - u128 => i128 - usize => isize -} diff --git a/cranelift/entity/src/unsigned.rs b/cranelift/entity/src/unsigned.rs deleted file mode 100644 index 2d8eb4c640..0000000000 --- a/cranelift/entity/src/unsigned.rs +++ /dev/null @@ -1,71 +0,0 @@ -/// Helper trait used to add `unsigned()` methods to primitive signed integer -/// types. -/// -/// The purpose of this trait is to signal the intent that the sign bit of a -/// signed integer is intended to be discarded and the value is instead -/// understood to be a "bag of bits" where the conversion to an unsigned number -/// is intended to be lossless. This can be used for example when converting a -/// signed integer into a larger width with zero-extension. -pub trait Unsigned { - /// The unsigned integer for this type which has the same width. - type Unsigned; - - /// View this signed integer as an unsigned integer of the same width. - /// - /// All bits are preserved. - fn unsigned(self) -> Self::Unsigned; -} - -impl Unsigned for i8 { - type Unsigned = u8; - - #[inline] - fn unsigned(self) -> u8 { - self as u8 - } -} - -impl Unsigned for i16 { - type Unsigned = u16; - - #[inline] - fn unsigned(self) -> u16 { - self as u16 - } -} - -impl Unsigned for i32 { - type Unsigned = u32; - - #[inline] - fn unsigned(self) -> u32 { - self as u32 - } -} - -impl Unsigned for i64 { - type Unsigned = u64; - - #[inline] - fn unsigned(self) -> u64 { - self as u64 - } -} - -impl Unsigned for i128 { - type Unsigned = u128; - - #[inline] - fn unsigned(self) -> u128 { - self as u128 - } -} - -impl Unsigned for isize { - type Unsigned = usize; - - #[inline] - fn unsigned(self) -> usize { - self as usize - } -} diff --git a/cranelift/filetests/filetests/inline/user-external-name-refs.clif b/cranelift/filetests/filetests/inline/user-external-name-refs.clif new file mode 100644 index 0000000000..e517a72e75 --- /dev/null +++ b/cranelift/filetests/filetests/inline/user-external-name-refs.clif @@ -0,0 +1,55 @@ +test inline precise-output +target x86_64 + +function %callee() -> i32 { + sig0 = () -> i32 + fn0 = colocated u0:36 sig0 + fn1 = colocated u0:42 sig0 +block0: + v0 = call fn0() + v1 = call fn1() + v2 = iadd v0, v1 + return v2 +} + +; (no functions inlined into %callee) + +function %caller(i32) -> i32 { + sig0 = (i32) -> i32 + sig1 = () -> i32 + fn0 = colocated u0:1234 sig0 + fn1 = colocated u0:36 sig0 + fn2 = %callee sig1 +block0(v0: i32): + v1 = call fn0(v0) + v2 = call fn2() + v3 = call fn1(v1) + return v3 +} + +; function %caller(i32) -> i32 fast { +; sig0 = (i32) -> i32 fast +; sig1 = () -> i32 fast +; sig2 = () -> i32 fast +; fn0 = colocated u0:1234 sig0 +; fn1 = colocated u0:36 sig0 +; fn2 = %callee sig1 +; fn3 = colocated u0:36 sig2 +; fn4 = colocated u0:42 sig2 +; +; block0(v0: i32): +; v1 = call fn0(v0) +; jump block1 +; +; block1: +; v5 = call fn3() +; v6 = call fn4() +; v7 = iadd v5, v6 +; jump block2(v7) +; +; block2(v4: i32): +; v2 -> v4 +; v3 = call fn1(v1) +; return v3 +; } + diff --git a/cranelift/filetests/src/function_runner.rs b/cranelift/filetests/src/function_runner.rs index f5ed5c24d6..92833cc5d0 100644 --- a/cranelift/filetests/src/function_runner.rs +++ b/cranelift/filetests/src/function_runner.rs @@ -1,5 +1,5 @@ //! Provides functionality for compiling and running CLIF IR for `run` tests. -use anyhow::{Result, anyhow}; +use anyhow::{Context as _, Result, anyhow}; use core::mem; use cranelift::prelude::Imm64; use cranelift_codegen::cursor::{Cursor, FuncCursor}; @@ -64,7 +64,9 @@ struct DefinedFunction { /// /// let code = "test run \n function %add(i32, i32) -> i32 { block0(v0:i32, v1:i32): v2 = iadd v0, v1 return v2 }".into(); /// let func = parse_functions(code).unwrap().into_iter().nth(0).unwrap(); -/// let mut compiler = TestFileCompiler::with_default_host_isa().unwrap(); +/// let Ok(mut compiler) = TestFileCompiler::with_default_host_isa() else { +/// return; +/// }; /// compiler.declare_function(&func).unwrap(); /// compiler.define_function(func.clone(), ctrl_plane).unwrap(); /// compiler.create_trampoline_for_function(&func, ctrl_plane).unwrap(); @@ -135,8 +137,9 @@ impl TestFileCompiler { /// Build a [TestFileCompiler] using the host machine's ISA and the passed flags. pub fn with_host_isa(flags: settings::Flags) -> Result { - let builder = - builder_with_options(true).expect("Unable to build a TargetIsa for the current host"); + let builder = builder_with_options(true) + .map_err(anyhow::Error::msg) + .context("Unable to build a TargetIsa for the current host")?; let isa = builder.finish(flags)?; Ok(Self::new(isa)) } diff --git a/cranelift/frontend/Cargo.toml b/cranelift/frontend/Cargo.toml index cb85fcb3fc..36baabfcc3 100644 --- a/cranelift/frontend/Cargo.toml +++ b/cranelift/frontend/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-frontend" -version = "0.123.0" +version = "0.124.0" description = "Cranelift IR builder helper" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://docs.rs/cranelift-frontend" diff --git a/cranelift/interpreter/Cargo.toml b/cranelift/interpreter/Cargo.toml index 3c37b2b183..077af9369e 100644 --- a/cranelift/interpreter/Cargo.toml +++ b/cranelift/interpreter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cranelift-interpreter" -version = "0.123.0" +version = "0.124.0" authors = ["The Cranelift Project Developers"] description = "Interpret Cranelift IR" repository = "https://github.com/bytecodealliance/wasmtime" diff --git a/cranelift/isle/isle/Cargo.toml b/cranelift/isle/isle/Cargo.toml index b561af3e90..51dbac1c03 100644 --- a/cranelift/isle/isle/Cargo.toml +++ b/cranelift/isle/isle/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0 WITH LLVM-exception" name = "cranelift-isle" readme = "../README.md" repository = "https://github.com/bytecodealliance/wasmtime/tree/main/cranelift/isle" -version = "0.123.0" +version = "0.124.0" [lints] workspace = true diff --git a/cranelift/jit/Cargo.toml b/cranelift/jit/Cargo.toml index aec18afa6f..6a1e28873d 100644 --- a/cranelift/jit/Cargo.toml +++ b/cranelift/jit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cranelift-jit" -version = "0.123.0" +version = "0.124.0" authors = ["The Cranelift Project Developers"] description = "A JIT library backed by Cranelift" repository = "https://github.com/bytecodealliance/wasmtime" diff --git a/cranelift/module/Cargo.toml b/cranelift/module/Cargo.toml index 7aceddf1e9..e2d446fa8a 100644 --- a/cranelift/module/Cargo.toml +++ b/cranelift/module/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cranelift-module" -version = "0.123.0" +version = "0.124.0" authors = ["The Cranelift Project Developers"] description = "Support for linking functions and data with Cranelift" repository = "https://github.com/bytecodealliance/wasmtime" diff --git a/cranelift/native/Cargo.toml b/cranelift/native/Cargo.toml index 191e248a9f..654ca16d91 100644 --- a/cranelift/native/Cargo.toml +++ b/cranelift/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cranelift-native" -version = "0.123.0" +version = "0.124.0" authors = ["The Cranelift Project Developers"] description = "Support for targeting the host with Cranelift" documentation = "https://docs.rs/cranelift-native" diff --git a/cranelift/object/Cargo.toml b/cranelift/object/Cargo.toml index d0d6478c08..55a7dc16d2 100644 --- a/cranelift/object/Cargo.toml +++ b/cranelift/object/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cranelift-object" -version = "0.123.0" +version = "0.124.0" authors = ["The Cranelift Project Developers"] description = "Emit Cranelift output to native object files with `object`" repository = "https://github.com/bytecodealliance/wasmtime" diff --git a/cranelift/reader/Cargo.toml b/cranelift/reader/Cargo.toml index b0ae1ce895..ebfae99b7b 100644 --- a/cranelift/reader/Cargo.toml +++ b/cranelift/reader/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-reader" -version = "0.123.0" +version = "0.124.0" description = "Cranelift textual IR reader" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://docs.rs/cranelift-reader" diff --git a/cranelift/serde/Cargo.toml b/cranelift/serde/Cargo.toml index 487ae5b89a..e34ef619ca 100644 --- a/cranelift/serde/Cargo.toml +++ b/cranelift/serde/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cranelift-serde" -version = "0.123.0" +version = "0.124.0" authors = ["The Cranelift Project Developers"] description = "Serializer/Deserializer for Cranelift IR" repository = "https://github.com/bytecodealliance/wasmtime" diff --git a/cranelift/srcgen/Cargo.toml b/cranelift/srcgen/Cargo.toml index 9eb514f7b5..6f73c5c877 100644 --- a/cranelift/srcgen/Cargo.toml +++ b/cranelift/srcgen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cranelift-srcgen" -version = "0.123.0" +version = "0.124.0" authors = ["The Wasmtime Project Developers"] description = "Helper functions for generating Rust and ISLE files" license = "Apache-2.0 WITH LLVM-exception" diff --git a/cranelift/umbrella/Cargo.toml b/cranelift/umbrella/Cargo.toml index f58367a32e..60d6de8e0e 100644 --- a/cranelift/umbrella/Cargo.toml +++ b/cranelift/umbrella/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift" -version = "0.123.0" +version = "0.124.0" description = "Umbrella for commonly-used cranelift crates" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://docs.rs/cranelift" diff --git a/crates/bench-api/Cargo.toml b/crates/bench-api/Cargo.toml index de288922ba..b88b7d1399 100644 --- a/crates/bench-api/Cargo.toml +++ b/crates/bench-api/Cargo.toml @@ -26,7 +26,7 @@ wasmtime = { workspace = true, default-features = true, features = ["winch", "pu wasmtime-cli-flags = { workspace = true, default-features = true, features = [ "cranelift", ] } -wasi-common = { workspace = true, default-features = true } +wasmtime-wasi = { workspace = true, features = ['p1'] } wasmtime-wasi-nn = { workspace = true, optional = true } cap-std = { workspace = true } clap = { workspace = true } diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index d2eebc18d9..86c99c8c98 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -141,9 +141,10 @@ use clap::Parser; use std::os::raw::{c_int, c_void}; use std::slice; use std::{env, path::PathBuf}; -use wasi_common::{I32Exit, WasiCtx, sync::WasiCtxBuilder}; use wasmtime::{Engine, Instance, Linker, Module, Store}; use wasmtime_cli_flags::CommonOptions; +use wasmtime_wasi::cli::{InputFile, OutputFile}; +use wasmtime_wasi::{DirPerms, FilePerms, I32Exit, WasiCtx, p1::WasiP1Ctx}; pub type ExitCode = c_int; pub const OK: ExitCode = 0; @@ -272,15 +273,6 @@ pub extern "C" fn wasm_bench_create( ) -> ExitCode { let result = (|| -> Result<_> { let working_dir = config.working_dir()?; - let working_dir = - cap_std::fs::Dir::open_ambient_dir(&working_dir, cap_std::ambient_authority()) - .with_context(|| { - format!( - "failed to preopen the working directory: {}", - working_dir.display(), - ) - })?; - let stdout_path = config.stdout_path()?; let stderr_path = config.stderr_path()?; let stdin_path = config.stdin_path()?; @@ -298,39 +290,33 @@ pub extern "C" fn wasm_bench_create( config.execution_start, config.execution_end, move || { - let mut cx = WasiCtxBuilder::new(); + let mut cx = WasiCtx::builder(); let stdout = std::fs::File::create(&stdout_path) .with_context(|| format!("failed to create {}", stdout_path.display()))?; - let stdout = cap_std::fs::File::from_std(stdout); - let stdout = wasi_common::sync::file::File::from_cap_std(stdout); - cx.stdout(Box::new(stdout)); + cx.stdout(OutputFile::new(stdout)); let stderr = std::fs::File::create(&stderr_path) .with_context(|| format!("failed to create {}", stderr_path.display()))?; - let stderr = cap_std::fs::File::from_std(stderr); - let stderr = wasi_common::sync::file::File::from_cap_std(stderr); - cx.stderr(Box::new(stderr)); + cx.stderr(OutputFile::new(stderr)); if let Some(stdin_path) = &stdin_path { let stdin = std::fs::File::open(stdin_path) .with_context(|| format!("failed to open {}", stdin_path.display()))?; - let stdin = cap_std::fs::File::from_std(stdin); - let stdin = wasi_common::sync::file::File::from_cap_std(stdin); - cx.stdin(Box::new(stdin)); + cx.stdin(InputFile::new(stdin)); } // Allow access to the working directory so that the benchmark can read // its input workload(s). - cx.preopened_dir(working_dir.try_clone()?, ".")?; + cx.preopened_dir(working_dir.clone(), ".", DirPerms::READ, FilePerms::READ)?; // Pass this env var along so that the benchmark program can use smaller // input workload(s) if it has them and that has been requested. if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") { - cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val)?; + cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val); } - Ok(cx.build()) + Ok(cx.build_p1()) }, )?); Ok(Box::into_raw(state) as _) @@ -407,7 +393,7 @@ struct BenchState { instantiation_timer: *mut u8, instantiation_start: extern "C" fn(*mut u8), instantiation_end: extern "C" fn(*mut u8), - make_wasi_cx: Box Result>, + make_wasi_cx: Box Result>, module: Option, store_and_instance: Option<(Store, Instance)>, epoch_interruption: bool, @@ -415,7 +401,7 @@ struct BenchState { } struct HostState { - wasi: WasiCtx, + wasi: WasiP1Ctx, #[cfg(feature = "wasi-nn")] wasi_nn: wasmtime_wasi_nn::witx::WasiNnCtx, } @@ -432,7 +418,7 @@ impl BenchState { execution_timer: *mut u8, execution_start: extern "C" fn(*mut u8), execution_end: extern "C" fn(*mut u8), - make_wasi_cx: impl FnMut() -> Result + 'static, + make_wasi_cx: impl FnMut() -> Result + 'static, ) -> Result { let mut config = options.config(None)?; // NB: always disable the compilation cache. @@ -459,7 +445,7 @@ impl BenchState { let fuel = options.wasm.fuel; if options.wasi.common != Some(false) { - wasi_common::sync::add_to_linker(&mut linker, |cx| &mut cx.wasi)?; + wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |cx| &mut cx.wasi)?; } #[cfg(feature = "wasi-nn")] diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index 4d80b85c40..c7d11668d7 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -33,7 +33,7 @@ wat = { workspace = true, optional = true } # Optional dependencies for the `wasi` feature cap-std = { workspace = true, optional = true } tokio = { workspace = true, optional = true, features = ["fs"] } -wasmtime-wasi = { workspace = true, optional = true, features = ["preview1"] } +wasmtime-wasi = { workspace = true, optional = true, features = ["p1"] } # Optional dependencies for the `async` feature futures = { workspace = true, optional = true } diff --git a/crates/c-api/include/wasmtime.h b/crates/c-api/include/wasmtime.h index 1e8dca2c74..c98f5cefb1 100644 --- a/crates/c-api/include/wasmtime.h +++ b/crates/c-api/include/wasmtime.h @@ -213,11 +213,11 @@ /** * \brief Wasmtime version string. */ -#define WASMTIME_VERSION "36.0.0" +#define WASMTIME_VERSION "37.0.0" /** * \brief Wasmtime major version number. */ -#define WASMTIME_VERSION_MAJOR 36 +#define WASMTIME_VERSION_MAJOR 37 /** * \brief Wasmtime minor version number. */ diff --git a/crates/c-api/src/linker.rs b/crates/c-api/src/linker.rs index fb77542e8d..c564fac549 100644 --- a/crates/c-api/src/linker.rs +++ b/crates/c-api/src/linker.rs @@ -112,7 +112,7 @@ pub extern "C" fn wasmtime_linker_define_wasi( linker: &mut wasmtime_linker_t, ) -> Option> { handle_result( - wasmtime_wasi::preview1::add_to_linker_sync(&mut linker.linker, |ctx| { + wasmtime_wasi::p1::add_to_linker_sync(&mut linker.linker, |ctx| { ctx.wasi.as_mut().expect("wasi context must be populated") }), |_linker| (), diff --git a/crates/c-api/src/store.rs b/crates/c-api/src/store.rs index 0c3246b4f4..6381e1b243 100644 --- a/crates/c-api/src/store.rs +++ b/crates/c-api/src/store.rs @@ -83,7 +83,7 @@ wasmtime_c_api_macros::declare_own!(wasmtime_store_t); pub struct WasmtimeStoreData { foreign: crate::ForeignData, #[cfg(feature = "wasi")] - pub(crate) wasi: Option, + pub(crate) wasi: Option, /// Temporary storage for usage during a wasm->host call to store values /// in a slice we pass to the C API. @@ -100,20 +100,16 @@ pub struct WasmtimeStoreData { pub(crate) resource_table: wasmtime::component::ResourceTable, #[cfg(all(feature = "component-model", feature = "wasi"))] - pub(crate) wasip2: Option, + pub(crate) wasip2: Option, } #[cfg(all(feature = "component-model", feature = "wasi"))] -impl wasmtime_wasi::p2::IoView for WasmtimeStoreData { - fn table(&mut self) -> &mut wasmtime_wasi::ResourceTable { - &mut self.resource_table - } -} - -#[cfg(all(feature = "component-model", feature = "wasi"))] -impl wasmtime_wasi::p2::WasiView for WasmtimeStoreData { - fn ctx(&mut self) -> &mut wasmtime_wasi::p2::WasiCtx { - self.wasip2.as_mut().unwrap() +impl wasmtime_wasi::WasiView for WasmtimeStoreData { + fn ctx(&mut self) -> wasmtime_wasi::WasiCtxView<'_> { + wasmtime_wasi::WasiCtxView { + ctx: self.wasip2.as_mut().unwrap(), + table: &mut self.resource_table, + } } } diff --git a/crates/c-api/src/wasi.rs b/crates/c-api/src/wasi.rs index b11c993a76..2899d9312b 100644 --- a/crates/c-api/src/wasi.rs +++ b/crates/c-api/src/wasi.rs @@ -6,8 +6,8 @@ use std::ffi::{CStr, c_char}; use std::fs::File; use std::path::Path; use std::slice; -use wasmtime_wasi::p2::WasiCtxBuilder; -use wasmtime_wasi::preview1::WasiP1Ctx; +use wasmtime_wasi::WasiCtxBuilder; +use wasmtime_wasi::p1::WasiP1Ctx; unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> { CStr::from_ptr(path).to_str().map(Path::new).ok() @@ -106,9 +106,7 @@ pub unsafe extern "C" fn wasi_config_set_stdin_file( }; let file = tokio::fs::File::from_std(file); - let stdin_stream = wasmtime_wasi::p2::AsyncStdinStream::new( - wasmtime_wasi::p2::pipe::AsyncReadStream::new(file), - ); + let stdin_stream = wasmtime_wasi::cli::AsyncStdinStream::new(file); config.builder.stdin(stdin_stream); true @@ -141,7 +139,7 @@ pub unsafe extern "C" fn wasi_config_set_stdout_file( config .builder - .stdout(wasmtime_wasi::p2::OutputFile::new(file)); + .stdout(wasmtime_wasi::cli::OutputFile::new(file)); true } @@ -163,7 +161,7 @@ pub unsafe extern "C" fn wasi_config_set_stderr_file( config .builder - .stderr(wasmtime_wasi::p2::OutputFile::new(file)); + .stderr(wasmtime_wasi::cli::OutputFile::new(file)); true } diff --git a/crates/c-api/src/wasip2.rs b/crates/c-api/src/wasip2.rs index 27ac78604a..0806810e20 100644 --- a/crates/c-api/src/wasip2.rs +++ b/crates/c-api/src/wasip2.rs @@ -1,12 +1,12 @@ #[repr(transparent)] pub struct wasmtime_wasip2_config_t { - pub(crate) builder: wasmtime_wasi::p2::WasiCtxBuilder, + pub(crate) builder: wasmtime_wasi::WasiCtxBuilder, } #[unsafe(no_mangle)] pub unsafe extern "C" fn wasmtime_wasip2_config_new() -> Box { Box::new(wasmtime_wasip2_config_t { - builder: wasmtime_wasi::p2::WasiCtxBuilder::new(), + builder: wasmtime_wasi::WasiCtxBuilder::new(), }) } diff --git a/crates/cache/src/config.rs b/crates/cache/src/config.rs index 60b78885de..fda6790ab4 100644 --- a/crates/cache/src/config.rs +++ b/crates/cache/src/config.rs @@ -305,14 +305,9 @@ generate_deserializer!(deserialize_percent(num: u8, unit: &str) -> u8 { } }); -static CACHE_IMPROPER_CONFIG_ERROR_MSG: &str = - "Cache system should be enabled and all settings must be validated or defaulted"; - macro_rules! generate_setting_getter { ($setting:ident: $setting_type:ty) => { #[doc = concat!("Returns ", "`", stringify!($setting), "`.")] - /// - /// Panics if the cache is disabled. pub fn $setting(&self) -> $setting_type { self.$setting } @@ -320,7 +315,7 @@ macro_rules! generate_setting_getter { } impl CacheConfig { - /// Creates a new set of configuration which represents a disabled cache + /// Creates a cache configuration with default settings. pub fn new() -> Self { Self::default() } @@ -385,13 +380,9 @@ impl CacheConfig { generate_setting_getter!(file_count_limit_percent_if_deleting: u8); generate_setting_getter!(files_total_size_limit_percent_if_deleting: u8); - /// Returns path to the cache directory. - /// - /// Panics if the cache is disabled. - pub fn directory(&self) -> &PathBuf { - self.directory - .as_ref() - .expect(CACHE_IMPROPER_CONFIG_ERROR_MSG) + /// Returns path to the cache directory if one is set. + pub fn directory(&self) -> Option<&PathBuf> { + self.directory.as_ref() } /// Specify where the cache directory is. Must be an absolute path. diff --git a/crates/cache/src/config/tests.rs b/crates/cache/src/config/tests.rs index 45e50edbe4..a605c9e216 100644 --- a/crates/cache/src/config/tests.rs +++ b/crates/cache/src/config/tests.rs @@ -99,7 +99,7 @@ fn test_all_settings() { fn check_conf(conf: &CacheConfig, cd: &PathBuf) { assert_eq!( conf.directory(), - &fs::canonicalize(cd).expect("canonicalize failed") + Some(&fs::canonicalize(cd).expect("canonicalize failed")) ); assert_eq!(conf.worker_event_queue_size(), 0x10); assert_eq!(conf.baseline_compression_level(), 3); @@ -536,7 +536,7 @@ fn test_builder_all_settings() { fn check_conf(conf: &CacheConfig, cd: &PathBuf) { assert_eq!( conf.directory(), - &fs::canonicalize(cd).expect("canonicalize failed") + Some(&fs::canonicalize(cd).expect("canonicalize failed")) ); assert_eq!(conf.worker_event_queue_size(), 0x10); assert_eq!(conf.baseline_compression_level(), 3); diff --git a/crates/cache/src/lib.rs b/crates/cache/src/lib.rs index cd29e04433..8c22ecc19e 100644 --- a/crates/cache/src/lib.rs +++ b/crates/cache/src/lib.rs @@ -37,8 +37,6 @@ pub struct Cache { macro_rules! generate_config_setting_getter { ($setting:ident: $setting_type:ty) => { #[doc = concat!("Returns ", "`", stringify!($setting), "`.")] - /// - /// Panics if the cache is disabled. pub fn $setting(&self) -> $setting_type { self.config.$setting() } @@ -97,10 +95,11 @@ impl Cache { generate_config_setting_getter!(files_total_size_limit_percent_if_deleting: u8); /// Returns path to the cache directory. - /// - /// Panics if the cache directory is not set. pub fn directory(&self) -> &PathBuf { - &self.config.directory() + &self + .config + .directory() + .expect("directory should be validated in Config::new") } #[cfg(test)] diff --git a/crates/cache/src/tests.rs b/crates/cache/src/tests.rs index a8a8c9169b..a31126615c 100644 --- a/crates/cache/src/tests.rs +++ b/crates/cache/src/tests.rs @@ -24,8 +24,8 @@ fn test_cache_init() { // assumption: config init creates cache directory and returns canonicalized path assert_eq!( - *cache_config.directory(), - fs::canonicalize(cache_dir).unwrap() + cache_config.directory(), + Some(&fs::canonicalize(cache_dir).unwrap()) ); assert_eq!( cache_config.baseline_compression_level(), @@ -53,8 +53,8 @@ fn test_write_read_cache() { // assumption: config load creates cache directory and returns canonicalized path assert_eq!( - *cache_config.directory(), - fs::canonicalize(cache_dir).unwrap() + cache_config.directory(), + Some(&fs::canonicalize(cache_dir).unwrap()) ); let compiler1 = "test-1"; diff --git a/crates/cache/src/worker.rs b/crates/cache/src/worker.rs index 3272cb8094..dd1fdc32fc 100644 --- a/crates/cache/src/worker.rs +++ b/crates/cache/src/worker.rs @@ -393,6 +393,12 @@ impl WorkerThread { trace!("Task finished: recompress file: {}", path.display()); } + fn directory(&self) -> &PathBuf { + self.cache_config + .directory() + .expect("CacheConfig should be validated before being passed to a WorkerThread") + } + fn handle_on_cache_update(&self, path: PathBuf) { trace!("handle_on_cache_update() for path: {}", path.display()); @@ -416,7 +422,7 @@ impl WorkerThread { // acquire lock for cleanup task // Lock is a proof of recent cleanup task, so we don't want to delete them. // Expired locks will be deleted by the cleanup task. - let cleanup_file = self.cache_config.directory().join(".cleanup"); // some non existing marker file + let cleanup_file = self.directory().join(".cleanup"); // some non existing marker file if acquire_task_fs_lock( &cleanup_file, self.cache_config.cleanup_interval(), @@ -722,12 +728,7 @@ impl WorkerThread { } let mut vec = Vec::new(); - enter_dir( - &mut vec, - self.cache_config.directory(), - 0, - &self.cache_config, - ); + enter_dir(&mut vec, self.directory(), 0, &self.cache_config); vec } } diff --git a/crates/cranelift/src/bounds_checks.rs b/crates/cranelift/src/bounds_checks.rs index ad00757bb8..c0dc9ad664 100644 --- a/crates/cranelift/src/bounds_checks.rs +++ b/crates/cranelift/src/bounds_checks.rs @@ -31,7 +31,6 @@ use cranelift_codegen::{ ir::{Expr, Fact}, }; use cranelift_frontend::FunctionBuilder; -use wasmtime_environ::Unsigned; /// The kind of bounds check to perform when accessing a Wasm linear memory or /// GC heap. @@ -961,7 +960,7 @@ fn statically_in_bounds( _ => return None, }; let ty = func.dfg.value_type(index); - let index = imm.zero_extend_from_width(ty.bits()).bits().unsigned(); + let index = imm.zero_extend_from_width(ty.bits()).bits().cast_unsigned(); let final_addr = index.checked_add(offset_and_size)?; Some(final_addr <= heap.memory.minimum_byte_size().unwrap_or(u64::MAX)) }) diff --git a/crates/cranelift/src/debug/transform/address_transform.rs b/crates/cranelift/src/debug/transform/address_transform.rs index f7a2b82830..9d84b4ce20 100644 --- a/crates/cranelift/src/debug/transform/address_transform.rs +++ b/crates/cranelift/src/debug/transform/address_transform.rs @@ -552,8 +552,10 @@ impl AddressTransform { } fn translate_raw(&self, addr: u64) -> Option<(usize, GeneratedAddress)> { - if addr == 0 { - // It's normally 0 for debug info without the linked code. + const TOMBSTONE: u64 = u32::MAX as u64; + if addr == 0 || addr == TOMBSTONE { + // Addresses for unlinked code may be left as 0 or replaced + // with -1, depending on the linker used. return None; } if let Some(func) = self.find_func(addr) { diff --git a/crates/cranelift/src/debug/transform/unit.rs b/crates/cranelift/src/debug/transform/unit.rs index 94411f09b1..3bb5c0855d 100644 --- a/crates/cranelift/src/debug/transform/unit.rs +++ b/crates/cranelift/src/debug/transform/unit.rs @@ -245,15 +245,6 @@ fn replace_pointer_type( Ok(wrapper_die_id) } -fn is_dead_code(entry: &DebuggingInformationEntry>) -> bool { - const TOMBSTONE: u64 = u32::MAX as u64; - - match entry.attr_value(gimli::DW_AT_low_pc) { - Ok(Some(AttributeValue::Addr(addr))) => addr == TOMBSTONE, - _ => false, - } -} - pub(crate) fn clone_unit( compilation: &mut Compilation<'_>, module: StaticModuleIndex, @@ -387,7 +378,6 @@ pub(crate) fn clone_unit( if !context .reachable .contains(&entry.offset().to_unit_section_offset(&unit)) - || is_dead_code(&entry) { // entry is not reachable: discarding all its info. // Here B = C so `depth` is 0. A is the previous node so `cached` = diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 63a4a3c9c1..5586da114d 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -22,11 +22,11 @@ use smallvec::SmallVec; use std::mem; use wasmparser::{Operator, WasmFeatures}; use wasmtime_environ::{ - BuiltinFunctionIndex, DataIndex, ElemIndex, EngineOrModuleTypeIndex, FuncIndex, GlobalIndex, - IndexType, Memory, MemoryIndex, Module, ModuleInternedTypeIndex, ModuleTranslation, - ModuleTypesBuilder, PtrSize, Table, TableIndex, TripleExt, Tunables, TypeConvert, TypeIndex, - VMOffsets, WasmCompositeInnerType, WasmFuncType, WasmHeapTopType, WasmHeapType, WasmRefType, - WasmResult, WasmValType, + BuiltinFunctionIndex, DataIndex, DefinedFuncIndex, ElemIndex, EngineOrModuleTypeIndex, + FuncIndex, GlobalIndex, IndexType, Memory, MemoryIndex, Module, ModuleInternedTypeIndex, + ModuleTranslation, ModuleTypesBuilder, PtrSize, Table, TableIndex, TripleExt, Tunables, + TypeConvert, TypeIndex, VMOffsets, WasmCompositeInnerType, WasmFuncType, WasmHeapTopType, + WasmHeapType, WasmRefType, WasmResult, WasmValType, }; use wasmtime_environ::{FUNCREF_INIT_BIT, FUNCREF_MASK}; use wasmtime_math::f64_cvt_to_int_bounds; @@ -1176,9 +1176,14 @@ pub(crate) struct WasmEntities { /// `ir::SigRef` in the Cranelift function we are building. pub(crate) sig_refs: SecondaryMap>, - /// Map from a Wasm function index to its associated function reference in - /// the Cranelift function we are building. - pub(crate) func_refs: SecondaryMap>, + /// Map from a defined Wasm function index to its associated function + /// reference in the Cranelift function we are building. + pub(crate) defined_func_refs: SecondaryMap>, + + /// Map from an imported Wasm function index for which we statically know + /// which function will always be used to satisfy that import to its + /// associated function reference in the Cranelift function we are building. + pub(crate) imported_func_refs: SecondaryMap>, /// Map from a Wasm table index to its associated implementation in the /// Cranelift function we are building. @@ -1207,7 +1212,8 @@ impl FuncEnvironment<'_> { get_or_create_global(globals) : make_global : GlobalIndex => GlobalVariable; get_or_create_heap(memories) : make_heap : MemoryIndex => Heap; get_or_create_interned_sig_ref(sig_refs) : make_sig_ref : ModuleInternedTypeIndex => ir::SigRef; - get_or_create_func_ref(func_refs) : make_func_ref : FuncIndex => ir::FuncRef; + get_or_create_defined_func_ref(defined_func_refs) : make_defined_func_ref : DefinedFuncIndex => ir::FuncRef; + get_or_create_imported_func_ref(imported_func_refs) : make_imported_func_ref : FuncIndex => ir::FuncRef; get_or_create_table(tables) : make_table : TableIndex => TableData; } @@ -1253,39 +1259,48 @@ impl FuncEnvironment<'_> { sig_ref } - fn make_func_ref(&mut self, func: &mut ir::Function, index: FuncIndex) -> ir::FuncRef { - let sig = self.module.functions[index] + fn make_defined_func_ref( + &mut self, + func: &mut ir::Function, + def_func_index: DefinedFuncIndex, + ) -> ir::FuncRef { + let func_index = self.module.func_index(def_func_index); + let ty = self.module.functions[func_index] .signature .unwrap_module_type_index(); - let wasm_func_ty = self.types[sig].unwrap_func(); - let sig = crate::wasm_call_signature(self.isa, wasm_func_ty, &self.tunables); - let signature = func.import_signature(sig); - self.sig_ref_to_ty[signature] = Some(wasm_func_ty); + let signature = self.get_or_create_interned_sig_ref(func, ty); let name = ir::ExternalName::User(func.declare_imported_user_function(ir::UserExternalName { namespace: crate::NS_WASM_FUNC, - index: index.as_u32(), + index: func_index.as_u32(), })); func.import_function(ir::ExtFuncData { name, signature, + colocated: true, + }) + } - // the value of this flag determines the codegen for calls to this - // function. if this flag is `false` then absolute relocations will - // be generated for references to the function, which requires - // load-time relocation resolution. if this flag is set to `true` - // then relative relocations are emitted which can be resolved at - // object-link-time, just after all functions are compiled. - // - // this flag is set to `true` for functions defined in the object - // we'll be defining in this compilation unit, or everything local - // to the wasm module. this means that between functions in a wasm - // module there's relative calls encoded. all calls external to a - // wasm module (e.g. imports or libcalls) are either encoded through - // the `vmcontext` as relative jumps (hence no relocations) or - // they're libcalls with absolute relocations. - colocated: self.module.defined_func_index(index).is_some() - || self.translation.known_imported_functions[index].is_some(), + fn make_imported_func_ref( + &mut self, + func: &mut ir::Function, + func_index: FuncIndex, + ) -> ir::FuncRef { + assert!(self.module.is_imported_function(func_index)); + assert!(self.translation.known_imported_functions[func_index].is_some()); + let ty = self.module.functions[func_index] + .signature + .unwrap_module_type_index(); + let signature = self.get_or_create_interned_sig_ref(func, ty); + let name = + ir::ExternalName::User(func.declare_imported_user_function(ir::UserExternalName { + namespace: crate::NS_WASM_FUNC, + index: func_index.as_u32(), + })); + func.import_function(ir::ExtFuncData { + name, + signature, + colocated: true, }) } @@ -1628,11 +1643,11 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { } } - /// Do a direct call to the given callee function. + /// Do a Wasm-level direct call to the given callee function. pub fn direct_call( mut self, callee_index: FuncIndex, - callee: ir::FuncRef, + sig_ref: ir::SigRef, call_args: &[ir::Value], ) -> WasmResult { let mut real_call_args = Vec::with_capacity(call_args.len() + 2); @@ -1643,7 +1658,7 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { .unwrap(); // Handle direct calls to locally-defined functions. - if !self.env.module.is_imported_function(callee_index) { + if let Some(def_func_index) = self.env.module.defined_func_index(callee_index) { // First append the callee vmctx address, which is the same as the caller vmctx in // this case. real_call_args.push(caller_vmctx); @@ -1655,13 +1670,15 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { real_call_args.extend_from_slice(call_args); // Finally, make the direct call! + let callee = self + .env + .get_or_create_defined_func_ref(self.builder.func, def_func_index); return Ok(self.direct_call_inst(callee, &real_call_args)); } // Handle direct calls to imported functions. We use an indirect call // so that we don't have to patch the code at runtime. let pointer_type = self.env.pointer_type(); - let sig_ref = self.builder.func.dfg.ext_funcs[callee].signature; let vmctx = self.env.vmctx(self.builder.func); let base = self.builder.ins().global_value(pointer_type, vmctx); @@ -1694,6 +1711,9 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { // pass the callee's vmctx that we just loaded, not our own). Otherwise, // we really do an indirect call. if self.env.translation.known_imported_functions[callee_index].is_some() { + let callee = self + .env + .get_or_create_imported_func_ref(self.builder.func, callee_index); Ok(self.direct_call_inst(callee, &real_call_args)) } else { let func_addr = self @@ -1704,7 +1724,7 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { } } - /// Do an indirect call through the given funcref table. + /// Do a Wasm-level indirect call through the given funcref table. pub fn indirect_call( mut self, features: &WasmFeatures, @@ -2773,10 +2793,10 @@ impl FuncEnvironment<'_> { &mut self, builder: &mut FunctionBuilder, callee_index: FuncIndex, - callee: ir::FuncRef, + sig_ref: ir::SigRef, call_args: &[ir::Value], ) -> WasmResult { - Call::new(builder, self).direct_call(callee_index, callee, call_args) + Call::new(builder, self).direct_call(callee_index, sig_ref, call_args) } pub fn translate_call_ref( @@ -2793,10 +2813,10 @@ impl FuncEnvironment<'_> { &mut self, builder: &mut FunctionBuilder, callee_index: FuncIndex, - callee: ir::FuncRef, + sig_ref: ir::SigRef, call_args: &[ir::Value], ) -> WasmResult<()> { - Call::new_tail(builder, self).direct_call(callee_index, callee, call_args)?; + Call::new_tail(builder, self).direct_call(callee_index, sig_ref, call_args)?; Ok(()) } diff --git a/crates/cranelift/src/obj.rs b/crates/cranelift/src/obj.rs index 861f864241..db76673159 100644 --- a/crates/cranelift/src/obj.rs +++ b/crates/cranelift/src/obj.rs @@ -24,7 +24,7 @@ use object::write::{Object, SectionId, StandardSegment, Symbol, SymbolId, Symbol use object::{Architecture, SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope}; use std::ops::Range; use wasmtime_environ::obj; -use wasmtime_environ::{Compiler, TripleExt, Unsigned}; +use wasmtime_environ::{Compiler, TripleExt}; const TEXT_SECTION_NAME: &[u8] = b".text"; @@ -546,7 +546,7 @@ impl<'a> UnwindInfoBuilder<'a> { // unwinders just use this constant for a relative addition with the // address of the FDE, which means that the sign doesn't actually // matter. - let fde = unwind_info.to_fde(Address::Constant(actual_offset.unsigned())); + let fde = unwind_info.to_fde(Address::Constant(actual_offset.cast_unsigned())); table.add_fde(cie_id, fde); } let endian = match compiler.triple().endianness().unwrap() { diff --git a/crates/cranelift/src/translate/code_translator.rs b/crates/cranelift/src/translate/code_translator.rs index 693c0a7394..f117168570 100644 --- a/crates/cranelift/src/translate/code_translator.rs +++ b/crates/cranelift/src/translate/code_translator.rs @@ -93,8 +93,8 @@ use std::collections::{HashMap, hash_map}; use std::vec::Vec; use wasmparser::{FuncValidator, MemArg, Operator, WasmModuleResources}; use wasmtime_environ::{ - DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, Signed, TableIndex, TypeConvert, - TypeIndex, Unsigned, WasmRefType, WasmResult, WasmValType, wasm_unsupported, + DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TypeConvert, TypeIndex, + WasmRefType, WasmResult, WasmValType, wasm_unsupported, }; /// Given a `Reachability`, unwrap the inner `T` or, when unreachable, set @@ -588,25 +588,21 @@ pub fn translate_operator( ************************************************************************************/ Operator::Call { function_index } => { let function_index = FuncIndex::from_u32(*function_index); - let fref = environ.get_or_create_func_ref(builder.func, function_index); + let ty = environ.module.functions[function_index] + .signature + .unwrap_module_type_index(); + let sig_ref = environ.get_or_create_interned_sig_ref(builder.func, ty); let num_args = environ.num_params_for_func(function_index); // Bitcast any vector arguments to their default type, I8X16, before calling. let args = stack.peekn_mut(num_args); - bitcast_wasm_params( - environ, - builder.func.dfg.ext_funcs[fref].signature, - args, - builder, - ); + bitcast_wasm_params(environ, sig_ref, args, builder); - let call = environ.translate_call(builder, function_index, fref, args)?; + let call = environ.translate_call(builder, function_index, sig_ref, args)?; let inst_results = builder.inst_results(call); debug_assert_eq!( inst_results.len(), - builder.func.dfg.signatures[builder.func.dfg.ext_funcs[fref].signature] - .returns - .len(), + builder.func.dfg.signatures[sig_ref].returns.len(), "translate_call results should match the call signature" ); stack.popn(num_args); @@ -662,19 +658,17 @@ pub fn translate_operator( ************************************************************************************/ Operator::ReturnCall { function_index } => { let function_index = FuncIndex::from_u32(*function_index); - let fref = environ.get_or_create_func_ref(builder.func, function_index); + let ty = environ.module.functions[function_index] + .signature + .unwrap_module_type_index(); + let sig_ref = environ.get_or_create_interned_sig_ref(builder.func, ty); let num_args = environ.num_params_for_func(function_index); // Bitcast any vector arguments to their default type, I8X16, before calling. let args = stack.peekn_mut(num_args); - bitcast_wasm_params( - environ, - builder.func.dfg.ext_funcs[fref].signature, - args, - builder, - ); + bitcast_wasm_params(environ, sig_ref, args, builder); - environ.translate_return_call(builder, function_index, fref, args)?; + environ.translate_return_call(builder, function_index, sig_ref, args)?; stack.popn(num_args); stack.reachable = false; @@ -911,7 +905,7 @@ pub fn translate_operator( } /****************************** Nullary Operators ************************************/ Operator::I32Const { value } => { - stack.push1(builder.ins().iconst(I32, i64::from(value.unsigned()))); + stack.push1(builder.ins().iconst(I32, i64::from(value.cast_unsigned()))); } Operator::I64Const { value } => stack.push1(builder.ins().iconst(I64, *value)), Operator::F32Const { value } => { @@ -3301,7 +3295,7 @@ fn prepare_addr( Err(_) => { let offset = builder .ins() - .iconst(heap.index_type(), memarg.offset.signed()); + .iconst(heap.index_type(), memarg.offset.cast_signed()); let adjusted_index = environ.uadd_overflow_trap( builder, index, @@ -3370,7 +3364,7 @@ fn align_atomic_addr( let effective_addr = if memarg.offset == 0 { addr } else { - builder.ins().iadd_imm(addr, memarg.offset.signed()) + builder.ins().iadd_imm(addr, memarg.offset.cast_signed()) }; debug_assert!(loaded_bytes.is_power_of_two()); let misalignment = builder diff --git a/crates/environ/src/compile/module_environ.rs b/crates/environ/src/compile/module_environ.rs index 090c24b6cb..9347cfca06 100644 --- a/crates/environ/src/compile/module_environ.rs +++ b/crates/environ/src/compile/module_environ.rs @@ -6,8 +6,8 @@ use crate::{ ConstExpr, ConstOp, DataIndex, DefinedFuncIndex, ElemIndex, EngineOrModuleTypeIndex, EntityIndex, EntityType, FuncIndex, GlobalIndex, IndexType, InitMemory, MemoryIndex, ModuleInternedTypeIndex, ModuleTypesBuilder, PrimaryMap, SizeOverflow, StaticMemoryInitializer, - TableIndex, TableInitialValue, Tag, TagIndex, Tunables, TypeConvert, TypeIndex, Unsigned, - WasmError, WasmHeapTopType, WasmHeapType, WasmResult, WasmValType, WasmparserTypeConverter, + TableIndex, TableInitialValue, Tag, TagIndex, Tunables, TypeConvert, TypeIndex, WasmError, + WasmHeapTopType, WasmHeapType, WasmResult, WasmValType, WasmparserTypeConverter, }; use crate::{StaticModuleIndex, prelude::*}; use anyhow::{Result, bail}; @@ -360,6 +360,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { for entry in tables { let wasmparser::Table { ty, init } = entry?; let table = self.convert_table_type(&ty)?; + self.result.module.needs_gc_heap |= table.ref_type.is_vmgcref_type(); self.result.module.tables.push(table); let init = match init { wasmparser::TableInit::RefNull => TableInitialValue::Null { @@ -961,9 +962,9 @@ impl ModuleTranslation<'_> { fn eval_offset(&mut self, memory_index: MemoryIndex, expr: &ConstExpr) -> Option { match (expr.ops(), self.module.memories[memory_index].idx_type) { (&[ConstOp::I32Const(offset)], IndexType::I32) => { - Some(offset.unsigned().into()) + Some(offset.cast_unsigned().into()) } - (&[ConstOp::I64Const(offset)], IndexType::I64) => Some(offset.unsigned()), + (&[ConstOp::I64Const(offset)], IndexType::I64) => Some(offset.cast_unsigned()), _ => None, } } @@ -1200,8 +1201,8 @@ impl ModuleTranslation<'_> { // include it in the statically-built array of initial // contents. let offset = match segment.offset.ops() { - &[ConstOp::I32Const(offset)] => u64::from(offset.unsigned()), - &[ConstOp::I64Const(offset)] => offset.unsigned(), + &[ConstOp::I32Const(offset)] => u64::from(offset.cast_unsigned()), + &[ConstOp::I64Const(offset)] => offset.cast_unsigned(), _ => break, }; diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs index ba67fe1f51..799e260361 100644 --- a/crates/environ/src/fact/trampoline.rs +++ b/crates/environ/src/fact/trampoline.rs @@ -32,7 +32,6 @@ use crate::fact::{ }; use crate::prelude::*; use crate::{FuncIndex, GlobalIndex}; -use cranelift_entity::Signed; use std::collections::HashMap; use std::mem; use std::ops::Range; @@ -568,9 +567,9 @@ impl<'a, 'b> Compiler<'a, 'b> { )); } else { if result_types.len() > 0 { - self.instruction(I32Const(PREPARE_ASYNC_WITH_RESULT.signed())); + self.instruction(I32Const(PREPARE_ASYNC_WITH_RESULT.cast_signed())); } else { - self.instruction(I32Const(PREPARE_ASYNC_NO_RESULT.signed())); + self.instruction(I32Const(PREPARE_ASYNC_NO_RESULT.cast_signed())); } } diff --git a/crates/misc/component-async-tests/src/borrowing_host.rs b/crates/misc/component-async-tests/src/borrowing_host.rs index ddc92e5b21..0d9f752be4 100644 --- a/crates/misc/component-async-tests/src/borrowing_host.rs +++ b/crates/misc/component-async-tests/src/borrowing_host.rs @@ -1,6 +1,5 @@ use anyhow::Result; use wasmtime::component::Resource; -use wasmtime_wasi::p2::IoView; use super::Ctx; @@ -20,16 +19,16 @@ pub struct MyX; impl bindings::local::local::borrowing_types::HostX for &mut Ctx { fn new(&mut self) -> Result> { - Ok(IoView::table(self).push(MyX)?) + Ok(self.table.push(MyX)?) } fn foo(&mut self, x: Resource) -> Result<()> { - _ = IoView::table(self).get(&x)?; + _ = self.table.get(&x)?; Ok(()) } fn drop(&mut self, x: Resource) -> Result<()> { - IoView::table(self).delete(x)?; + self.table.delete(x)?; Ok(()) } } diff --git a/crates/misc/component-async-tests/src/lib.rs b/crates/misc/component-async-tests/src/lib.rs index a59515b880..24925d9153 100644 --- a/crates/misc/component-async-tests/src/lib.rs +++ b/crates/misc/component-async-tests/src/lib.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex}; use std::task::Waker; use wasmtime::component::{HasData, ResourceTable}; -use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView}; +use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; pub mod borrowing_host; pub mod closed_streams; @@ -25,15 +25,12 @@ pub struct Ctx { pub continue_: bool, } -impl IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} - impl WasiView for Ctx { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi, + table: &mut self.table, + } } } diff --git a/crates/misc/component-async-tests/src/resource_stream.rs b/crates/misc/component-async-tests/src/resource_stream.rs index 3b8bf30dda..b545efd34c 100644 --- a/crates/misc/component-async-tests/src/resource_stream.rs +++ b/crates/misc/component-async-tests/src/resource_stream.rs @@ -2,7 +2,6 @@ use anyhow::Result; use wasmtime::component::{ Accessor, AccessorTask, GuardedStreamWriter, Resource, StreamReader, StreamWriter, }; -use wasmtime_wasi::p2::IoView; use super::Ctx; @@ -24,12 +23,12 @@ pub struct ResourceStreamX; impl bindings::local::local::resource_stream::HostX for Ctx { fn foo(&mut self, x: Resource) -> Result<()> { - self.table().get(&x)?; + self.table.get(&x)?; Ok(()) } fn drop(&mut self, x: Resource) -> Result<()> { - IoView::table(self).delete(x)?; + self.table.delete(x)?; Ok(()) } } @@ -49,8 +48,7 @@ impl bindings::local::local::resource_stream::HostWithStore for Ctx { async fn run(self, accessor: &Accessor) -> Result<()> { let mut tx = GuardedStreamWriter::new(accessor, self.tx); for _ in 0..self.count { - let item = - accessor.with(|mut view| view.get().table().push(ResourceStreamX))?; + let item = accessor.with(|mut view| view.get().table.push(ResourceStreamX))?; tx.write_all(Some(item)).await; } Ok(()) diff --git a/crates/misc/component-async-tests/tests/scenario/borrowing.rs b/crates/misc/component-async-tests/tests/scenario/borrowing.rs index caed0d77f1..7e1f7cfd1c 100644 --- a/crates/misc/component-async-tests/tests/scenario/borrowing.rs +++ b/crates/misc/component-async-tests/tests/scenario/borrowing.rs @@ -7,7 +7,7 @@ use anyhow::Result; use futures::stream::{FuturesUnordered, TryStreamExt}; use wasmtime::component::{Linker, ResourceTable}; use wasmtime::{Engine, Store}; -use wasmtime_wasi::p2::WasiCtxBuilder; +use wasmtime_wasi::WasiCtxBuilder; #[tokio::test] pub async fn async_borrowing_caller() -> Result<()> { diff --git a/crates/misc/component-async-tests/tests/scenario/round_trip.rs b/crates/misc/component-async-tests/tests/scenario/round_trip.rs index 1db81d8ec6..a40dd26f9b 100644 --- a/crates/misc/component-async-tests/tests/scenario/round_trip.rs +++ b/crates/misc/component-async-tests/tests/scenario/round_trip.rs @@ -15,7 +15,7 @@ use futures::{ }; use wasmtime::component::{Accessor, AccessorTask, HasSelf, Instance, Linker, ResourceTable, Val}; use wasmtime::{Engine, Store}; -use wasmtime_wasi::p2::WasiCtxBuilder; +use wasmtime_wasi::WasiCtxBuilder; #[tokio::test] pub async fn async_round_trip_stackful() -> Result<()> { diff --git a/crates/misc/component-async-tests/tests/scenario/round_trip_direct.rs b/crates/misc/component-async-tests/tests/scenario/round_trip_direct.rs index 7b503aaaec..8f61aae27b 100644 --- a/crates/misc/component-async-tests/tests/scenario/round_trip_direct.rs +++ b/crates/misc/component-async-tests/tests/scenario/round_trip_direct.rs @@ -8,7 +8,7 @@ use component_async_tests::util::sleep; use futures::stream::{FuturesUnordered, TryStreamExt}; use wasmtime::component::{Linker, ResourceTable, Val}; use wasmtime::{Engine, Store}; -use wasmtime_wasi::p2::WasiCtxBuilder; +use wasmtime_wasi::WasiCtxBuilder; #[tokio::test] pub async fn async_round_trip_direct_stackless() -> Result<()> { diff --git a/crates/misc/component-async-tests/tests/scenario/round_trip_many.rs b/crates/misc/component-async-tests/tests/scenario/round_trip_many.rs index 4379749764..c9d59a271f 100644 --- a/crates/misc/component-async-tests/tests/scenario/round_trip_many.rs +++ b/crates/misc/component-async-tests/tests/scenario/round_trip_many.rs @@ -15,7 +15,7 @@ use futures::{ }; use wasmtime::component::{Linker, ResourceTable, Val}; use wasmtime::{Engine, Store}; -use wasmtime_wasi::p2::WasiCtxBuilder; +use wasmtime_wasi::WasiCtxBuilder; #[tokio::test] pub async fn async_round_trip_many_stackless() -> Result<()> { diff --git a/crates/misc/component-async-tests/tests/scenario/streams.rs b/crates/misc/component-async-tests/tests/scenario/streams.rs index 0798411d90..c024fc1ae5 100644 --- a/crates/misc/component-async-tests/tests/scenario/streams.rs +++ b/crates/misc/component-async-tests/tests/scenario/streams.rs @@ -19,7 +19,7 @@ use { ResourceTable, VecBuffer, }, }, - wasmtime_wasi::p2::WasiCtxBuilder, + wasmtime_wasi::WasiCtxBuilder, }; #[tokio::test] diff --git a/crates/misc/component-async-tests/tests/scenario/transmit.rs b/crates/misc/component-async-tests/tests/scenario/transmit.rs index 35bb44cdd4..59a505832f 100644 --- a/crates/misc/component-async-tests/tests/scenario/transmit.rs +++ b/crates/misc/component-async-tests/tests/scenario/transmit.rs @@ -16,7 +16,7 @@ use wasmtime::component::{ GuardedStreamWriter, HasSelf, Instance, Linker, ResourceTable, StreamReader, Val, }; use wasmtime::{AsContextMut, Engine, Store}; -use wasmtime_wasi::p2::WasiCtxBuilder; +use wasmtime_wasi::WasiCtxBuilder; #[tokio::test] pub async fn async_poll_synchronous() -> Result<()> { diff --git a/crates/misc/component-async-tests/tests/scenario/util.rs b/crates/misc/component-async-tests/tests/scenario/util.rs index 824de1fd2c..b972fa8100 100644 --- a/crates/misc/component-async-tests/tests/scenario/util.rs +++ b/crates/misc/component-async-tests/tests/scenario/util.rs @@ -13,7 +13,7 @@ use tokio::sync::Mutex; use wasm_compose::composer::ComponentComposer; use wasmtime::component::{Component, Linker, ResourceTable}; use wasmtime::{Config, Engine, Store}; -use wasmtime_wasi::p2::WasiCtxBuilder; +use wasmtime_wasi::WasiCtxBuilder; pub fn init_logger() { static ONCE: Once = Once::new(); diff --git a/crates/test-programs/src/bin/cli_no_tcp.rs b/crates/test-programs/src/bin/cli_no_tcp.rs index 58ca3de895..a59a0559c0 100644 --- a/crates/test-programs/src/bin/cli_no_tcp.rs +++ b/crates/test-programs/src/bin/cli_no_tcp.rs @@ -1,28 +1,12 @@ //! This test assumes that it will be run without tcp support enabled -use test_programs::wasi::sockets::{ - network::IpAddress, - tcp::{ErrorCode, IpAddressFamily, IpSocketAddress, Network, TcpSocket}, -}; -fn main() { - let net = Network::default(); - let family = IpAddressFamily::Ipv4; - let remote1 = IpSocketAddress::new(IpAddress::new_loopback(family), 4321); - let sock = TcpSocket::new(family).unwrap(); - - let bind = sock.blocking_bind(&net, remote1); - eprintln!("Result of binding: {bind:?}"); - assert!(matches!(bind, Err(ErrorCode::AccessDenied))); +#![deny(warnings)] - let listen = sock.blocking_listen(); - eprintln!("Result of listen: {listen:?}"); - assert!(matches!(listen, Err(ErrorCode::AccessDenied))); +use test_programs::wasi::sockets::tcp::{ErrorCode, IpAddressFamily, TcpSocket}; - let connect = sock.blocking_connect(&net, remote1); - eprintln!("Result of connect: {connect:?}"); - assert!(matches!(connect, Err(ErrorCode::AccessDenied))); - - let accept = sock.blocking_accept(); - eprintln!("Result of accept: {accept:?}"); - assert!(matches!(accept, Err(ErrorCode::AccessDenied))); +fn main() { + assert!(matches!( + TcpSocket::new(IpAddressFamily::Ipv4), + Err(ErrorCode::AccessDenied) + )); } diff --git a/crates/test-programs/src/bin/cli_no_udp.rs b/crates/test-programs/src/bin/cli_no_udp.rs index 1dd0240612..a225336b68 100644 --- a/crates/test-programs/src/bin/cli_no_udp.rs +++ b/crates/test-programs/src/bin/cli_no_udp.rs @@ -1,16 +1,11 @@ //! This test assumes that it will be run without udp support enabled -use test_programs::wasi::sockets::{ - network::IpAddress, - udp::{ErrorCode, IpAddressFamily, IpSocketAddress, Network, UdpSocket}, -}; -fn main() { - let net = Network::default(); - let family = IpAddressFamily::Ipv4; - let remote1 = IpSocketAddress::new(IpAddress::new_loopback(family), 4321); - let sock = UdpSocket::new(family).unwrap(); +#![deny(warnings)] +use test_programs::wasi::sockets::udp::{ErrorCode, IpAddressFamily, UdpSocket}; - let bind = sock.blocking_bind(&net, remote1); - eprintln!("Result of binding: {bind:?}"); - assert!(matches!(bind, Err(ErrorCode::AccessDenied))); +fn main() { + assert!(matches!( + UdpSocket::new(IpAddressFamily::Ipv4), + Err(ErrorCode::AccessDenied) + )); } diff --git a/crates/wasi-config/src/lib.rs b/crates/wasi-config/src/lib.rs index 63fde906f1..186fad8ebc 100644 --- a/crates/wasi-config/src/lib.rs +++ b/crates/wasi-config/src/lib.rs @@ -17,7 +17,7 @@ //! component::{Linker, ResourceTable}, //! Config, Engine, Result, Store, //! }; -//! use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView}; +//! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; //! use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables}; //! //! #[tokio::main] @@ -28,7 +28,7 @@ //! //! let mut store = Store::new(&engine, Ctx { //! table: ResourceTable::new(), -//! wasi_ctx: WasiCtxBuilder::new().build(), +//! wasi_ctx: WasiCtx::builder().build(), //! wasi_config_vars: WasiConfigVariables::from_iter(vec![ //! ("config_key1", "value1"), //! ("config_key2", "value2"), @@ -53,11 +53,10 @@ //! wasi_config_vars: WasiConfigVariables, //! } //! -//! impl IoView for Ctx { -//! fn table(&mut self) -> &mut ResourceTable { &mut self.table } -//! } //! impl WasiView for Ctx { -//! fn ctx(&mut self) -> &mut WasiCtx { &mut self.wasi_ctx } +//! fn ctx(&mut self) -> WasiCtxView<'_> { +//! WasiCtxView { ctx: &mut self.wasi_ctx, table: &mut self.table } +//! } //! } //! ``` //! diff --git a/crates/wasi-config/tests/main.rs b/crates/wasi-config/tests/main.rs index eea6d1eea4..ec798025ed 100644 --- a/crates/wasi-config/tests/main.rs +++ b/crates/wasi-config/tests/main.rs @@ -4,9 +4,8 @@ use wasmtime::{ Store, component::{Component, Linker, ResourceTable}, }; -use wasmtime_wasi::p2::{ - IoView, WasiCtx, WasiCtxBuilder, WasiView, add_to_linker_async, bindings::Command, -}; +use wasmtime_wasi::p2::{add_to_linker_async, bindings::Command}; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables}; struct Ctx { @@ -15,14 +14,12 @@ struct Ctx { wasi_config_vars: WasiConfigVariables, } -impl IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} impl WasiView for Ctx { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi_ctx + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi_ctx, + table: &mut self.table, + } } } diff --git a/crates/wasi-http/src/http_impl.rs b/crates/wasi-http/src/http_impl.rs index c437706b68..5f61b6e250 100644 --- a/crates/wasi-http/src/http_impl.rs +++ b/crates/wasi-http/src/http_impl.rs @@ -14,7 +14,6 @@ use bytes::Bytes; use http_body_util::{BodyExt, Empty}; use hyper::Method; use wasmtime::component::Resource; -use wasmtime_wasi::p2::IoView; impl outgoing_handler::Host for WasiHttpImpl where diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs index d728215881..38a3fd18de 100644 --- a/crates/wasi-http/src/lib.rs +++ b/crates/wasi-http/src/lib.rs @@ -71,7 +71,7 @@ //! use tokio::net::TcpListener; //! use wasmtime::component::{Component, Linker, ResourceTable}; //! use wasmtime::{Config, Engine, Result, Store}; -//! use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView}; +//! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; //! use wasmtime_wasi_http::bindings::ProxyPre; //! use wasmtime_wasi_http::bindings::http::types::Scheme; //! use wasmtime_wasi_http::body::HyperOutgoingBody; @@ -143,7 +143,7 @@ //! self.pre.engine(), //! MyClientState { //! table: ResourceTable::new(), -//! wasi: WasiCtxBuilder::new().inherit_stdio().build(), +//! wasi: WasiCtx::builder().inherit_stdio().build(), //! http: WasiHttpCtx::new(), //! }, //! ); @@ -197,14 +197,10 @@ //! http: WasiHttpCtx, //! table: ResourceTable, //! } -//! impl IoView for MyClientState { -//! fn table(&mut self) -> &mut ResourceTable { -//! &mut self.table -//! } -//! } +//! //! impl WasiView for MyClientState { -//! fn ctx(&mut self) -> &mut WasiCtx { -//! &mut self.wasi +//! fn ctx(&mut self) -> WasiCtxView<'_> { +//! WasiCtxView { ctx: &mut self.wasi, table: &mut self.table } //! } //! } //! @@ -212,6 +208,10 @@ //! fn ctx(&mut self) -> &mut WasiHttpCtx { //! &mut self.http //! } +//! +//! fn table(&mut self) -> &mut ResourceTable { +//! &mut self.table +//! } //! } //! ``` @@ -241,7 +241,6 @@ pub use crate::types::{ WasiHttpImpl, WasiHttpView, }; use wasmtime::component::{HasData, Linker}; -use wasmtime_wasi::p2::IoImpl; /// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`]. /// @@ -257,7 +256,7 @@ use wasmtime_wasi::p2::IoImpl; /// ``` /// use wasmtime::{Engine, Result, Config}; /// use wasmtime::component::{ResourceTable, Linker}; -/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; /// /// fn main() -> Result<()> { @@ -278,19 +277,20 @@ use wasmtime_wasi::p2::IoImpl; /// table: ResourceTable, /// } /// -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } /// impl WasiHttpView for MyState { /// fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx } +/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } /// } +/// /// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn ctx(&mut self) -> WasiCtxView<'_> { +/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +/// } /// } /// ``` pub fn add_to_linker_async(l: &mut wasmtime::component::Linker) -> anyhow::Result<()> where - T: WasiHttpView + wasmtime_wasi::p2::WasiView + 'static, + T: WasiHttpView + wasmtime_wasi::WasiView + 'static, { wasmtime_wasi::p2::add_to_linker_proxy_interfaces_async(l)?; add_only_http_to_linker_async(l) @@ -309,10 +309,10 @@ where { let options = crate::bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options. crate::bindings::http::outgoing_handler::add_to_linker::<_, WasiHttp>(l, |x| { - WasiHttpImpl(IoImpl(x)) + WasiHttpImpl(x) })?; crate::bindings::http::types::add_to_linker::<_, WasiHttp>(l, &options.into(), |x| { - WasiHttpImpl(IoImpl(x)) + WasiHttpImpl(x) })?; Ok(()) @@ -335,7 +335,7 @@ impl HasData for WasiHttp { /// ``` /// use wasmtime::{Engine, Result, Config}; /// use wasmtime::component::{ResourceTable, Linker}; -/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; /// /// fn main() -> Result<()> { @@ -354,19 +354,19 @@ impl HasData for WasiHttp { /// http_ctx: WasiHttpCtx, /// table: ResourceTable, /// } -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } /// impl WasiHttpView for MyState { /// fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx } +/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } /// } /// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn ctx(&mut self) -> WasiCtxView<'_> { +/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +/// } /// } /// ``` pub fn add_to_linker_sync(l: &mut Linker) -> anyhow::Result<()> where - T: WasiHttpView + wasmtime_wasi::p2::WasiView + 'static, + T: WasiHttpView + wasmtime_wasi::WasiView + 'static, { wasmtime_wasi::p2::add_to_linker_proxy_interfaces_sync(l)?; add_only_http_to_linker_sync(l) @@ -383,10 +383,10 @@ where { let options = crate::bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options. crate::bindings::sync::http::outgoing_handler::add_to_linker::<_, WasiHttp>(l, |x| { - WasiHttpImpl(IoImpl(x)) + WasiHttpImpl(x) })?; crate::bindings::sync::http::types::add_to_linker::<_, WasiHttp>(l, &options.into(), |x| { - WasiHttpImpl(IoImpl(x)) + WasiHttpImpl(x) })?; Ok(()) diff --git a/crates/wasi-http/src/p3/mod.rs b/crates/wasi-http/src/p3/mod.rs index 9c9fd75aff..520c5940fc 100644 --- a/crates/wasi-http/src/p3/mod.rs +++ b/crates/wasi-http/src/p3/mod.rs @@ -71,7 +71,7 @@ //! use tokio::net::TcpListener; //! use wasmtime::component::{Component, Linker, ResourceTable}; //! use wasmtime::{Config, Engine, Result, Store}; -//! use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView}; +//! use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView, WasiCtxView}; //! use wasmtime_wasi_http::bindings::ProxyPre; //! use wasmtime_wasi_http::bindings::http::types::Scheme; //! use wasmtime_wasi_http::body::HyperOutgoingBody; @@ -193,14 +193,10 @@ //! http: WasiHttpCtx, //! table: ResourceTable, //! } -//! impl IoView for MyClientState { -//! fn table(&mut self) -> &mut ResourceTable { -//! &mut self.table -//! } -//! } +//! //! impl WasiView for MyClientState { -//! fn ctx(&mut self) -> &mut WasiCtx { -//! &mut self.wasi +//! fn ctx(&mut self) -> WasiCtxView<'_> { +//! WasiCtxView { ctx: &mut self.wasi, table: &mut self.table } //! } //! } //! @@ -208,6 +204,10 @@ //! fn ctx(&mut self) -> &mut WasiHttpCtx { //! &mut self.http //! } +//! +//! fn table(&mut self) -> &mut ResourceTable { +//! &mut self.table +//! } //! } //! ``` @@ -243,7 +243,7 @@ use wasmtime_wasi::p3::ResourceView; /// ``` /// use wasmtime::{Engine, Result, Config}; /// use wasmtime::component::{ResourceTable, Linker}; -/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; /// /// fn main() -> Result<()> { @@ -264,14 +264,14 @@ use wasmtime_wasi::p3::ResourceView; /// table: ResourceTable, /// } /// -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } /// impl WasiHttpView for MyState { /// fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx } +/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } /// } /// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn ctx(&mut self) -> WasiCtxView<'_> { +/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +/// } /// } /// ``` pub fn add_to_linker(l: &mut wasmtime::component::Linker) -> anyhow::Result<()> @@ -279,7 +279,7 @@ where T: WasiHttpView + wasmtime_wasi::clocks::WasiClocksView + wasmtime_wasi::random::WasiRandomView - + wasmtime_wasi::p3::cli::WasiCliView + + wasmtime_wasi::cli::WasiCliView + 'static, { wasmtime_wasi::p3::clocks::add_to_linker(l)?; diff --git a/crates/wasi-http/src/types.rs b/crates/wasi-http/src/types.rs index d865e36ed7..3a0aa0235b 100644 --- a/crates/wasi-http/src/types.rs +++ b/crates/wasi-http/src/types.rs @@ -13,7 +13,7 @@ use hyper::header::HeaderName; use std::any::Any; use std::time::Duration; use wasmtime::component::{Resource, ResourceTable}; -use wasmtime_wasi::p2::{IoImpl, IoView, Pollable}; +use wasmtime_wasi::p2::Pollable; use wasmtime_wasi::runtime::AbortOnDropJoinHandle; #[cfg(feature = "default-send-request")] @@ -43,7 +43,7 @@ impl WasiHttpCtx { /// /// ``` /// use wasmtime::component::ResourceTable; -/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView, WasiCtxBuilder}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; /// /// struct MyState { @@ -52,20 +52,20 @@ impl WasiHttpCtx { /// table: ResourceTable, /// } /// -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } /// impl WasiHttpView for MyState { /// fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx } +/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } /// } /// /// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn ctx(&mut self) -> WasiCtxView<'_> { +/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +/// } /// } /// /// impl MyState { /// fn new() -> MyState { -/// let mut wasi = WasiCtxBuilder::new(); +/// let mut wasi = WasiCtx::builder(); /// wasi.arg("./foo.wasm"); /// wasi.arg("--help"); /// wasi.env("FOO", "bar"); @@ -78,10 +78,13 @@ impl WasiHttpCtx { /// } /// } /// ``` -pub trait WasiHttpView: IoView { +pub trait WasiHttpView { /// Returns a mutable reference to the WASI HTTP context. fn ctx(&mut self) -> &mut WasiHttpCtx; + /// Returns the table used to manage resources. + fn table(&mut self) -> &mut ResourceTable; + /// Create a new incoming request resource. fn new_incoming_request( &mut self, @@ -161,6 +164,10 @@ impl WasiHttpView for &mut T { T::ctx(self) } + fn table(&mut self) -> &mut ResourceTable { + T::table(self) + } + fn new_response_outparam( &mut self, result: tokio::sync::oneshot::Sender< @@ -196,6 +203,10 @@ impl WasiHttpView for Box { T::ctx(self) } + fn table(&mut self) -> &mut ResourceTable { + T::table(self) + } + fn new_response_outparam( &mut self, result: tokio::sync::oneshot::Sender< @@ -239,16 +250,15 @@ impl WasiHttpView for Box { /// [`add_to_linker_sync`](crate::add_to_linker_sync) /// and doesn't need to be manually configured. #[repr(transparent)] -pub struct WasiHttpImpl(pub IoImpl); +pub struct WasiHttpImpl(pub T); -impl IoView for WasiHttpImpl { - fn table(&mut self) -> &mut ResourceTable { - T::table(&mut self.0.0) - } -} impl WasiHttpView for WasiHttpImpl { fn ctx(&mut self) -> &mut WasiHttpCtx { - self.0.0.ctx() + self.0.ctx() + } + + fn table(&mut self) -> &mut ResourceTable { + self.0.table() } fn new_response_outparam( @@ -257,7 +267,7 @@ impl WasiHttpView for WasiHttpImpl { Result, types::ErrorCode>, >, ) -> wasmtime::Result> { - self.0.0.new_response_outparam(result) + self.0.new_response_outparam(result) } fn send_request( @@ -265,19 +275,19 @@ impl WasiHttpView for WasiHttpImpl { request: hyper::Request, config: OutgoingRequestConfig, ) -> crate::HttpResult { - self.0.0.send_request(request, config) + self.0.send_request(request, config) } fn is_forbidden_header(&mut self, name: &HeaderName) -> bool { - self.0.0.is_forbidden_header(name) + self.0.is_forbidden_header(name) } fn outgoing_body_buffer_chunks(&mut self) -> usize { - self.0.0.outgoing_body_buffer_chunks() + self.0.outgoing_body_buffer_chunks() } fn outgoing_body_chunk_size(&mut self) -> usize { - self.0.0.outgoing_body_chunk_size() + self.0.outgoing_body_chunk_size() } } diff --git a/crates/wasi-http/src/types_impl.rs b/crates/wasi-http/src/types_impl.rs index 9713a298aa..2e00c61a83 100644 --- a/crates/wasi-http/src/types_impl.rs +++ b/crates/wasi-http/src/types_impl.rs @@ -14,7 +14,7 @@ use anyhow::{Context, anyhow}; use std::any::Any; use std::str::FromStr; use wasmtime::component::{Resource, ResourceTable, ResourceTableError}; -use wasmtime_wasi::p2::{DynInputStream, DynOutputStream, DynPollable, IoView}; +use wasmtime_wasi::p2::{DynInputStream, DynOutputStream, DynPollable}; impl crate::bindings::http::types::Host for WasiHttpImpl where diff --git a/crates/wasi-http/tests/all/p2/mod.rs b/crates/wasi-http/tests/all/p2/mod.rs index 9421714de1..c4a631f30e 100644 --- a/crates/wasi-http/tests/all/p2/mod.rs +++ b/crates/wasi-http/tests/all/p2/mod.rs @@ -12,7 +12,7 @@ use wasmtime::{ Config, Engine, Store, component::{Component, Linker, ResourceTable}, }; -use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView, pipe::MemoryOutputPipe}; +use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView, p2::pipe::MemoryOutputPipe}; use wasmtime_wasi_http::{ HttpResult, WasiHttpCtx, WasiHttpView, bindings::http::types::{ErrorCode, Scheme}, @@ -37,14 +37,12 @@ struct Ctx { rejected_authority: Option, } -impl IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} impl WasiView for Ctx { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi, + table: &mut self.table, + } } } @@ -53,6 +51,10 @@ impl WasiHttpView for Ctx { &mut self.http } + fn table(&mut self) -> &mut ResourceTable { + &mut self.table + } + fn send_request( &mut self, request: hyper::Request, @@ -82,7 +84,7 @@ fn store(engine: &Engine, server: &Server) -> Store { let stderr = MemoryOutputPipe::new(4096); // Create our wasi context. - let mut builder = WasiCtxBuilder::new(); + let mut builder = WasiCtx::builder(); builder.stdout(stdout.clone()); builder.stderr(stderr.clone()); builder.env("HTTP_SERVER", &server.addr()); @@ -134,7 +136,7 @@ async fn run_wasi_http( let component = Component::from_file(&engine, component_filename)?; // Create our wasi context. - let mut builder = WasiCtxBuilder::new(); + let mut builder = WasiCtx::builder(); builder.stdout(stdout.clone()); builder.stderr(stderr.clone()); let wasi = builder.build(); diff --git a/crates/wasi-http/tests/all/p3/mod.rs b/crates/wasi-http/tests/all/p3/mod.rs index 77216be6ae..63727823d2 100644 --- a/crates/wasi-http/tests/all/p3/mod.rs +++ b/crates/wasi-http/tests/all/p3/mod.rs @@ -3,9 +3,8 @@ use core::future::Future; use bytes::Bytes; use wasmtime::Store; use wasmtime::component::{Component, Linker, ResourceTable}; -use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView}; use wasmtime_wasi::p3::ResourceView; -use wasmtime_wasi::p3::filesystem::{WasiFilesystemCtx, WasiFilesystemView}; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; use wasmtime_wasi_http::p3::bindings::http::types::ErrorCode; use wasmtime_wasi_http::p3::{ Client, DEFAULT_FORBIDDEN_HEADERS, RequestOptions, WasiHttpCtx, WasiHttpView, @@ -17,10 +16,8 @@ mod outgoing; mod proxy; struct Ctx { - filesystem: WasiFilesystemCtx, table: ResourceTable, - wasip2: WasiCtx, - wasip3: wasmtime_wasi::p3::WasiCtx, + wasi: WasiCtx, http: WasiHttpCtx, } @@ -30,33 +27,17 @@ where { fn default() -> Self { Self { - filesystem: WasiFilesystemCtx::default(), table: ResourceTable::default(), - wasip2: WasiCtxBuilder::new().inherit_stdio().build(), - wasip3: wasmtime_wasi::p3::WasiCtxBuilder::new() - .inherit_stdio() - .build(), + wasi: WasiCtxBuilder::new().inherit_stdio().build(), http: WasiHttpCtx::default(), } } } impl WasiView for Ctx { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasip2 - } -} - -impl IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} - -impl wasmtime_wasi::p3::WasiView for Ctx { - fn ctx(&mut self) -> wasmtime_wasi::p3::WasiCtxView<'_> { - wasmtime_wasi::p3::WasiCtxView { - ctx: &mut self.wasip3, + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi, table: &mut self.table, } } @@ -68,12 +49,6 @@ impl ResourceView for Ctx { } } -impl WasiFilesystemView for Ctx { - fn filesystem(&self) -> &WasiFilesystemCtx { - &self.filesystem - } -} - impl WasiHttpView for Ctx { type Client = C; diff --git a/crates/wasi-http/tests/all/p3/outgoing.rs b/crates/wasi-http/tests/all/p3/outgoing.rs index ded44b157e..c3fa8b1fd0 100644 --- a/crates/wasi-http/tests/all/p3/outgoing.rs +++ b/crates/wasi-http/tests/all/p3/outgoing.rs @@ -20,7 +20,7 @@ async fn run(path: &str, server: &Server) -> anyhow::Result<()> { let mut store = Store::new( &engine, Ctx { - wasip3: wasmtime_wasi::p3::WasiCtxBuilder::new() + wasi: wasmtime_wasi::WasiCtx::builder() .env("HTTP_SERVER", server.addr()) .build(), ..Ctx::::default() diff --git a/crates/wasi-io/src/impls.rs b/crates/wasi-io/src/impls.rs index 69ad012830..fe366e59b5 100644 --- a/crates/wasi-io/src/impls.rs +++ b/crates/wasi-io/src/impls.rs @@ -1,7 +1,6 @@ use crate::bindings::wasi::io::{error, poll, streams}; use crate::poll::{DynFuture, DynPollable, MakeFuture, subscribe}; use crate::streams::{DynInputStream, DynOutputStream, StreamError, StreamResult}; -use crate::{IoImpl, IoView}; use alloc::collections::BTreeMap; use alloc::string::String; use alloc::vec::Vec; @@ -9,9 +8,9 @@ use anyhow::{Result, anyhow}; use core::future::Future; use core::pin::Pin; use core::task::{Context, Poll}; -use wasmtime::component::Resource; +use wasmtime::component::{Resource, ResourceTable}; -impl poll::Host for IoImpl { +impl poll::Host for ResourceTable { async fn poll(&mut self, pollables: Vec>) -> Result> { type ReadylistIndex = u32; @@ -19,14 +18,12 @@ impl poll::Host for IoImpl { return Err(anyhow!("empty poll list")); } - let table = self.table(); - let mut table_futures: BTreeMap)> = BTreeMap::new(); for (ix, p) in pollables.iter().enumerate() { let ix: u32 = ix.try_into()?; - let pollable = table.get(p)?; + let pollable = self.get(p)?; let (_, list) = table_futures .entry(pollable.index) .or_insert((pollable.make_future, Vec::new())); @@ -34,7 +31,7 @@ impl poll::Host for IoImpl { } let mut futures: Vec<(DynFuture<'_>, Vec)> = Vec::new(); - for (entry, (make_future, readylist_indices)) in table.iter_entries(table_futures) { + for (entry, (make_future, readylist_indices)) in self.iter_entries(table_futures) { let entry = entry?; futures.push((make_future(entry), readylist_indices)); } @@ -69,18 +66,16 @@ impl poll::Host for IoImpl { } } -impl crate::bindings::wasi::io::poll::HostPollable for IoImpl { +impl crate::bindings::wasi::io::poll::HostPollable for ResourceTable { async fn block(&mut self, pollable: Resource) -> Result<()> { - let table = self.table(); - let pollable = table.get(&pollable)?; - let ready = (pollable.make_future)(table.get_any_mut(pollable.index)?); + let pollable = self.get(&pollable)?; + let ready = (pollable.make_future)(self.get_any_mut(pollable.index)?); ready.await; Ok(()) } async fn ready(&mut self, pollable: Resource) -> Result { - let table = self.table(); - let pollable = table.get(&pollable)?; - let ready = (pollable.make_future)(table.get_any_mut(pollable.index)?); + let pollable = self.get(&pollable)?; + let ready = (pollable.make_future)(self.get_any_mut(pollable.index)?); futures::pin_mut!(ready); Ok(matches!( futures::future::poll_immediate(ready).await, @@ -88,57 +83,57 @@ impl crate::bindings::wasi::io::poll::HostPollable for IoImpl { )) } fn drop(&mut self, pollable: Resource) -> Result<()> { - let pollable = self.table().delete(pollable)?; + let pollable = self.delete(pollable)?; if let Some(delete) = pollable.remove_index_on_delete { - delete(self.table(), pollable.index)?; + delete(self, pollable.index)?; } Ok(()) } } -impl error::Host for IoImpl {} +impl error::Host for ResourceTable {} -impl streams::Host for IoImpl { +impl streams::Host for ResourceTable { fn convert_stream_error(&mut self, err: StreamError) -> Result { match err { StreamError::Closed => Ok(streams::StreamError::Closed), - StreamError::LastOperationFailed(e) => Ok(streams::StreamError::LastOperationFailed( - self.table().push(e)?, - )), + StreamError::LastOperationFailed(e) => { + Ok(streams::StreamError::LastOperationFailed(self.push(e)?)) + } StreamError::Trap(e) => Err(e), } } } -impl error::HostError for IoImpl { +impl error::HostError for ResourceTable { fn drop(&mut self, err: Resource) -> Result<()> { - self.table().delete(err)?; + self.delete(err)?; Ok(()) } fn to_debug_string(&mut self, err: Resource) -> Result { - Ok(alloc::format!("{:?}", self.table().get(&err)?)) + Ok(alloc::format!("{:?}", self.get(&err)?)) } } -impl streams::HostOutputStream for IoImpl { +impl streams::HostOutputStream for ResourceTable { async fn drop(&mut self, stream: Resource) -> Result<()> { - self.table().delete(stream)?.cancel().await; + self.delete(stream)?.cancel().await; Ok(()) } fn check_write(&mut self, stream: Resource) -> StreamResult { - let bytes = self.table().get_mut(&stream)?.check_write()?; + let bytes = self.get_mut(&stream)?.check_write()?; Ok(bytes as u64) } fn write(&mut self, stream: Resource, bytes: Vec) -> StreamResult<()> { - self.table().get_mut(&stream)?.write(bytes.into())?; + self.get_mut(&stream)?.write(bytes.into())?; Ok(()) } fn subscribe(&mut self, stream: Resource) -> Result> { - subscribe(self.table(), stream) + subscribe(self, stream) } async fn blocking_write_and_flush( @@ -152,8 +147,7 @@ impl streams::HostOutputStream for IoImpl { )); } - self.table() - .get_mut(&stream)? + self.get_mut(&stream)? .blocking_write_and_flush(bytes.into()) .await } @@ -169,24 +163,23 @@ impl streams::HostOutputStream for IoImpl { )); } - self.table() - .get_mut(&stream)? + self.get_mut(&stream)? .blocking_write_zeroes_and_flush(len as usize) .await } fn write_zeroes(&mut self, stream: Resource, len: u64) -> StreamResult<()> { - self.table().get_mut(&stream)?.write_zeroes(len as usize)?; + self.get_mut(&stream)?.write_zeroes(len as usize)?; Ok(()) } fn flush(&mut self, stream: Resource) -> StreamResult<()> { - self.table().get_mut(&stream)?.flush()?; + self.get_mut(&stream)?.flush()?; Ok(()) } async fn blocking_flush(&mut self, stream: Resource) -> StreamResult<()> { - let s = self.table().get_mut(&stream)?; + let s = self.get_mut(&stream)?; s.flush()?; s.write_ready().await?; Ok(()) @@ -201,7 +194,7 @@ impl streams::HostOutputStream for IoImpl { let len = len.try_into().unwrap_or(usize::MAX); let permit = { - let output = self.table().get_mut(&dest)?; + let output = self.get_mut(&dest)?; output.check_write()? }; let len = len.min(permit); @@ -209,14 +202,14 @@ impl streams::HostOutputStream for IoImpl { return Ok(0); } - let contents = self.table().get_mut(&src)?.read(len)?; + let contents = self.get_mut(&src)?.read(len)?; let len = contents.len(); if len == 0 { return Ok(0); } - let output = self.table().get_mut(&dest)?; + let output = self.get_mut(&dest)?; output.write(contents)?; Ok(len.try_into().expect("usize can fit in u64")) } @@ -230,7 +223,7 @@ impl streams::HostOutputStream for IoImpl { let len = len.try_into().unwrap_or(usize::MAX); let permit = { - let output = self.table().get_mut(&dest)?; + let output = self.get_mut(&dest)?; output.write_ready().await? }; let len = len.min(permit); @@ -238,28 +231,28 @@ impl streams::HostOutputStream for IoImpl { return Ok(0); } - let contents = self.table().get_mut(&src)?.blocking_read(len).await?; + let contents = self.get_mut(&src)?.blocking_read(len).await?; let len = contents.len(); if len == 0 { return Ok(0); } - let output = self.table().get_mut(&dest)?; + let output = self.get_mut(&dest)?; output.blocking_write_and_flush(contents).await?; Ok(len.try_into().expect("usize can fit in u64")) } } -impl streams::HostInputStream for IoImpl { +impl streams::HostInputStream for ResourceTable { async fn drop(&mut self, stream: Resource) -> Result<()> { - self.table().delete(stream)?.cancel().await; + self.delete(stream)?.cancel().await; Ok(()) } fn read(&mut self, stream: Resource, len: u64) -> StreamResult> { let len = len.try_into().unwrap_or(usize::MAX); - let bytes = self.table().get_mut(&stream)?.read(len)?; + let bytes = self.get_mut(&stream)?.read(len)?; debug_assert!(bytes.len() <= len); Ok(bytes.into()) } @@ -270,14 +263,14 @@ impl streams::HostInputStream for IoImpl { len: u64, ) -> StreamResult> { let len = len.try_into().unwrap_or(usize::MAX); - let bytes = self.table().get_mut(&stream)?.blocking_read(len).await?; + let bytes = self.get_mut(&stream)?.blocking_read(len).await?; debug_assert!(bytes.len() <= len); Ok(bytes.into()) } fn skip(&mut self, stream: Resource, len: u64) -> StreamResult { let len = len.try_into().unwrap_or(usize::MAX); - let written = self.table().get_mut(&stream)?.skip(len)?; + let written = self.get_mut(&stream)?.skip(len)?; Ok(written.try_into().expect("usize always fits in u64")) } @@ -287,11 +280,11 @@ impl streams::HostInputStream for IoImpl { len: u64, ) -> StreamResult { let len = len.try_into().unwrap_or(usize::MAX); - let written = self.table().get_mut(&stream)?.blocking_skip(len).await?; + let written = self.get_mut(&stream)?.blocking_skip(len).await?; Ok(written.try_into().expect("usize always fits in u64")) } fn subscribe(&mut self, stream: Resource) -> Result> { - crate::poll::subscribe(self.table(), stream) + crate::poll::subscribe(self, stream) } } diff --git a/crates/wasi-io/src/lib.rs b/crates/wasi-io/src/lib.rs index a88375f795..96dd631c3a 100644 --- a/crates/wasi-io/src/lib.rs +++ b/crates/wasi-io/src/lib.rs @@ -71,7 +71,7 @@ use wasmtime::component::{HasData, ResourceTable}; /// [`Linker`]: wasmtime::component::Linker /// [`ResourceTable`]: wasmtime::component::ResourceTable /// -pub trait IoView: Send { +pub trait IoView { /// Yields mutable access to the internal resource management that this /// context contains. /// @@ -79,6 +79,7 @@ pub trait IoView: Send { /// resources to wasm as well. fn table(&mut self) -> &mut ResourceTable; } + impl IoView for &mut T { fn table(&mut self) -> &mut ResourceTable { T::table(self) @@ -90,28 +91,6 @@ impl IoView for Box { } } -/// A small newtype wrapper which serves as the basis for implementations of -/// `Host` WASI traits in this crate. -/// -/// This type is used as the basis for the implementation of all `Host` traits -/// generated by `bindgen!` for WASI interfaces. -/// -/// You don't need to use this type if you are using the root -/// [`add_to_linker_async`] in this crate. -/// -/// If you're calling the `add_to_linker` functions generated by `bindgen!` -/// from the [`bindings` module](crate::bindings), you'll want to create a -/// value of this type in the closures added to a [`Linker`]. -/// -/// [`Linker`]: wasmtime::component::Linker -#[repr(transparent)] -pub struct IoImpl(pub T); -impl IoView for IoImpl { - fn table(&mut self) -> &mut ResourceTable { - T::table(&mut self.0) - } -} - /// Add the wasi-io host implementation from this crate into the `linker` /// provided. /// @@ -168,17 +147,17 @@ impl IoView for IoImpl { /// fn table(&mut self) -> &mut ResourceTable { &mut self.table } /// } /// ``` -pub fn add_to_linker_async( +pub fn add_to_linker_async( l: &mut wasmtime::component::Linker, ) -> wasmtime::Result<()> { - crate::bindings::wasi::io::error::add_to_linker::>(l, |x| IoImpl(x))?; - crate::bindings::wasi::io::poll::add_to_linker::>(l, |x| IoImpl(x))?; - crate::bindings::wasi::io::streams::add_to_linker::>(l, |x| IoImpl(x))?; + crate::bindings::wasi::io::error::add_to_linker::(l, T::table)?; + crate::bindings::wasi::io::poll::add_to_linker::(l, T::table)?; + crate::bindings::wasi::io::streams::add_to_linker::(l, T::table)?; Ok(()) } -struct HasIo(T); +struct WasiIo; -impl HasData for HasIo { - type Data<'a> = IoImpl<&'a mut T>; +impl HasData for WasiIo { + type Data<'a> = &'a mut ResourceTable; } diff --git a/crates/wasi-keyvalue/src/lib.rs b/crates/wasi-keyvalue/src/lib.rs index 303ab783ec..4e5972359e 100644 --- a/crates/wasi-keyvalue/src/lib.rs +++ b/crates/wasi-keyvalue/src/lib.rs @@ -20,7 +20,7 @@ //! component::{Linker, ResourceTable}, //! Config, Engine, Result, Store, //! }; -//! use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView}; +//! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; //! use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder}; //! //! #[tokio::main] @@ -31,7 +31,7 @@ //! //! let mut store = Store::new(&engine, Ctx { //! table: ResourceTable::new(), -//! wasi_ctx: WasiCtxBuilder::new().build(), +//! wasi_ctx: WasiCtx::builder().build(), //! wasi_keyvalue_ctx: WasiKeyValueCtxBuilder::new().build(), //! }); //! @@ -53,11 +53,10 @@ //! wasi_keyvalue_ctx: WasiKeyValueCtx, //! } //! -//! impl IoView for Ctx { -//! fn table(&mut self) -> &mut ResourceTable { &mut self.table } -//! } //! impl WasiView for Ctx { -//! fn ctx(&mut self) -> &mut WasiCtx { &mut self.wasi_ctx } +//! fn ctx(&mut self) -> WasiCtxView<'_> { +//! WasiCtxView { ctx: &mut self.wasi_ctx, table: &mut self.table } +//! } //! } //! ``` //! diff --git a/crates/wasi-keyvalue/tests/main.rs b/crates/wasi-keyvalue/tests/main.rs index 45887738b8..a9529441d9 100644 --- a/crates/wasi-keyvalue/tests/main.rs +++ b/crates/wasi-keyvalue/tests/main.rs @@ -4,7 +4,7 @@ use wasmtime::{ Store, component::{Component, Linker, ResourceTable}, }; -use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView, bindings::Command}; +use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView, p2::bindings::Command}; use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder}; struct Ctx { @@ -13,14 +13,12 @@ struct Ctx { wasi_keyvalue_ctx: WasiKeyValueCtx, } -impl IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} impl WasiView for Ctx { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi_ctx + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi_ctx, + table: &mut self.table, + } } } @@ -60,7 +58,7 @@ async fn keyvalue_main() -> Result<()> { KEYVALUE_MAIN_COMPONENT, Ctx { table: ResourceTable::new(), - wasi_ctx: WasiCtxBuilder::new().inherit_stderr().build(), + wasi_ctx: WasiCtx::builder().inherit_stderr().build(), wasi_keyvalue_ctx: WasiKeyValueCtxBuilder::new() .in_memory_data([("atomics_key", "5")]) .build(), diff --git a/crates/wasi-nn/Cargo.toml b/crates/wasi-nn/Cargo.toml index f7ee12c6bc..93bc6c965f 100644 --- a/crates/wasi-nn/Cargo.toml +++ b/crates/wasi-nn/Cargo.toml @@ -59,7 +59,7 @@ walkdir = { workspace = true } cap-std = { workspace = true } libtest-mimic = { workspace = true } test-programs-artifacts = { workspace = true } -wasmtime-wasi = { workspace = true, features = ["preview1"] } +wasmtime-wasi = { workspace = true, features = ["p1"] } wasmtime = { workspace = true, features = ["cranelift"] } tracing-subscriber = { workspace = true } diff --git a/crates/wasi-nn/tests/exec/wit.rs b/crates/wasi-nn/tests/exec/wit.rs index dc57b8df13..268dc92fa6 100644 --- a/crates/wasi-nn/tests/exec/wit.rs +++ b/crates/wasi-nn/tests/exec/wit.rs @@ -5,13 +5,12 @@ use std::path::Path; use wasmtime::component::{Component, Linker, ResourceTable}; use wasmtime::{Config, Engine, Store}; use wasmtime_wasi::p2::bindings::sync::Command; -use wasmtime_wasi::p2::{WasiCtx, WasiCtxBuilder}; -use wasmtime_wasi::{DirPerms, FilePerms}; +use wasmtime_wasi::{DirPerms, FilePerms, WasiCtx, WasiCtxView}; use wasmtime_wasi_nn::wit::WasiNnView; use wasmtime_wasi_nn::{Backend, InMemoryRegistry, wit::WasiNnCtx}; /// Run a wasi-nn test program. This is modeled after -/// `crates/wasi/tests/all/main.rs` but still uses the older preview1 API for +/// `crates/wasi/tests/all/main.rs` but still uses the older p1 API for /// file reads. pub fn run(path: &str, backend: Backend, preload_model: bool) -> Result<()> { let path = Path::new(path); @@ -37,7 +36,7 @@ struct Ctx { impl Ctx { fn new(preopen_dir: &Path, preload_model: bool, mut backend: Backend) -> Result { - let mut builder = WasiCtxBuilder::new(); + let mut builder = WasiCtx::builder(); builder.inherit_stdio().preopened_dir( preopen_dir, PREOPENED_DIR_NAME, @@ -63,13 +62,11 @@ impl Ctx { } } -impl wasmtime_wasi::p2::IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} -impl wasmtime_wasi::p2::WasiView for Ctx { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi +impl wasmtime_wasi::WasiView for Ctx { + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi, + table: &mut self.table, + } } } diff --git a/crates/wasi-nn/tests/exec/witx.rs b/crates/wasi-nn/tests/exec/witx.rs index 8de8ca4fa0..b5611c941d 100644 --- a/crates/wasi-nn/tests/exec/witx.rs +++ b/crates/wasi-nn/tests/exec/witx.rs @@ -3,20 +3,19 @@ use crate::check::artifacts_dir; use anyhow::Result; use std::path::Path; use wasmtime::{Config, Engine, Linker, Module, Store}; -use wasmtime_wasi::p2::WasiCtxBuilder; -use wasmtime_wasi::preview1::WasiP1Ctx; -use wasmtime_wasi::{DirPerms, FilePerms}; +use wasmtime_wasi::p1::WasiP1Ctx; +use wasmtime_wasi::{DirPerms, FilePerms, WasiCtxBuilder}; use wasmtime_wasi_nn::{Backend, InMemoryRegistry, witx::WasiNnCtx}; /// Run a wasi-nn test program. This is modeled after -/// `crates/wasi/tests/all/main.rs` but still uses the older preview1 API +/// `crates/wasi/tests/all/main.rs` but still uses the older p1 API /// for file reads. pub fn run(path: &str, backend: Backend, preload_model: bool) -> Result<()> { let path = Path::new(path); let engine = Engine::new(&Config::new())?; let mut linker = Linker::new(&engine); wasmtime_wasi_nn::witx::add_to_linker(&mut linker, |s: &mut Ctx| &mut s.wasi_nn)?; - wasmtime_wasi::preview1::add_to_linker_sync(&mut linker, |s: &mut Ctx| &mut s.wasi)?; + wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |s: &mut Ctx| &mut s.wasi)?; let module = Module::from_file(&engine, path)?; let mut store = Store::new(&engine, Ctx::new(&artifacts_dir(), preload_model, backend)?); let instance = linker.instantiate(&mut store, &module)?; diff --git a/crates/wasi-tls-nativetls/tests/main.rs b/crates/wasi-tls-nativetls/tests/main.rs index d86fe08f63..202a2cd34c 100644 --- a/crates/wasi-tls-nativetls/tests/main.rs +++ b/crates/wasi-tls-nativetls/tests/main.rs @@ -3,7 +3,7 @@ use wasmtime::{ Store, component::{Component, Linker, ResourceTable}, }; -use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView, bindings::Command}; +use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView, p2::bindings::Command}; use wasmtime_wasi_tls::{LinkOptions, WasiTls, WasiTlsCtx, WasiTlsCtxBuilder}; struct Ctx { @@ -12,14 +12,12 @@ struct Ctx { wasi_tls_ctx: WasiTlsCtx, } -impl IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} impl WasiView for Ctx { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi_ctx + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi_ctx, + table: &mut self.table, + } } } @@ -27,7 +25,7 @@ async fn run_test(path: &str) -> Result<()> { let provider = Box::new(wasmtime_wasi_tls_nativetls::NativeTlsProvider::default()); let ctx = Ctx { table: ResourceTable::new(), - wasi_ctx: WasiCtxBuilder::new() + wasi_ctx: WasiCtx::builder() .inherit_stderr() .inherit_network() .allow_ip_name_lookup(true) diff --git a/crates/wasi-tls/src/lib.rs b/crates/wasi-tls/src/lib.rs index 28c96d8a46..f98cdf186b 100644 --- a/crates/wasi-tls/src/lib.rs +++ b/crates/wasi-tls/src/lib.rs @@ -8,7 +8,7 @@ //! # An example of how to configure [wasi-tls] is the following: //! //! ```rust -//! use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView}; +//! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; //! use wasmtime::{ //! component::{Linker, ResourceTable}, //! Store, Engine, Result, Config @@ -21,15 +21,9 @@ //! wasi_tls_ctx: WasiTlsCtx, //! } //! -//! impl IoView for Ctx { -//! fn table(&mut self) -> &mut ResourceTable { -//! &mut self.table -//! } -//! } -//! //! impl WasiView for Ctx { -//! fn ctx(&mut self) -> &mut WasiCtx { -//! &mut self.wasi_ctx +//! fn ctx(&mut self) -> WasiCtxView<'_> { +//! WasiCtxView { ctx: &mut self.wasi_ctx, table: &mut self.table } //! } //! } //! @@ -37,7 +31,7 @@ //! async fn main() -> Result<()> { //! let ctx = Ctx { //! table: ResourceTable::new(), -//! wasi_ctx: WasiCtxBuilder::new() +//! wasi_ctx: WasiCtx::builder() //! .inherit_stderr() //! .inherit_network() //! .allow_ip_name_lookup(true) diff --git a/crates/wasi-tls/tests/main.rs b/crates/wasi-tls/tests/main.rs index 9c8494b6ea..d80797ff38 100644 --- a/crates/wasi-tls/tests/main.rs +++ b/crates/wasi-tls/tests/main.rs @@ -3,7 +3,7 @@ use wasmtime::{ Store, component::{Component, Linker, ResourceTable}, }; -use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView, bindings::Command}; +use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView, p2::bindings::Command}; use wasmtime_wasi_tls::{LinkOptions, WasiTls, WasiTlsCtx, WasiTlsCtxBuilder}; struct Ctx { @@ -12,21 +12,19 @@ struct Ctx { wasi_tls_ctx: WasiTlsCtx, } -impl IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} impl WasiView for Ctx { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi_ctx + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi_ctx, + table: &mut self.table, + } } } async fn run_test(path: &str) -> Result<()> { let ctx = Ctx { table: ResourceTable::new(), - wasi_ctx: WasiCtxBuilder::new() + wasi_ctx: WasiCtx::builder() .inherit_stdout() .inherit_stderr() .inherit_network() diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 50cf35cc31..78cfd5055d 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -16,7 +16,7 @@ include = ["src/**/*", "README.md", "LICENSE", "witx/*", "wit/**/*", "tests/*"] workspace = true [dependencies] -wasmtime = { workspace = true, features = ["component-model", "async", "runtime", "std"] } +wasmtime = { workspace = true, features = ["runtime", "std"] } wasmtime-wasi-io = { workspace = true, features = ["std"] } anyhow = { workspace = true } wiggle = { workspace = true, optional = true, features = ["wasmtime"] } @@ -56,10 +56,10 @@ windows-sys = { workspace = true } rustix = { workspace = true, features = ["event", "net"] } [features] -default = ["preview1", "p3"] -preview1 = [ - "dep:wiggle", -] +default = ["p1", "p2", "p3"] +p0 = ["p1"] +p1 = ["dep:wiggle", "p2"] +p2 = ["wasmtime/component-model", "wasmtime/async"] p3 = [ "wasmtime/component-model-async", "wasmtime/component-model-async-bytes", diff --git a/crates/wasi/src/cli.rs b/crates/wasi/src/cli.rs index 3769a11d08..2d1ad21ad2 100644 --- a/crates/wasi/src/cli.rs +++ b/crates/wasi/src/cli.rs @@ -1,14 +1,62 @@ -use std::rc::Rc; +use crate::p2; +use std::pin::Pin; use std::sync::Arc; +use tokio::io::{AsyncRead, AsyncWrite, empty}; +use wasmtime::component::{HasData, ResourceTable}; +use wasmtime_wasi_io::streams::{InputStream, OutputStream}; -#[derive(Default)] -pub struct WasiCliCtx { - pub environment: Vec<(String, String)>, - pub arguments: Vec, - pub initial_cwd: Option, - pub stdin: I, - pub stdout: O, - pub stderr: O, +mod empty; +mod file; +mod locked_async; +mod mem; +mod stdout; +mod worker_thread_stdin; + +pub use self::file::{InputFile, OutputFile}; +pub use self::locked_async::{AsyncStdinStream, AsyncStdoutStream}; + +// Convenience reexport for stdio types so tokio doesn't have to be imported +// itself. +#[doc(no_inline)] +pub use tokio::io::{Stderr, Stdin, Stdout, stderr, stdin, stdout}; + +pub(crate) struct WasiCli; + +impl HasData for WasiCli { + type Data<'a> = WasiCliCtxView<'a>; +} + +/// Provides a "view" of `wasi:cli`-related context used to implement host +/// traits. +pub trait WasiCliView: Send { + fn cli(&mut self) -> WasiCliCtxView<'_>; +} + +pub struct WasiCliCtxView<'a> { + pub ctx: &'a mut WasiCliCtx, + pub table: &'a mut ResourceTable, +} + +pub struct WasiCliCtx { + pub(crate) environment: Vec<(String, String)>, + pub(crate) arguments: Vec, + pub(crate) initial_cwd: Option, + pub(crate) stdin: Box, + pub(crate) stdout: Box, + pub(crate) stderr: Box, +} + +impl Default for WasiCliCtx { + fn default() -> WasiCliCtx { + WasiCliCtx { + environment: Vec::new(), + arguments: Vec::new(), + initial_cwd: None, + stdin: Box::new(empty()), + stdout: Box::new(empty()), + stderr: Box::new(empty()), + } + } } pub trait IsTerminal { @@ -16,80 +64,268 @@ pub trait IsTerminal { fn is_terminal(&self) -> bool; } +/// A trait used to represent the standard input to a guest program. +/// +/// Note that there are many built-in implementations of this trait for various +/// types such as [`tokio::io::Stdin`], [`tokio::io::Empty`], and +/// [`p2::pipe::MemoryInputPipe`]. +pub trait StdinStream: IsTerminal + Send { + /// Creates a fresh stream which is reading stdin. + /// + /// Note that the returned stream must share state with all other streams + /// previously created. Guests may create multiple handles to the same stdin + /// and they should all be synchronized in their progress through the + /// program's input. + /// + /// Note that this means that if one handle becomes ready for reading they + /// all become ready for reading. Subsequently if one is read from it may + /// mean that all the others are no longer ready for reading. This is + /// basically a consequence of the way the WIT APIs are designed today. + fn async_stream(&self) -> Box; + + /// Same as [`Self::async_stream`] except that a WASIp2 [`InputStream`] is + /// returned. + /// + /// Note that this has a default implementation which uses + /// [`p2::pipe::AsyncReadStream`] as an adapter, but this can be overridden + /// if there's a more specialized implementation available. + fn p2_stream(&self) -> Box { + Box::new(p2::pipe::AsyncReadStream::new(Pin::from( + self.async_stream(), + ))) + } +} + +/// Similar to [`StdinStream`], except for output. +/// +/// This is used both for a guest stdin and a guest stdout. +/// +/// Note that there are many built-in implementations of this trait for various +/// types such as [`tokio::io::Stdout`], [`tokio::io::Empty`], and +/// [`p2::pipe::MemoryOutputPipe`]. +pub trait StdoutStream: IsTerminal + Send { + /// Returns a fresh new stream which can write to this output stream. + /// + /// Note that all output streams should output to the same logical source. + /// This means that it's possible for each independent stream to acquire a + /// separate "permit" to write and then act on that permit. Note that + /// additionally at this time once a permit is "acquired" there's no way to + /// release it, for example you can wait for readiness and then never + /// actually write in WASI. This means that acquisition of a permit for one + /// stream cannot discount the size of a permit another stream could + /// obtain. + /// + /// Implementations must be able to handle this + fn async_stream(&self) -> Box; + + /// Same as [`Self::async_stream`] except that a WASIp2 [`OutputStream`] is + /// returned. + /// + /// Note that this has a default implementation which uses + /// [`p2::pipe::AsyncWriteStream`] as an adapter, but this can be overridden + /// if there's a more specialized implementation available. + fn p2_stream(&self) -> Box { + Box::new(p2::pipe::AsyncWriteStream::new( + 8192, // FIXME: extract this to a constant. + Pin::from(self.async_stream()), + )) + } +} + +// Forward `&T => T` impl IsTerminal for &T { fn is_terminal(&self) -> bool { T::is_terminal(self) } } +impl StdinStream for &T { + fn p2_stream(&self) -> Box { + T::p2_stream(self) + } + fn async_stream(&self) -> Box { + T::async_stream(self) + } +} +impl StdoutStream for &T { + fn p2_stream(&self) -> Box { + T::p2_stream(self) + } + fn async_stream(&self) -> Box { + T::async_stream(self) + } +} +// Forward `&mut T => T` impl IsTerminal for &mut T { fn is_terminal(&self) -> bool { T::is_terminal(self) } } +impl StdinStream for &mut T { + fn p2_stream(&self) -> Box { + T::p2_stream(self) + } + fn async_stream(&self) -> Box { + T::async_stream(self) + } +} +impl StdoutStream for &mut T { + fn p2_stream(&self) -> Box { + T::p2_stream(self) + } + fn async_stream(&self) -> Box { + T::async_stream(self) + } +} +// Forward `Box => T` impl IsTerminal for Box { fn is_terminal(&self) -> bool { T::is_terminal(self) } } - -impl IsTerminal for Rc { - fn is_terminal(&self) -> bool { - T::is_terminal(self) +impl StdinStream for Box { + fn p2_stream(&self) -> Box { + T::p2_stream(self) + } + fn async_stream(&self) -> Box { + T::async_stream(self) + } +} +impl StdoutStream for Box { + fn p2_stream(&self) -> Box { + T::p2_stream(self) + } + fn async_stream(&self) -> Box { + T::async_stream(self) } } +// Forward `Arc => T` impl IsTerminal for Arc { fn is_terminal(&self) -> bool { T::is_terminal(self) } } - -impl IsTerminal for tokio::io::Empty { - fn is_terminal(&self) -> bool { - false +impl StdinStream for Arc { + fn p2_stream(&self) -> Box { + T::p2_stream(self) } -} - -impl IsTerminal for std::io::Empty { - fn is_terminal(&self) -> bool { - false + fn async_stream(&self) -> Box { + T::async_stream(self) } } - -impl IsTerminal for tokio::io::Stdin { - fn is_terminal(&self) -> bool { - std::io::stdin().is_terminal() +impl StdoutStream for Arc { + fn p2_stream(&self) -> Box { + T::p2_stream(self) } -} - -impl IsTerminal for std::io::Stdin { - fn is_terminal(&self) -> bool { - std::io::IsTerminal::is_terminal(self) + fn async_stream(&self) -> Box { + T::async_stream(self) } } -impl IsTerminal for tokio::io::Stdout { - fn is_terminal(&self) -> bool { - std::io::stdout().is_terminal() +#[cfg(test)] +mod test { + use crate::cli::{AsyncStdoutStream, StdinStream, StdoutStream}; + use crate::p2::{self, OutputStream}; + use anyhow::Result; + use bytes::Bytes; + use tokio::io::AsyncReadExt; + + #[test] + fn memory_stdin_stream() { + // A StdinStream has the property that there are multiple + // InputStreams created, using the stream() method which are each + // views on the same shared state underneath. Consuming input on one + // stream results in consuming that input on all streams. + // + // The simplest way to measure this is to check if the MemoryInputPipe + // impl of StdinStream follows this property. + + let pipe = + p2::pipe::MemoryInputPipe::new("the quick brown fox jumped over the three lazy dogs"); + + let mut view1 = pipe.p2_stream(); + let mut view2 = pipe.p2_stream(); + + let read1 = view1.read(10).expect("read first 10 bytes"); + assert_eq!(read1, "the quick ".as_bytes(), "first 10 bytes"); + let read2 = view2.read(10).expect("read second 10 bytes"); + assert_eq!(read2, "brown fox ".as_bytes(), "second 10 bytes"); + let read3 = view1.read(10).expect("read third 10 bytes"); + assert_eq!(read3, "jumped ove".as_bytes(), "third 10 bytes"); + let read4 = view2.read(10).expect("read fourth 10 bytes"); + assert_eq!(read4, "r the thre".as_bytes(), "fourth 10 bytes"); } -} -impl IsTerminal for std::io::Stdout { - fn is_terminal(&self) -> bool { - std::io::IsTerminal::is_terminal(self) + #[tokio::test] + async fn async_stdin_stream() { + // A StdinStream has the property that there are multiple + // InputStreams created, using the stream() method which are each + // views on the same shared state underneath. Consuming input on one + // stream results in consuming that input on all streams. + // + // AsyncStdinStream is a slightly more complex impl of StdinStream + // than the MemoryInputPipe above. We can create an AsyncReadStream + // from a file on the disk, and an AsyncStdinStream from that common + // stream, then check that the same property holds as above. + + let dir = tempfile::tempdir().unwrap(); + let mut path = std::path::PathBuf::from(dir.path()); + path.push("file"); + std::fs::write(&path, "the quick brown fox jumped over the three lazy dogs").unwrap(); + + let file = tokio::fs::File::open(&path) + .await + .expect("open created file"); + let stdin_stream = super::AsyncStdinStream::new(file); + + use super::StdinStream; + + let mut view1 = stdin_stream.p2_stream(); + let mut view2 = stdin_stream.p2_stream(); + + view1.ready().await; + + let read1 = view1.read(10).expect("read first 10 bytes"); + assert_eq!(read1, "the quick ".as_bytes(), "first 10 bytes"); + let read2 = view2.read(10).expect("read second 10 bytes"); + assert_eq!(read2, "brown fox ".as_bytes(), "second 10 bytes"); + let read3 = view1.read(10).expect("read third 10 bytes"); + assert_eq!(read3, "jumped ove".as_bytes(), "third 10 bytes"); + let read4 = view2.read(10).expect("read fourth 10 bytes"); + assert_eq!(read4, "r the thre".as_bytes(), "fourth 10 bytes"); } -} -impl IsTerminal for tokio::io::Stderr { - fn is_terminal(&self) -> bool { - std::io::stderr().is_terminal() + #[tokio::test] + async fn async_stdout_stream_unblocks() { + let (mut read, write) = tokio::io::duplex(32); + let stdout = AsyncStdoutStream::new(32, write); + + let task = tokio::task::spawn(async move { + let mut stream = stdout.p2_stream(); + blocking_write_and_flush(&mut *stream, "x".into()) + .await + .unwrap(); + }); + + let mut buf = [0; 100]; + let n = read.read(&mut buf).await.unwrap(); + assert_eq!(&buf[..n], b"x"); + + task.await.unwrap(); } -} -impl IsTerminal for std::io::Stderr { - fn is_terminal(&self) -> bool { - std::io::IsTerminal::is_terminal(self) + async fn blocking_write_and_flush(s: &mut dyn OutputStream, mut bytes: Bytes) -> Result<()> { + while !bytes.is_empty() { + let permit = s.write_ready().await?; + let len = bytes.len().min(permit); + let chunk = bytes.split_to(len); + s.write(chunk)?; + } + + s.flush()?; + s.write_ready().await?; + Ok(()) } } diff --git a/crates/wasi/src/cli/empty.rs b/crates/wasi/src/cli/empty.rs new file mode 100644 index 0000000000..98f134f7b4 --- /dev/null +++ b/crates/wasi/src/cli/empty.rs @@ -0,0 +1,115 @@ +use crate::cli::{IsTerminal, StdinStream, StdoutStream}; +use crate::p2; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::io::{self, AsyncRead, AsyncWrite}; +use wasmtime_wasi_io::streams::{InputStream, OutputStream}; + +// Implementation for tokio::io::Empty +impl IsTerminal for tokio::io::Empty { + fn is_terminal(&self) -> bool { + false + } +} +impl StdinStream for tokio::io::Empty { + fn p2_stream(&self) -> Box { + Box::new(p2::pipe::ClosedInputStream) + } + fn async_stream(&self) -> Box { + Box::new(tokio::io::empty()) + } +} +impl StdoutStream for tokio::io::Empty { + fn p2_stream(&self) -> Box { + Box::new(p2::pipe::SinkOutputStream) + } + fn async_stream(&self) -> Box { + Box::new(tokio::io::empty()) + } +} + +// Implementation for std::io::Empty +impl IsTerminal for std::io::Empty { + fn is_terminal(&self) -> bool { + false + } +} +impl StdinStream for std::io::Empty { + fn p2_stream(&self) -> Box { + Box::new(p2::pipe::ClosedInputStream) + } + fn async_stream(&self) -> Box { + Box::new(tokio::io::empty()) + } +} +impl StdoutStream for std::io::Empty { + fn p2_stream(&self) -> Box { + Box::new(p2::pipe::SinkOutputStream) + } + fn async_stream(&self) -> Box { + Box::new(tokio::io::empty()) + } +} + +// Implementation for p2::pipe::ClosedInputStream +impl IsTerminal for p2::pipe::ClosedInputStream { + fn is_terminal(&self) -> bool { + false + } +} +impl StdinStream for p2::pipe::ClosedInputStream { + fn p2_stream(&self) -> Box { + Box::new(p2::pipe::ClosedInputStream) + } + fn async_stream(&self) -> Box { + Box::new(tokio::io::empty()) + } +} + +// Implementation for p2::pipe::SinkOutputStream +impl IsTerminal for p2::pipe::SinkOutputStream { + fn is_terminal(&self) -> bool { + false + } +} +impl StdoutStream for p2::pipe::SinkOutputStream { + fn p2_stream(&self) -> Box { + Box::new(p2::pipe::SinkOutputStream) + } + fn async_stream(&self) -> Box { + Box::new(tokio::io::empty()) + } +} + +// Implementation for p2::pipe::ClosedOutputStream +impl IsTerminal for p2::pipe::ClosedOutputStream { + fn is_terminal(&self) -> bool { + false + } +} +impl StdoutStream for p2::pipe::ClosedOutputStream { + fn p2_stream(&self) -> Box { + Box::new(p2::pipe::ClosedOutputStream) + } + fn async_stream(&self) -> Box { + struct AlwaysClosed; + + impl AsyncWrite for AlwaysClosed { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &[u8], + ) -> Poll> { + Poll::Ready(Ok(0)) + } + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + } + + Box::new(AlwaysClosed) + } +} diff --git a/crates/wasi/src/cli/file.rs b/crates/wasi/src/cli/file.rs new file mode 100644 index 0000000000..9cbd702601 --- /dev/null +++ b/crates/wasi/src/cli/file.rs @@ -0,0 +1,150 @@ +use crate::cli::{IsTerminal, StdinStream, StdoutStream}; +use crate::p2::{InputStream, OutputStream, Pollable, StreamError, StreamResult}; +use bytes::Bytes; +use std::io::{Read, Write}; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use tokio::io::{self, AsyncRead, AsyncWrite}; + +/// This implementation will yield output streams that block on writes, and +/// output directly to a file. If truly async output is required, [`AsyncStdoutStream`] +/// should be used instead. +#[derive(Clone)] +pub struct OutputFile { + file: Arc, +} + +impl OutputFile { + pub fn new(file: std::fs::File) -> Self { + Self { + file: Arc::new(file), + } + } +} + +impl IsTerminal for OutputFile { + fn is_terminal(&self) -> bool { + false + } +} + +impl StdoutStream for OutputFile { + fn p2_stream(&self) -> Box { + Box::new(self.clone()) + } + + fn async_stream(&self) -> Box { + Box::new(self.clone()) + } +} + +#[async_trait::async_trait] +impl Pollable for OutputFile { + async fn ready(&mut self) {} +} + +impl OutputStream for OutputFile { + fn write(&mut self, bytes: Bytes) -> StreamResult<()> { + (&*self.file) + .write_all(&bytes) + .map_err(|e| StreamError::LastOperationFailed(anyhow::anyhow!(e))) + } + + fn flush(&mut self) -> StreamResult<()> { + use std::io::Write; + self.file + .flush() + .map_err(|e| StreamError::LastOperationFailed(anyhow::anyhow!(e))) + } + + fn check_write(&mut self) -> StreamResult { + Ok(1024 * 1024) + } +} + +impl AsyncWrite for OutputFile { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + match (&*self.file).write_all(buf) { + Ok(()) => Poll::Ready(Ok(buf.len())), + Err(e) => Poll::Ready(Err(e)), + } + } + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready((&*self.file).flush()) + } + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + +/// This implementation will yield input streams that block on reads, and +/// reads directly from a file. If truly async input is required, +/// [`AsyncStdinStream`] should be used instead. +#[derive(Clone)] +pub struct InputFile { + file: Arc, +} + +impl InputFile { + pub fn new(file: std::fs::File) -> Self { + Self { + file: Arc::new(file), + } + } +} + +impl StdinStream for InputFile { + fn p2_stream(&self) -> Box { + Box::new(self.clone()) + } + fn async_stream(&self) -> Box { + Box::new(self.clone()) + } +} + +impl IsTerminal for InputFile { + fn is_terminal(&self) -> bool { + false + } +} + +#[async_trait::async_trait] +impl Pollable for InputFile { + async fn ready(&mut self) {} +} + +impl InputStream for InputFile { + fn read(&mut self, size: usize) -> StreamResult { + let mut buf = bytes::BytesMut::zeroed(size); + let bytes_read = self + .file + .read(&mut buf) + .map_err(|e| StreamError::LastOperationFailed(anyhow::anyhow!(e)))?; + if bytes_read == 0 { + return Err(StreamError::Closed); + } + buf.truncate(bytes_read); + StreamResult::Ok(buf.into()) + } +} + +impl AsyncRead for InputFile { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> Poll> { + match (&*self.file).read(buf.initialize_unfilled()) { + Ok(n) => { + buf.advance(n); + Poll::Ready(Ok(())) + } + Err(e) => Poll::Ready(Err(e)), + } + } +} diff --git a/crates/wasi/src/cli/locked_async.rs b/crates/wasi/src/cli/locked_async.rs new file mode 100644 index 0000000000..acd8a2d350 --- /dev/null +++ b/crates/wasi/src/cli/locked_async.rs @@ -0,0 +1,351 @@ +use crate::cli::{IsTerminal, StdinStream, StdoutStream}; +use crate::p2; +use bytes::Bytes; +use std::mem; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll, ready}; +use tokio::io::{self, AsyncRead, AsyncWrite}; +use tokio::sync::{Mutex, OwnedMutexGuard}; +use wasmtime_wasi_io::streams::{InputStream, OutputStream}; + +trait SharedHandleReady: Send + Sync + 'static { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<()>; +} + +impl SharedHandleReady for p2::pipe::AsyncWriteStream { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<()> { + ::poll_ready(self, cx) + } +} + +impl SharedHandleReady for p2::pipe::AsyncReadStream { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<()> { + ::poll_ready(self, cx) + } +} + +/// An impl of [`StdinStream`] built on top of [`AsyncRead`]. +// +// Note the usage of `tokio::sync::Mutex` here as opposed to a +// `std::sync::Mutex`. This is intentionally done to implement the `Pollable` +// variant of this trait. Note that in doing so we're left with the quandry of +// how to implement methods of `InputStream` since those methods are not +// `async`. They're currently implemented with `try_lock`, which then raises the +// question of what to do on contention. Currently traps are returned. +// +// Why should it be ok to return a trap? In general concurrency/contention +// shouldn't return a trap since it should be able to happen normally. The +// current assumption, though, is that WASI stdin/stdout streams are special +// enough that the contention case should never come up in practice. Currently +// in WASI there is no actually concurrency, there's just the items in a single +// `Store` and that store owns all of its I/O in a single Tokio task. There's no +// means to actually spawn multiple Tokio tasks that use the same store. This +// means at the very least that there's zero parallelism. Due to the lack of +// multiple tasks that also means that there's no concurrency either. +// +// This `AsyncStdinStream` wrapper is only intended to be used by the WASI +// bindings themselves. It's possible for the host to take this and work with it +// on its own task, but that's niche enough it's not designed for. +// +// Overall that means that the guest is either calling `Pollable` or +// `InputStream` methods. This means that there should never be contention +// between the two at this time. This may all change in the future with WASI +// 0.3, but perhaps we'll have a better story for stdio at that time (see the +// doc block on the `OutputStream` impl below) +pub struct AsyncStdinStream(Arc>); + +impl AsyncStdinStream { + pub fn new(s: impl AsyncRead + Send + Sync + 'static) -> Self { + Self(Arc::new(Mutex::new(p2::pipe::AsyncReadStream::new(s)))) + } +} + +impl StdinStream for AsyncStdinStream { + fn p2_stream(&self) -> Box { + Box::new(Self(self.0.clone())) + } + fn async_stream(&self) -> Box { + Box::new(StdioHandle::Ready(self.0.clone())) + } +} + +impl IsTerminal for AsyncStdinStream { + fn is_terminal(&self) -> bool { + false + } +} + +#[async_trait::async_trait] +impl InputStream for AsyncStdinStream { + fn read(&mut self, size: usize) -> Result { + match self.0.try_lock() { + Ok(mut stream) => stream.read(size), + Err(_) => Err(p2::StreamError::trap("concurrent reads are not supported")), + } + } + fn skip(&mut self, size: usize) -> Result { + match self.0.try_lock() { + Ok(mut stream) => stream.skip(size), + Err(_) => Err(p2::StreamError::trap("concurrent skips are not supported")), + } + } + async fn cancel(&mut self) { + // Cancel the inner stream if we're the last reference to it: + if let Some(mutex) = Arc::get_mut(&mut self.0) { + match mutex.try_lock() { + Ok(mut stream) => stream.cancel().await, + Err(_) => {} + } + } + } +} + +#[async_trait::async_trait] +impl p2::Pollable for AsyncStdinStream { + async fn ready(&mut self) { + self.0.lock().await.ready().await + } +} + +impl AsyncRead for StdioHandle { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> Poll> { + match ready!(self.as_mut().poll(cx, |g| g.read(buf.remaining()))) { + Some(Ok(bytes)) => { + buf.put_slice(&bytes); + Poll::Ready(Ok(())) + } + Some(Err(e)) => Poll::Ready(Err(e)), + // If the guard can't be acquired that means that this stream is + // closed, so return that we're ready without filling in data. + None => Poll::Ready(Ok(())), + } + } +} + +/// A wrapper of [`crate::p2::pipe::AsyncWriteStream`] that implements +/// [`StdoutStream`]. Note that the [`OutputStream`] impl for this is not +/// correct when used for interleaved async IO. +// +// Note that the use of `tokio::sync::Mutex` here is intentional, in addition to +// the `try_lock()` calls below in the implementation of `OutputStream`. For +// more information see the documentation on `AsyncStdinStream`. +pub struct AsyncStdoutStream(Arc>); + +impl AsyncStdoutStream { + pub fn new(budget: usize, s: impl AsyncWrite + Send + Sync + 'static) -> Self { + Self(Arc::new(Mutex::new(p2::pipe::AsyncWriteStream::new( + budget, s, + )))) + } +} + +impl StdoutStream for AsyncStdoutStream { + fn p2_stream(&self) -> Box { + Box::new(Self(self.0.clone())) + } + fn async_stream(&self) -> Box { + Box::new(StdioHandle::Ready(self.0.clone())) + } +} + +impl IsTerminal for AsyncStdoutStream { + fn is_terminal(&self) -> bool { + false + } +} + +// This implementation is known to be bogus. All check-writes and writes are +// directed at the same underlying stream. The check-write/write protocol does +// require the size returned by a check-write to be accepted by write, even if +// other side-effects happen between those calls, and this implementation +// permits another view (created by StdoutStream::stream()) of the same +// underlying stream to accept a write which will invalidate a prior +// check-write of another view. +// Ultimately, the Std{in,out}Stream::stream() methods exist because many +// different places in a linked component (which may itself contain many +// modules) may need to access stdio without any coordination to keep those +// accesses all using pointing to the same resource. So, we allow many +// resources to be created. We have the reasonable expectation that programs +// won't attempt to interleave async IO from these disparate uses of stdio. +// If that expectation doesn't turn out to be true, and you find yourself at +// this comment to correct it: sorry about that. +#[async_trait::async_trait] +impl OutputStream for AsyncStdoutStream { + fn check_write(&mut self) -> Result { + match self.0.try_lock() { + Ok(mut stream) => stream.check_write(), + Err(_) => Err(p2::StreamError::trap("concurrent writes are not supported")), + } + } + fn write(&mut self, bytes: Bytes) -> Result<(), p2::StreamError> { + match self.0.try_lock() { + Ok(mut stream) => stream.write(bytes), + Err(_) => Err(p2::StreamError::trap("concurrent writes not supported yet")), + } + } + fn flush(&mut self) -> Result<(), p2::StreamError> { + match self.0.try_lock() { + Ok(mut stream) => stream.flush(), + Err(_) => Err(p2::StreamError::trap( + "concurrent flushes not supported yet", + )), + } + } + async fn cancel(&mut self) { + // Cancel the inner stream if we're the last reference to it: + if let Some(mutex) = Arc::get_mut(&mut self.0) { + match mutex.try_lock() { + Ok(mut stream) => stream.cancel().await, + Err(_) => {} + } + } + } +} + +#[async_trait::async_trait] +impl p2::Pollable for AsyncStdoutStream { + async fn ready(&mut self) { + self.0.lock().await.ready().await + } +} + +impl AsyncWrite for StdioHandle { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + match ready!(self.poll(cx, |i| i.write(Bytes::copy_from_slice(buf)))) { + Some(Ok(())) => Poll::Ready(Ok(buf.len())), + Some(Err(e)) => Poll::Ready(Err(e)), + None => Poll::Ready(Ok(0)), + } + } + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match ready!(self.poll(cx, |i| i.flush())) { + Some(result) => Poll::Ready(result), + None => Poll::Ready(Ok(())), + } + } + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + +/// State necessary for effectively transforming `Arc>` into `Async{Read,Write}`. +/// +/// This is a beast and inefficient. It should get the job done in theory but +/// one must truly ask oneself at some point "but at what cost". +/// +/// More seriously, it's unclear if this is the best way to transform a single +/// `AsyncRead` into a "multiple `AsyncRead`". This certainly is an attempt and +/// the hope is that everything here is private enough that we can refactor as +/// necessary in the future without causing much churn. +enum StdioHandle { + Ready(Arc>), + Locking(Box> + Send + Sync>), + Locked(OwnedMutexGuard), + Closed, +} + +impl StdioHandle +where + S: SharedHandleReady, +{ + fn poll( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + op: impl FnOnce(&mut S) -> p2::StreamResult, + ) -> Poll>> { + // If we don't currently have the lock on this handle, initiate the + // lock acquisition. + if let StdioHandle::Ready(lock) = &*self { + self.set(StdioHandle::Locking(Box::new(lock.clone().lock_owned()))); + } + + // If we're in the process of locking this handle, wait for that to + // finish. + if let Some(lock) = self.as_mut().as_locking() { + let guard = ready!(lock.poll(cx)); + self.set(StdioHandle::Locked(guard)); + } + + let mut guard = match self.as_mut().take_guard() { + Some(guard) => guard, + // If the guard can't be acquired that means that this stream is + // closed, so return that we're ready without filling in data. + None => return Poll::Ready(None), + }; + + // Wait for our locked stream to be ready, resetting to the "locked" + // state if it's not quite ready yet. + match guard.poll_ready(cx) { + Poll::Ready(()) => {} + + // If the read isn't ready yet then restore our "locked" state + // since we haven't finished, then return pending. + Poll::Pending => { + self.set(StdioHandle::Locked(guard)); + return Poll::Pending; + } + } + + // Perform the I/O and delegate on the result. + match op(&mut guard) { + // The I/O succeeded so relinquish the lock on this stream by + // transitioning back to the "Ready" state. + Ok(result) => { + self.set(StdioHandle::Ready(OwnedMutexGuard::mutex(&guard).clone())); + Poll::Ready(Some(Ok(result))) + } + + // The stream is closed, and `take_guard` above already set the + // closed state, so return nothing indicating the closure. + Err(p2::StreamError::Closed) => Poll::Ready(None), + + // The stream failed so propagate the error. Errors should only + // come from the underlying I/O object and thus should cast + // successfully. Additionally `take_guard` replaced our state + // with "closed" above which is the desired state at this point. + Err(p2::StreamError::LastOperationFailed(e)) => { + Poll::Ready(Some(Err(e.downcast().unwrap()))) + } + + // Shouldn't be possible to produce a trap here. + Err(p2::StreamError::Trap(_)) => unreachable!(), + } + } + + fn as_locking( + self: Pin<&mut Self>, + ) -> Option>>> { + // SAFETY: this is a pin-projection from `self` into the `Locking` + // field. + unsafe { + match self.get_unchecked_mut() { + StdioHandle::Locking(future) => Some(Pin::new_unchecked(&mut **future)), + _ => None, + } + } + } + + fn take_guard(self: Pin<&mut Self>) -> Option> { + if !matches!(*self, StdioHandle::Locked(_)) { + return None; + } + // SAFETY: the `Locked` arm is safe to move as it's an invariant of this + // type that it's not pinned. + unsafe { + match mem::replace(self.get_unchecked_mut(), StdioHandle::Closed) { + StdioHandle::Locked(guard) => Some(guard), + _ => unreachable!(), + } + } + } +} diff --git a/crates/wasi/src/cli/mem.rs b/crates/wasi/src/cli/mem.rs new file mode 100644 index 0000000000..5d71cb8763 --- /dev/null +++ b/crates/wasi/src/cli/mem.rs @@ -0,0 +1,34 @@ +use crate::cli::{IsTerminal, StdinStream, StdoutStream}; +use crate::p2; +use tokio::io::{AsyncRead, AsyncWrite}; +use wasmtime_wasi_io::streams::{InputStream, OutputStream}; + +// Implementation for p2::pipe::MemoryInputPipe +impl IsTerminal for p2::pipe::MemoryInputPipe { + fn is_terminal(&self) -> bool { + false + } +} +impl StdinStream for p2::pipe::MemoryInputPipe { + fn p2_stream(&self) -> Box { + Box::new(self.clone()) + } + fn async_stream(&self) -> Box { + Box::new(self.clone()) + } +} + +// Implementation for p2::pipe::MemoryOutputPipe +impl IsTerminal for p2::pipe::MemoryOutputPipe { + fn is_terminal(&self) -> bool { + false + } +} +impl StdoutStream for p2::pipe::MemoryOutputPipe { + fn p2_stream(&self) -> Box { + Box::new(self.clone()) + } + fn async_stream(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/crates/wasi/src/cli/stdout.rs b/crates/wasi/src/cli/stdout.rs new file mode 100644 index 0000000000..a275364980 --- /dev/null +++ b/crates/wasi/src/cli/stdout.rs @@ -0,0 +1,99 @@ +use crate::cli::{IsTerminal, StdoutStream}; +use crate::p2; +use bytes::Bytes; +use tokio::io::AsyncWrite; +use wasmtime_wasi_io::streams::OutputStream; + +// Implementation for tokio::io::Stdout +impl IsTerminal for tokio::io::Stdout { + fn is_terminal(&self) -> bool { + std::io::stdout().is_terminal() + } +} +impl StdoutStream for tokio::io::Stdout { + fn p2_stream(&self) -> Box { + Box::new(StdioOutputStream::Stdout) + } + fn async_stream(&self) -> Box { + Box::new(tokio::io::stdout()) + } +} + +// Implementation for std::io::Stdout +impl IsTerminal for std::io::Stdout { + fn is_terminal(&self) -> bool { + std::io::IsTerminal::is_terminal(self) + } +} +impl StdoutStream for std::io::Stdout { + fn p2_stream(&self) -> Box { + Box::new(StdioOutputStream::Stdout) + } + fn async_stream(&self) -> Box { + Box::new(tokio::io::stdout()) + } +} + +// Implementation for tokio::io::Stderr +impl IsTerminal for tokio::io::Stderr { + fn is_terminal(&self) -> bool { + std::io::stderr().is_terminal() + } +} +impl StdoutStream for tokio::io::Stderr { + fn p2_stream(&self) -> Box { + Box::new(StdioOutputStream::Stderr) + } + fn async_stream(&self) -> Box { + Box::new(tokio::io::stderr()) + } +} + +// Implementation for std::io::Stderr +impl IsTerminal for std::io::Stderr { + fn is_terminal(&self) -> bool { + std::io::IsTerminal::is_terminal(self) + } +} +impl StdoutStream for std::io::Stderr { + fn p2_stream(&self) -> Box { + Box::new(StdioOutputStream::Stderr) + } + fn async_stream(&self) -> Box { + Box::new(tokio::io::stderr()) + } +} + +enum StdioOutputStream { + Stdout, + Stderr, +} + +impl OutputStream for StdioOutputStream { + fn write(&mut self, bytes: Bytes) -> p2::StreamResult<()> { + use std::io::Write; + match self { + StdioOutputStream::Stdout => std::io::stdout().write_all(&bytes), + StdioOutputStream::Stderr => std::io::stderr().write_all(&bytes), + } + .map_err(|e| p2::StreamError::LastOperationFailed(anyhow::anyhow!(e))) + } + + fn flush(&mut self) -> p2::StreamResult<()> { + use std::io::Write; + match self { + StdioOutputStream::Stdout => std::io::stdout().flush(), + StdioOutputStream::Stderr => std::io::stderr().flush(), + } + .map_err(|e| p2::StreamError::LastOperationFailed(anyhow::anyhow!(e))) + } + + fn check_write(&mut self) -> p2::StreamResult { + Ok(1024 * 1024) + } +} + +#[async_trait::async_trait] +impl p2::Pollable for StdioOutputStream { + async fn ready(&mut self) {} +} diff --git a/crates/wasi/src/p2/stdio/worker_thread_stdin.rs b/crates/wasi/src/cli/worker_thread_stdin.rs similarity index 54% rename from crates/wasi/src/p2/stdio/worker_thread_stdin.rs rename to crates/wasi/src/cli/worker_thread_stdin.rs index 7557e561d4..10400cd121 100644 --- a/crates/wasi/src/p2/stdio/worker_thread_stdin.rs +++ b/crates/wasi/src/cli/worker_thread_stdin.rs @@ -23,18 +23,51 @@ //! This module is one that's likely to change over time though as new systems //! are encountered along with preexisting bugs. -use crate::cli::IsTerminal; -use crate::p2::stdio::StdinStream; +use crate::cli::{IsTerminal, StdinStream}; use bytes::{Bytes, BytesMut}; use std::io::Read; use std::mem; +use std::pin::Pin; use std::sync::{Condvar, Mutex, OnceLock}; +use std::task::{Context, Poll}; +use tokio::io::{self, AsyncRead, ReadBuf}; use tokio::sync::Notify; +use tokio::sync::futures::Notified; use wasmtime_wasi_io::{ poll::Pollable, streams::{InputStream, StreamError}, }; +// Implementation for tokio::io::Stdin +impl IsTerminal for tokio::io::Stdin { + fn is_terminal(&self) -> bool { + std::io::stdin().is_terminal() + } +} +impl StdinStream for tokio::io::Stdin { + fn p2_stream(&self) -> Box { + Box::new(WasiStdin) + } + fn async_stream(&self) -> Box { + Box::new(WasiStdinAsyncRead::Ready) + } +} + +// Implementation for std::io::Stdin +impl IsTerminal for std::io::Stdin { + fn is_terminal(&self) -> bool { + std::io::IsTerminal::is_terminal(self) + } +} +impl StdinStream for std::io::Stdin { + fn p2_stream(&self) -> Box { + Box::new(WasiStdin) + } + fn async_stream(&self) -> Box { + Box::new(WasiStdinAsyncRead::Ready) + } +} + #[derive(Default)] struct GlobalStdin { state: Mutex, @@ -99,32 +132,10 @@ fn create() -> GlobalStdin { GlobalStdin::default() } -/// Only public interface is the [`InputStream`] impl. -#[derive(Clone)] -pub struct Stdin; - -/// Returns a stream that represents the host's standard input. -/// -/// Suitable for passing to -/// [`WasiCtxBuilder::stdin`](crate::p2::WasiCtxBuilder::stdin). -pub fn stdin() -> Stdin { - Stdin -} - -impl StdinStream for Stdin { - fn stream(&self) -> Box { - Box::new(Stdin) - } -} - -impl IsTerminal for Stdin { - fn is_terminal(&self) -> bool { - std::io::stdin().is_terminal() - } -} +struct WasiStdin; #[async_trait::async_trait] -impl InputStream for Stdin { +impl InputStream for WasiStdin { fn read(&mut self, size: usize) -> Result { let g = GlobalStdin::get(); let mut locked = g.state.lock().unwrap(); @@ -157,7 +168,7 @@ impl InputStream for Stdin { } #[async_trait::async_trait] -impl Pollable for Stdin { +impl Pollable for WasiStdin { async fn ready(&mut self) { let g = GlobalStdin::get(); @@ -180,3 +191,94 @@ impl Pollable for Stdin { notified.await; } } + +enum WasiStdinAsyncRead { + Ready, + Waiting(Notified<'static>), +} + +impl AsyncRead for WasiStdinAsyncRead { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let g = GlobalStdin::get(); + + // Perform everything below in a `loop` to handle the case that a read + // was stolen by another thread, for example, or perhaps a spurious + // notification to `Notified`. + loop { + // If we were previously blocked on reading a "ready" notification, + // wait for that notification to complete. + if let Some(notified) = self.as_mut().notified_future() { + match notified.poll(cx) { + Poll::Ready(()) => self.set(WasiStdinAsyncRead::Ready), + Poll::Pending => break Poll::Pending, + } + } + + assert!(matches!(*self, WasiStdinAsyncRead::Ready)); + + // Once we're in the "ready" state then take a look at the global + // state of stdin. + let mut locked = g.state.lock().unwrap(); + match mem::replace(&mut *locked, StdinState::ReadRequested) { + // If data is available then drain what we can into `buf`. + StdinState::Data(mut data) => { + let size = data.len().min(buf.remaining()); + let bytes = data.split_to(size); + *locked = if data.is_empty() { + StdinState::ReadNotRequested + } else { + StdinState::Data(data) + }; + buf.put_slice(&bytes); + break Poll::Ready(Ok(())); + } + + // If stdin failed to be read then we fail with that error and + // transition to "closed" + StdinState::Error(e) => { + *locked = StdinState::Closed; + break Poll::Ready(Err(e)); + } + + // If stdin is closed, keep it closed. + StdinState::Closed => { + *locked = StdinState::Closed; + break Poll::Ready(Ok(())); + } + + // For these states we indicate that a read is requested, if it + // wasn't previously requested, and then we transition to + // `Waiting` below by falling through outside this `match`. + StdinState::ReadNotRequested => { + g.read_requested.notify_one(); + } + StdinState::ReadRequested => {} + } + + self.set(WasiStdinAsyncRead::Waiting(g.read_completed.notified())); + + // Intentionally drop the lock after the `notified()` future + // creation just above as to work correctly this needs to happen + // within the lock. + drop(locked); + } + } +} + +impl WasiStdinAsyncRead { + fn notified_future(self: Pin<&mut Self>) -> Option>> { + // SAFETY: this is a pin-projection from `self` to the field `Notified` + // internally. Given that `self` is pinned it should be safe to acquire + // a pinned version of the internal field. + unsafe { + match self.get_unchecked_mut() { + WasiStdinAsyncRead::Ready => None, + WasiStdinAsyncRead::Waiting(notified) => Some(Pin::new_unchecked(notified)), + } + } + } +} diff --git a/crates/wasi/src/clocks.rs b/crates/wasi/src/clocks.rs index 61fd630dea..f8ce9eb416 100644 --- a/crates/wasi/src/clocks.rs +++ b/crates/wasi/src/clocks.rs @@ -1,6 +1,13 @@ -use cap_std::time::{Duration, Instant, SystemClock}; +use cap_std::time::{Duration, Instant, SystemClock, SystemTime}; use cap_std::{AmbientAuthority, ambient_authority}; use cap_time_ext::{MonotonicClockExt as _, SystemClockExt as _}; +use wasmtime::component::{HasData, ResourceTable}; + +pub(crate) struct WasiClocks; + +impl HasData for WasiClocks { + type Data<'a> = WasiClocksCtxView<'a>; +} pub struct WasiClocksCtx { pub wall_clock: Box, @@ -17,13 +24,12 @@ impl Default for WasiClocksCtx { } pub trait WasiClocksView: Send { - fn clocks(&mut self) -> &mut WasiClocksCtx; + fn clocks(&mut self) -> WasiClocksCtxView<'_>; } -impl WasiClocksView for WasiClocksCtx { - fn clocks(&mut self) -> &mut WasiClocksCtx { - self - } +pub struct WasiClocksCtxView<'a> { + pub ctx: &'a mut WasiClocksCtx, + pub table: &'a mut ResourceTable, } pub trait HostWallClock: Send { @@ -116,3 +122,22 @@ pub fn monotonic_clock() -> Box { pub fn wall_clock() -> Box { Box::new(WallClock::default()) } + +pub(crate) struct Datetime { + pub seconds: u64, + pub nanoseconds: u32, +} + +impl TryFrom for Datetime { + type Error = wasmtime::Error; + + fn try_from(time: SystemTime) -> Result { + let duration = + time.duration_since(SystemTime::from_std(std::time::SystemTime::UNIX_EPOCH))?; + + Ok(Self { + seconds: duration.as_secs(), + nanoseconds: duration.subsec_nanos(), + }) + } +} diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs index a13adf7d6a..f5b07d7bbd 100644 --- a/crates/wasi/src/ctx.rs +++ b/crates/wasi/src/ctx.rs @@ -1,32 +1,50 @@ -use crate::cli::WasiCliCtx; +use crate::cli::{StdinStream, StdoutStream, WasiCliCtx}; use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx}; +use crate::filesystem::{Dir, WasiFilesystemCtx}; use crate::random::WasiRandomCtx; use crate::sockets::{SocketAddrCheck, SocketAddrUse, WasiSocketsCtx}; +use crate::{DirPerms, FilePerms, OpenMode}; +use anyhow::Result; use cap_rand::RngCore; +use cap_std::ambient_authority; use std::future::Future; +use std::mem; use std::net::SocketAddr; +use std::path::Path; use std::pin::Pin; -use std::sync::Arc; +use tokio::io::{stderr, stdin, stdout}; -/// Builder-style structure used to create a WASI context. +/// Builder-style structure used to create a [`WasiCtx`]. /// -/// This type is used to create a WASI context that is considered per-[`Store`] -/// state. -/// This is a low-level abstraction, users of this crate are expected to use it via -/// builders specific to WASI version used, for example, -/// [p2::WasiCtxBuilder](crate::p2::WasiCtxBuilder) +/// This type is used to create a [`WasiCtx`] that is considered per-[`Store`] +/// state. The [`build`][WasiCtxBuilder::build] method is used to finish the +/// building process and produce a finalized [`WasiCtx`]. +/// +/// # Examples +/// +/// ``` +/// use wasmtime_wasi::WasiCtx; +/// +/// let mut wasi = WasiCtx::builder(); +/// wasi.arg("./foo.wasm"); +/// wasi.arg("--help"); +/// wasi.env("FOO", "bar"); +/// +/// let wasi: WasiCtx = wasi.build(); +/// ``` /// /// [`Store`]: wasmtime::Store #[derive(Default)] -pub(crate) struct WasiCtxBuilder { - pub(crate) cli: WasiCliCtx, - pub(crate) clocks: WasiClocksCtx, - pub(crate) random: WasiRandomCtx, - pub(crate) sockets: WasiSocketsCtx, - pub(crate) allow_blocking_current_thread: bool, +pub struct WasiCtxBuilder { + cli: WasiCliCtx, + clocks: WasiClocksCtx, + filesystem: WasiFilesystemCtx, + random: WasiRandomCtx, + sockets: WasiSocketsCtx, + built: bool, } -impl WasiCtxBuilder { +impl WasiCtxBuilder { /// Creates a builder for a new context with default parameters set. /// /// The current defaults are: @@ -40,49 +58,84 @@ impl WasiCtxBuilder { /// * RNGs are all initialized with random state and suitable generator /// quality to satisfy the requirements of WASI APIs. /// * TCP/UDP are allowed but all addresses are denied by default. - /// * IP name lookup is denied by default. + /// * `wasi:sockets/ip-name-lookup` is denied by default. /// /// These defaults can all be updated via the various builder configuration /// methods below. - pub(crate) fn new(stdin: I, stdout: O, stderr: O) -> Self { - let cli = WasiCliCtx { - environment: Vec::default(), - arguments: Vec::default(), - initial_cwd: None, - stdin, - stdout, - stderr, - }; - let clocks = WasiClocksCtx::default(); - let random = WasiRandomCtx::default(); - let sockets = WasiSocketsCtx::default(); - Self { - cli, - clocks, - random, - sockets, - allow_blocking_current_thread: false, - } + pub fn new() -> Self { + Self::default() } /// Provides a custom implementation of stdin to use. - pub fn stdin(&mut self, stdin: I) -> &mut Self { - self.cli.stdin = stdin; + /// + /// By default stdin is closed but an example of using the host's native + /// stdin looks like: + /// + /// ``` + /// use wasmtime_wasi::WasiCtx; + /// use wasmtime_wasi::cli::stdin; + /// + /// let mut wasi = WasiCtx::builder(); + /// wasi.stdin(stdin()); + /// ``` + /// + /// Note that inheriting the process's stdin can also be done through + /// [`inherit_stdin`](WasiCtxBuilder::inherit_stdin). + pub fn stdin(&mut self, stdin: impl StdinStream + 'static) -> &mut Self { + self.cli.stdin = Box::new(stdin); self } /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout. - pub fn stdout(&mut self, stdout: O) -> &mut Self { - self.cli.stdout = stdout; + pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) -> &mut Self { + self.cli.stdout = Box::new(stdout); self } /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr. - pub fn stderr(&mut self, stderr: O) -> &mut Self { - self.cli.stderr = stderr; + pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) -> &mut Self { + self.cli.stderr = Box::new(stderr); self } + /// Configures this context's stdin stream to read the host process's + /// stdin. + /// + /// Note that concurrent reads of stdin can produce surprising results so + /// when using this it's typically best to have a single wasm instance in + /// the process using this. + pub fn inherit_stdin(&mut self) -> &mut Self { + self.stdin(stdin()) + } + + /// Configures this context's stdout stream to write to the host process's + /// stdout. + /// + /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) + /// multiple instances printing to stdout works well. + pub fn inherit_stdout(&mut self) -> &mut Self { + self.stdout(stdout()) + } + + /// Configures this context's stderr stream to write to the host process's + /// stderr. + /// + /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) + /// multiple instances printing to stderr works well. + pub fn inherit_stderr(&mut self) -> &mut Self { + self.stderr(stderr()) + } + + /// Configures all of stdin, stdout, and stderr to be inherited from the + /// host process. + /// + /// See [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) for some rationale + /// on why this should only be done in situations of + /// one-instance-per-process. + pub fn inherit_stdio(&mut self) -> &mut Self { + self.inherit_stdin().inherit_stdout().inherit_stderr() + } + /// Configures whether or not blocking operations made through this /// `WasiCtx` are allowed to block the current thread. /// @@ -110,7 +163,7 @@ impl WasiCtxBuilder { /// /// [`Config::async_support`]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support pub fn allow_blocking_current_thread(&mut self, enable: bool) -> &mut Self { - self.allow_blocking_current_thread = enable; + self.filesystem.allow_blocking_current_thread = enable; self } @@ -121,6 +174,18 @@ impl WasiCtxBuilder { /// /// At this time environment variables are not deduplicated and if the same /// key is set twice then the guest will see two entries for the same key. + /// + /// # Examples + /// + /// ``` + /// use wasmtime_wasi::WasiCtxBuilder; + /// + /// let mut wasi = WasiCtxBuilder::new(); + /// wasi.envs(&[ + /// ("FOO", "bar"), + /// ("HOME", "/somewhere"), + /// ]); + /// ``` pub fn envs(&mut self, env: &[(impl AsRef, impl AsRef)]) -> &mut Self { self.cli.environment.extend( env.iter() @@ -133,6 +198,15 @@ impl WasiCtxBuilder { /// /// At this time environment variables are not deduplicated and if the same /// key is set twice then the guest will see two entries for the same key. + /// + /// # Examples + /// + /// ``` + /// use wasmtime_wasi::WasiCtxBuilder; + /// + /// let mut wasi = WasiCtxBuilder::new(); + /// wasi.env("FOO", "bar"); + /// ``` pub fn env(&mut self, k: impl AsRef, v: impl AsRef) -> &mut Self { self.cli .environment @@ -169,6 +243,80 @@ impl WasiCtxBuilder { self.args(&std::env::args().collect::>()) } + /// Configures a "preopened directory" to be available to WebAssembly. + /// + /// By default WebAssembly does not have access to the filesystem because + /// there are no preopened directories. All filesystem operations, such as + /// opening a file, are done through a preexisting handle. This means that + /// to provide WebAssembly access to a directory it must be configured + /// through this API. + /// + /// WASI will also prevent access outside of files provided here. For + /// example `..` can't be used to traverse up from the `host_path` provided here + /// to the containing directory. + /// + /// * `host_path` - a path to a directory on the host to open and make + /// accessible to WebAssembly. Note that the name of this directory in the + /// guest is configured with `guest_path` below. + /// * `guest_path` - the name of the preopened directory from WebAssembly's + /// perspective. Note that this does not need to match the host's name for + /// the directory. + /// * `dir_perms` - this is the permissions that wasm will have to operate on + /// `guest_path`. This can be used, for example, to provide readonly access to a + /// directory. + /// * `file_perms` - similar to `dir_perms` but corresponds to the maximum set + /// of permissions that can be used for any file in this directory. + /// + /// # Errors + /// + /// This method will return an error if `host_path` cannot be opened. + /// + /// # Examples + /// + /// ``` + /// use wasmtime_wasi::WasiCtxBuilder; + /// use wasmtime_wasi::{DirPerms, FilePerms}; + /// + /// # fn main() {} + /// # fn foo() -> wasmtime::Result<()> { + /// let mut wasi = WasiCtxBuilder::new(); + /// + /// // Make `./host-directory` available in the guest as `.` + /// wasi.preopened_dir("./host-directory", ".", DirPerms::all(), FilePerms::all()); + /// + /// // Make `./readonly` available in the guest as `./ro` + /// wasi.preopened_dir("./readonly", "./ro", DirPerms::READ, FilePerms::READ); + /// # Ok(()) + /// # } + /// ``` + pub fn preopened_dir( + &mut self, + host_path: impl AsRef, + guest_path: impl AsRef, + dir_perms: DirPerms, + file_perms: FilePerms, + ) -> Result<&mut Self> { + let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?; + let mut open_mode = OpenMode::empty(); + if dir_perms.contains(DirPerms::READ) { + open_mode |= OpenMode::READ; + } + if dir_perms.contains(DirPerms::MUTATE) { + open_mode |= OpenMode::WRITE; + } + self.filesystem.preopens.push(( + Dir::new( + dir, + dir_perms, + file_perms, + open_mode, + self.filesystem.allow_blocking_current_thread, + ), + guest_path.as_ref().to_owned(), + )); + Ok(self) + } + /// Set the generator for the `wasi:random/random` number generator to the /// custom generator specified. /// @@ -240,7 +388,7 @@ impl WasiCtxBuilder { + Sync + 'static, { - self.sockets.socket_addr_check = SocketAddrCheck(Arc::new(check)); + self.sockets.socket_addr_check = SocketAddrCheck::new(check); self } @@ -269,4 +417,115 @@ impl WasiCtxBuilder { self.sockets.allowed_network_uses.tcp = enable; self } + + /// Uses the configured context so far to construct the final [`WasiCtx`]. + /// + /// Note that each `WasiCtxBuilder` can only be used to "build" once, and + /// calling this method twice will panic. + /// + /// # Panics + /// + /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be + /// used to create only a single [`WasiCtx`]. Repeated usage of this method + /// is not allowed and should use a second builder instead. + pub fn build(&mut self) -> WasiCtx { + assert!(!self.built); + + let Self { + cli, + clocks, + filesystem, + random, + sockets, + built: _, + } = mem::replace(self, Self::new()); + self.built = true; + + WasiCtx { + cli, + clocks, + filesystem, + random, + sockets, + } + } + /// Builds a WASIp1 context instead of a [`WasiCtx`]. + /// + /// This method is the same as [`build`](WasiCtxBuilder::build) but it + /// creates a [`WasiP1Ctx`] instead. This is intended for use with the + /// [`p1`] module of this crate + /// + /// [`WasiP1Ctx`]: crate::p1::WasiP1Ctx + /// [`p1`]: crate::p1 + /// + /// # Panics + /// + /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be + /// used to create only a single [`WasiCtx`] or [`WasiP1Ctx`]. Repeated + /// usage of this method is not allowed and should use a second builder + /// instead. + #[cfg(feature = "p1")] + pub fn build_p1(&mut self) -> crate::p1::WasiP1Ctx { + let wasi = self.build(); + crate::p1::WasiP1Ctx::new(wasi) + } +} + +/// Per-[`Store`] state which holds state necessary to implement WASI from this +/// crate. +/// +/// This structure is created through [`WasiCtxBuilder`] and is stored within +/// the `T` of [`Store`][`Store`]. Access to the structure is provided +/// through the [`WasiView`](crate::WasiView) trait as an implementation on `T`. +/// +/// Note that this structure itself does not have any accessors, it's here for +/// internal use within the `wasmtime-wasi` crate's implementation of +/// bindgen-generated traits. +/// +/// [`Store`]: wasmtime::Store +/// +/// # Example +/// +/// ``` +/// use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxView, WasiView, WasiCtxBuilder}; +/// +/// struct MyState { +/// ctx: WasiCtx, +/// table: ResourceTable, +/// } +/// +/// impl WasiView for MyState { +/// fn ctx(&mut self) -> WasiCtxView<'_> { +/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +/// } +/// } +/// +/// impl MyState { +/// fn new() -> MyState { +/// let mut wasi = WasiCtxBuilder::new(); +/// wasi.arg("./foo.wasm"); +/// wasi.arg("--help"); +/// wasi.env("FOO", "bar"); +/// +/// MyState { +/// ctx: wasi.build(), +/// table: ResourceTable::new(), +/// } +/// } +/// } +/// ``` +#[derive(Default)] +pub struct WasiCtx { + pub(crate) cli: WasiCliCtx, + pub(crate) clocks: WasiClocksCtx, + pub(crate) filesystem: WasiFilesystemCtx, + pub(crate) random: WasiRandomCtx, + pub(crate) sockets: WasiSocketsCtx, +} + +impl WasiCtx { + /// Convenience function for calling [`WasiCtxBuilder::new`]. + pub fn builder() -> WasiCtxBuilder { + WasiCtxBuilder::new() + } } diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs index c363a49ebe..8303d9ee4d 100644 --- a/crates/wasi/src/filesystem.rs +++ b/crates/wasi/src/filesystem.rs @@ -1,3 +1,34 @@ +use crate::clocks::Datetime; +use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking}; +use anyhow::Context as _; +use cap_fs_ext::{FileTypeExt as _, MetadataExt as _}; +use fs_set_times::SystemTimeSpec; +use std::collections::hash_map; +use std::sync::Arc; +use tracing::debug; +use wasmtime::component::{HasData, Resource, ResourceTable}; + +pub(crate) struct WasiFilesystem; + +impl HasData for WasiFilesystem { + type Data<'a> = WasiFilesystemCtxView<'a>; +} + +#[derive(Clone, Default)] +pub struct WasiFilesystemCtx { + pub allow_blocking_current_thread: bool, + pub preopens: Vec<(Dir, String)>, +} + +pub struct WasiFilesystemCtxView<'a> { + pub ctx: &'a mut WasiFilesystemCtx, + pub table: &'a mut ResourceTable, +} + +pub trait WasiFilesystemView: Send { + fn filesystem(&mut self) -> WasiFilesystemCtxView<'_>; +} + bitflags::bitflags! { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct FilePerms: usize { @@ -30,3 +61,1055 @@ bitflags::bitflags! { const MUTATE = 0b10; } } + +bitflags::bitflags! { + /// Flags determining the method of how paths are resolved. + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub(crate) struct PathFlags: usize { + /// This directory can be read, for example its entries can be iterated + /// over and files can be opened. + const SYMLINK_FOLLOW = 0b1; + } +} + +bitflags::bitflags! { + /// Open flags used by `open-at`. + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub(crate) struct OpenFlags: usize { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + const CREATE = 0b1; + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + const DIRECTORY = 0b10; + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + const EXCLUSIVE = 0b100; + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + const TRUNCATE = 0b1000; + } +} + +bitflags::bitflags! { + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub(crate) struct DescriptorFlags: usize { + /// Read mode: Data can be read. + const READ = 0b1; + /// Write mode: Data can be written to. + const WRITE = 0b10; + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + const FILE_INTEGRITY_SYNC = 0b100; + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + const DATA_INTEGRITY_SYNC = 0b1000; + /// Requests that reads be performed at the same level of integrity + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + const REQUESTED_WRITE_SYNC = 0b10000; + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + const MUTATE_DIRECTORY = 0b100000; + } +} + +/// Error codes returned by functions, similar to `errno` in POSIX. +/// Not all of these error codes are returned by the functions provided by this +/// API; some are used in higher-level library layers, and others are provided +/// merely for alignment with POSIX. +#[cfg_attr( + windows, + expect(dead_code, reason = "on Windows, some of these are not used") +)] +pub(crate) enum ErrorCode { + /// Permission denied, similar to `EACCES` in POSIX. + Access, + /// Connection already in progress, similar to `EALREADY` in POSIX. + Already, + /// Bad descriptor, similar to `EBADF` in POSIX. + BadDescriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + Busy, + /// File exists, similar to `EEXIST` in POSIX. + Exist, + /// File too large, similar to `EFBIG` in POSIX. + FileTooLarge, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + IllegalByteSequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + InProgress, + /// Interrupted function, similar to `EINTR` in POSIX. + Interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + Invalid, + /// I/O error, similar to `EIO` in POSIX. + Io, + /// Is a directory, similar to `EISDIR` in POSIX. + IsDirectory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + Loop, + /// Too many links, similar to `EMLINK` in POSIX. + TooManyLinks, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + NameTooLong, + /// No such file or directory, similar to `ENOENT` in POSIX. + NoEntry, + /// Not enough space, similar to `ENOMEM` in POSIX. + InsufficientMemory, + /// No space left on device, similar to `ENOSPC` in POSIX. + InsufficientSpace, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + NotDirectory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + NotEmpty, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + Unsupported, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + Overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + NotPermitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + Pipe, + /// Invalid seek, similar to `ESPIPE` in POSIX. + InvalidSeek, +} + +fn datetime_from(t: std::time::SystemTime) -> Datetime { + // FIXME make this infallible or handle errors properly + Datetime::try_from(cap_std::time::SystemTime::from_std(t)).unwrap() +} + +/// The type of a filesystem object referenced by a descriptor. +/// +/// Note: This was called `filetype` in earlier versions of WASI. +pub(crate) enum DescriptorType { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + Unknown, + /// The descriptor refers to a block device inode. + BlockDevice, + /// The descriptor refers to a character device inode. + CharacterDevice, + /// The descriptor refers to a directory inode. + Directory, + /// The file refers to a symbolic link inode. + SymbolicLink, + /// The descriptor refers to a regular file inode. + RegularFile, +} + +impl From for DescriptorType { + fn from(ft: cap_std::fs::FileType) -> Self { + if ft.is_dir() { + DescriptorType::Directory + } else if ft.is_symlink() { + DescriptorType::SymbolicLink + } else if ft.is_block_device() { + DescriptorType::BlockDevice + } else if ft.is_char_device() { + DescriptorType::CharacterDevice + } else if ft.is_file() { + DescriptorType::RegularFile + } else { + DescriptorType::Unknown + } + } +} + +/// File attributes. +/// +/// Note: This was called `filestat` in earlier versions of WASI. +pub(crate) struct DescriptorStat { + /// File type. + pub type_: DescriptorType, + /// Number of hard links to the file. + pub link_count: u64, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + pub size: u64, + /// Last data access timestamp. + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + pub data_access_timestamp: Option, + /// Last data modification timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + pub data_modification_timestamp: Option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + pub status_change_timestamp: Option, +} + +impl From for DescriptorStat { + fn from(meta: cap_std::fs::Metadata) -> Self { + Self { + type_: meta.file_type().into(), + link_count: meta.nlink(), + size: meta.len(), + data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(), + data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(), + status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(), + } + } +} + +/// A 128-bit hash value, split into parts because wasm doesn't have a +/// 128-bit integer type. +pub(crate) struct MetadataHashValue { + /// 64 bits of a 128-bit hash value. + pub lower: u64, + /// Another 64 bits of a 128-bit hash value. + pub upper: u64, +} + +impl From<&cap_std::fs::Metadata> for MetadataHashValue { + fn from(meta: &cap_std::fs::Metadata) -> Self { + use cap_fs_ext::MetadataExt; + // Without incurring any deps, std provides us with a 64 bit hash + // function: + use std::hash::Hasher; + // Note that this means that the metadata hash (which becomes a preview1 ino) may + // change when a different rustc release is used to build this host implementation: + let mut hasher = hash_map::DefaultHasher::new(); + hasher.write_u64(meta.dev()); + hasher.write_u64(meta.ino()); + let lower = hasher.finish(); + // MetadataHashValue has a pair of 64-bit members for representing a + // single 128-bit number. However, we only have 64 bits of entropy. To + // synthesize the upper 64 bits, lets xor the lower half with an arbitrary + // constant, in this case the 64 bit integer corresponding to the IEEE + // double representation of (a number as close as possible to) pi. + // This seems better than just repeating the same bits in the upper and + // lower parts outright, which could make folks wonder if the struct was + // mangled in the ABI, or worse yet, lead to consumers of this interface + // expecting them to be equal. + let upper = lower ^ 4614256656552045848u64; + Self { lower, upper } + } +} + +#[cfg(unix)] +fn from_raw_os_error(err: Option) -> Option { + use rustix::io::Errno as RustixErrno; + if err.is_none() { + return None; + } + Some(match RustixErrno::from_raw_os_error(err.unwrap()) { + RustixErrno::PIPE => ErrorCode::Pipe, + RustixErrno::PERM => ErrorCode::NotPermitted, + RustixErrno::NOENT => ErrorCode::NoEntry, + RustixErrno::NOMEM => ErrorCode::InsufficientMemory, + RustixErrno::IO => ErrorCode::Io, + RustixErrno::BADF => ErrorCode::BadDescriptor, + RustixErrno::BUSY => ErrorCode::Busy, + RustixErrno::ACCESS => ErrorCode::Access, + RustixErrno::NOTDIR => ErrorCode::NotDirectory, + RustixErrno::ISDIR => ErrorCode::IsDirectory, + RustixErrno::INVAL => ErrorCode::Invalid, + RustixErrno::EXIST => ErrorCode::Exist, + RustixErrno::FBIG => ErrorCode::FileTooLarge, + RustixErrno::NOSPC => ErrorCode::InsufficientSpace, + RustixErrno::SPIPE => ErrorCode::InvalidSeek, + RustixErrno::MLINK => ErrorCode::TooManyLinks, + RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong, + RustixErrno::NOTEMPTY => ErrorCode::NotEmpty, + RustixErrno::LOOP => ErrorCode::Loop, + RustixErrno::OVERFLOW => ErrorCode::Overflow, + RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence, + RustixErrno::NOTSUP => ErrorCode::Unsupported, + RustixErrno::ALREADY => ErrorCode::Already, + RustixErrno::INPROGRESS => ErrorCode::InProgress, + RustixErrno::INTR => ErrorCode::Interrupted, + + // On some platforms, these have the same value as other errno values. + #[allow(unreachable_patterns, reason = "see comment")] + RustixErrno::OPNOTSUPP => ErrorCode::Unsupported, + + _ => return None, + }) +} + +#[cfg(windows)] +fn from_raw_os_error(raw_os_error: Option) -> Option { + use windows_sys::Win32::Foundation; + Some(match raw_os_error.map(|code| code as u32) { + Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry, + Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry, + Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access, + Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access, + Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted, + Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor, + Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry, + Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory, + Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory, + Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty, + Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy, + Some(Foundation::ERROR_BUSY) => ErrorCode::Busy, + Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported, + Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist, + Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe, + Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong, + Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid, + Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid, + Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory, + Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist, + Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop, + Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory, + _ => return None, + }) +} + +impl<'a> From<&'a std::io::Error> for ErrorCode { + fn from(err: &'a std::io::Error) -> ErrorCode { + match from_raw_os_error(err.raw_os_error()) { + Some(errno) => errno, + None => { + debug!("unknown raw os error: {err}"); + match err.kind() { + std::io::ErrorKind::NotFound => ErrorCode::NoEntry, + std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted, + std::io::ErrorKind::AlreadyExists => ErrorCode::Exist, + std::io::ErrorKind::InvalidInput => ErrorCode::Invalid, + _ => ErrorCode::Io, + } + } + } + } +} + +impl From for ErrorCode { + fn from(err: std::io::Error) -> ErrorCode { + ErrorCode::from(&err) + } +} + +#[derive(Clone)] +pub enum Descriptor { + File(File), + Dir(Dir), +} + +impl Descriptor { + pub(crate) fn file(&self) -> Result<&File, ErrorCode> { + match self { + Descriptor::File(f) => Ok(f), + Descriptor::Dir(_) => Err(ErrorCode::BadDescriptor), + } + } + + pub(crate) fn dir(&self) -> Result<&Dir, ErrorCode> { + match self { + Descriptor::Dir(d) => Ok(d), + Descriptor::File(_) => Err(ErrorCode::NotDirectory), + } + } + + async fn get_metadata(&self) -> std::io::Result { + match self { + Self::File(f) => { + // No permissions check on metadata: if opened, allowed to stat it + f.run_blocking(|f| f.metadata()).await + } + Self::Dir(d) => { + // No permissions check on metadata: if opened, allowed to stat it + d.run_blocking(|d| d.dir_metadata()).await + } + } + } + + pub(crate) async fn sync_data(&self) -> Result<(), ErrorCode> { + match self { + Self::File(f) => { + match f.run_blocking(|f| f.sync_data()).await { + Ok(()) => Ok(()), + // On windows, `sync_data` uses `FileFlushBuffers` which fails with + // `ERROR_ACCESS_DENIED` if the file is not upen for writing. Ignore + // this error, for POSIX compatibility. + #[cfg(windows)] + Err(err) + if err.raw_os_error() + == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) => + { + Ok(()) + } + Err(err) => Err(err.into()), + } + } + Self::Dir(d) => { + d.run_blocking(|d| { + let d = d.open(std::path::Component::CurDir)?; + d.sync_data()?; + Ok(()) + }) + .await + } + } + } + + pub(crate) async fn get_flags(&self) -> Result { + use system_interface::fs::{FdFlags, GetSetFdFlags}; + + fn get_from_fdflags(flags: FdFlags) -> DescriptorFlags { + let mut out = DescriptorFlags::empty(); + if flags.contains(FdFlags::DSYNC) { + out |= DescriptorFlags::REQUESTED_WRITE_SYNC; + } + if flags.contains(FdFlags::RSYNC) { + out |= DescriptorFlags::DATA_INTEGRITY_SYNC; + } + if flags.contains(FdFlags::SYNC) { + out |= DescriptorFlags::FILE_INTEGRITY_SYNC; + } + out + } + match self { + Self::File(f) => { + let flags = f.run_blocking(|f| f.get_fd_flags()).await?; + let mut flags = get_from_fdflags(flags); + if f.open_mode.contains(OpenMode::READ) { + flags |= DescriptorFlags::READ; + } + if f.open_mode.contains(OpenMode::WRITE) { + flags |= DescriptorFlags::WRITE; + } + Ok(flags) + } + Self::Dir(d) => { + let flags = d.run_blocking(|d| d.get_fd_flags()).await?; + let mut flags = get_from_fdflags(flags); + if d.open_mode.contains(OpenMode::READ) { + flags |= DescriptorFlags::READ; + } + if d.open_mode.contains(OpenMode::WRITE) { + flags |= DescriptorFlags::MUTATE_DIRECTORY; + } + Ok(flags) + } + } + } + + pub(crate) async fn get_type(&self) -> Result { + match self { + Self::File(f) => { + let meta = f.run_blocking(|f| f.metadata()).await?; + Ok(meta.file_type().into()) + } + Self::Dir(_) => Ok(DescriptorType::Directory), + } + } + + pub(crate) async fn set_times( + &self, + atim: Option, + mtim: Option, + ) -> Result<(), ErrorCode> { + use fs_set_times::SetTimes as _; + match self { + Self::File(f) => { + if !f.perms.contains(FilePerms::WRITE) { + return Err(ErrorCode::NotPermitted); + } + f.run_blocking(|f| f.set_times(atim, mtim)).await?; + Ok(()) + } + Self::Dir(d) => { + if !d.perms.contains(DirPerms::MUTATE) { + return Err(ErrorCode::NotPermitted); + } + d.run_blocking(|d| d.set_times(atim, mtim)).await?; + Ok(()) + } + } + } + + pub(crate) async fn sync(&self) -> Result<(), ErrorCode> { + match self { + Self::File(f) => { + match f.run_blocking(|f| f.sync_all()).await { + Ok(()) => Ok(()), + // On windows, `sync_data` uses `FileFlushBuffers` which fails with + // `ERROR_ACCESS_DENIED` if the file is not upen for writing. Ignore + // this error, for POSIX compatibility. + #[cfg(windows)] + Err(err) + if err.raw_os_error() + == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) => + { + Ok(()) + } + Err(err) => Err(err.into()), + } + } + Self::Dir(d) => { + d.run_blocking(|d| { + let d = d.open(std::path::Component::CurDir)?; + d.sync_all()?; + Ok(()) + }) + .await + } + } + } + + pub(crate) async fn stat(&self) -> Result { + match self { + Self::File(f) => { + // No permissions check on stat: if opened, allowed to stat it + let meta = f.run_blocking(|f| f.metadata()).await?; + Ok(meta.into()) + } + Self::Dir(d) => { + // No permissions check on stat: if opened, allowed to stat it + let meta = d.run_blocking(|d| d.dir_metadata()).await?; + Ok(meta.into()) + } + } + } + + pub(crate) async fn is_same_object(&self, other: &Self) -> wasmtime::Result { + use cap_fs_ext::MetadataExt; + let meta_a = self.get_metadata().await?; + let meta_b = other.get_metadata().await?; + if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() { + // MetadataHashValue does not derive eq, so use a pair of + // comparisons to check equality: + debug_assert_eq!( + MetadataHashValue::from(&meta_a).upper, + MetadataHashValue::from(&meta_b).upper, + ); + debug_assert_eq!( + MetadataHashValue::from(&meta_a).lower, + MetadataHashValue::from(&meta_b).lower, + ); + Ok(true) + } else { + // Hash collisions are possible, so don't assert the negative here + Ok(false) + } + } + + pub(crate) async fn metadata_hash(&self) -> Result { + let meta = self.get_metadata().await?; + Ok(MetadataHashValue::from(&meta)) + } +} + +#[derive(Clone)] +pub struct File { + /// The operating system File this struct is mediating access to. + /// + /// Wrapped in an Arc because the same underlying file is used for + /// implementing the stream types. A copy is also needed for + /// [`spawn_blocking`]. + /// + /// [`spawn_blocking`]: Self::spawn_blocking + pub file: Arc, + /// Permissions to enforce on access to the file. These permissions are + /// specified by a user of the `crate::WasiCtxBuilder`, and are + /// enforced prior to any enforced by the underlying operating system. + pub perms: FilePerms, + /// The mode the file was opened under: bits for reading, and writing. + /// Required to correctly report the DescriptorFlags, because cap-std + /// doesn't presently provide a cross-platform equivalent of reading the + /// oflags back out using fcntl. + pub open_mode: OpenMode, + + allow_blocking_current_thread: bool, +} + +impl File { + pub fn new( + file: cap_std::fs::File, + perms: FilePerms, + open_mode: OpenMode, + allow_blocking_current_thread: bool, + ) -> Self { + Self { + file: Arc::new(file), + perms, + open_mode, + allow_blocking_current_thread, + } + } + + /// Execute the blocking `body` function. + /// + /// Depending on how the WasiCtx was configured, the body may either be: + /// - Executed directly on the current thread. In this case the `async` + /// signature of this method is effectively a lie and the returned + /// Future will always be immediately Ready. Or: + /// - Spawned on a background thread using [`tokio::task::spawn_blocking`] + /// and immediately awaited. + /// + /// Intentionally blocking the executor thread might seem unorthodox, but is + /// not actually a problem for specific workloads. See: + /// - [`crate::WasiCtxBuilder::allow_blocking_current_thread`] + /// - [Poor performance of wasmtime file I/O maybe because tokio](https://github.com/bytecodealliance/wasmtime/issues/7973) + /// - [Implement opt-in for enabling WASI to block the current thread](https://github.com/bytecodealliance/wasmtime/pull/8190) + pub(crate) async fn run_blocking(&self, body: F) -> R + where + F: FnOnce(&cap_std::fs::File) -> R + Send + 'static, + R: Send + 'static, + { + match self.as_blocking_file() { + Some(file) => body(file), + None => self.spawn_blocking(body).await, + } + } + + pub(crate) fn spawn_blocking(&self, body: F) -> AbortOnDropJoinHandle + where + F: FnOnce(&cap_std::fs::File) -> R + Send + 'static, + R: Send + 'static, + { + let f = self.file.clone(); + spawn_blocking(move || body(&f)) + } + + /// Returns `Some` when the current thread is allowed to block in filesystem + /// operations, and otherwise returns `None` to indicate that + /// `spawn_blocking` must be used. + pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> { + if self.allow_blocking_current_thread { + Some(&self.file) + } else { + None + } + } + + pub(crate) async fn advise( + &self, + offset: u64, + len: u64, + advice: system_interface::fs::Advice, + ) -> Result<(), ErrorCode> { + use system_interface::fs::FileIoExt as _; + self.run_blocking(move |f| f.advise(offset, len, advice)) + .await?; + Ok(()) + } + + pub(crate) async fn set_size(&self, size: u64) -> Result<(), ErrorCode> { + if !self.perms.contains(FilePerms::WRITE) { + return Err(ErrorCode::NotPermitted); + } + self.run_blocking(move |f| f.set_len(size)).await?; + Ok(()) + } +} + +#[derive(Clone)] +pub struct Dir { + /// The operating system file descriptor this struct is mediating access + /// to. + /// + /// Wrapped in an Arc because a copy is needed for [`spawn_blocking`]. + /// + /// [`spawn_blocking`]: Self::spawn_blocking + pub dir: Arc, + /// Permissions to enforce on access to this directory. These permissions + /// are specified by a user of the `crate::WasiCtxBuilder`, and + /// are enforced prior to any enforced by the underlying operating system. + /// + /// These permissions are also enforced on any directories opened under + /// this directory. + pub perms: DirPerms, + /// Permissions to enforce on any files opened under this directory. + pub file_perms: FilePerms, + /// The mode the directory was opened under: bits for reading, and writing. + /// Required to correctly report the DescriptorFlags, because cap-std + /// doesn't presently provide a cross-platform equivalent of reading the + /// oflags back out using fcntl. + pub open_mode: OpenMode, + + allow_blocking_current_thread: bool, +} + +impl Dir { + pub fn new( + dir: cap_std::fs::Dir, + perms: DirPerms, + file_perms: FilePerms, + open_mode: OpenMode, + allow_blocking_current_thread: bool, + ) -> Self { + Dir { + dir: Arc::new(dir), + perms, + file_perms, + open_mode, + allow_blocking_current_thread, + } + } + + /// Execute the blocking `body` function. + /// + /// Depending on how the WasiCtx was configured, the body may either be: + /// - Executed directly on the current thread. In this case the `async` + /// signature of this method is effectively a lie and the returned + /// Future will always be immediately Ready. Or: + /// - Spawned on a background thread using [`tokio::task::spawn_blocking`] + /// and immediately awaited. + /// + /// Intentionally blocking the executor thread might seem unorthodox, but is + /// not actually a problem for specific workloads. See: + /// - [`crate::WasiCtxBuilder::allow_blocking_current_thread`] + /// - [Poor performance of wasmtime file I/O maybe because tokio](https://github.com/bytecodealliance/wasmtime/issues/7973) + /// - [Implement opt-in for enabling WASI to block the current thread](https://github.com/bytecodealliance/wasmtime/pull/8190) + pub(crate) async fn run_blocking(&self, body: F) -> R + where + F: FnOnce(&cap_std::fs::Dir) -> R + Send + 'static, + R: Send + 'static, + { + if self.allow_blocking_current_thread { + body(&self.dir) + } else { + let d = self.dir.clone(); + spawn_blocking(move || body(&d)).await + } + } + + pub(crate) async fn create_directory_at(&self, path: String) -> Result<(), ErrorCode> { + if !self.perms.contains(DirPerms::MUTATE) { + return Err(ErrorCode::NotPermitted); + } + self.run_blocking(move |d| d.create_dir(&path)).await?; + Ok(()) + } + + pub(crate) async fn stat_at( + &self, + path_flags: PathFlags, + path: String, + ) -> Result { + if !self.perms.contains(DirPerms::READ) { + return Err(ErrorCode::NotPermitted); + } + + let meta = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { + self.run_blocking(move |d| d.metadata(&path)).await? + } else { + self.run_blocking(move |d| d.symlink_metadata(&path)) + .await? + }; + Ok(meta.into()) + } + + pub(crate) async fn set_times_at( + &self, + path_flags: PathFlags, + path: String, + atim: Option, + mtim: Option, + ) -> Result<(), ErrorCode> { + use cap_fs_ext::DirExt as _; + + if !self.perms.contains(DirPerms::MUTATE) { + return Err(ErrorCode::NotPermitted); + } + if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { + self.run_blocking(move |d| { + d.set_times( + &path, + atim.map(cap_fs_ext::SystemTimeSpec::from_std), + mtim.map(cap_fs_ext::SystemTimeSpec::from_std), + ) + }) + .await?; + } else { + self.run_blocking(move |d| { + d.set_symlink_times( + &path, + atim.map(cap_fs_ext::SystemTimeSpec::from_std), + mtim.map(cap_fs_ext::SystemTimeSpec::from_std), + ) + }) + .await?; + } + Ok(()) + } + + pub(crate) async fn link_at( + &self, + old_path_flags: PathFlags, + old_path: String, + new_dir: &Self, + new_path: String, + ) -> Result<(), ErrorCode> { + if !self.perms.contains(DirPerms::MUTATE) { + return Err(ErrorCode::NotPermitted); + } + if !new_dir.perms.contains(DirPerms::MUTATE) { + return Err(ErrorCode::NotPermitted); + } + if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) { + return Err(ErrorCode::Invalid); + } + let new_dir_handle = Arc::clone(&new_dir.dir); + self.run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path)) + .await?; + Ok(()) + } + + pub(crate) async fn open_at( + &self, + path_flags: PathFlags, + path: String, + oflags: OpenFlags, + flags: DescriptorFlags, + allow_blocking_current_thread: bool, + ) -> Result { + use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt}; + use system_interface::fs::{FdFlags, GetSetFdFlags}; + + if !self.perms.contains(DirPerms::READ) { + return Err(ErrorCode::NotPermitted); + } + + if !self.perms.contains(DirPerms::MUTATE) { + if oflags.contains(OpenFlags::CREATE) || oflags.contains(OpenFlags::TRUNCATE) { + return Err(ErrorCode::NotPermitted); + } + if flags.contains(DescriptorFlags::WRITE) { + return Err(ErrorCode::NotPermitted); + } + } + + // Track whether we are creating file, for permission check: + let mut create = false; + // Track open mode, for permission check and recording in created descriptor: + let mut open_mode = OpenMode::empty(); + // Construct the OpenOptions to give the OS: + let mut opts = cap_std::fs::OpenOptions::new(); + opts.maybe_dir(true); + + if oflags.contains(OpenFlags::CREATE) { + if oflags.contains(OpenFlags::EXCLUSIVE) { + opts.create_new(true); + } else { + opts.create(true); + } + create = true; + opts.write(true); + open_mode |= OpenMode::WRITE; + } + + if oflags.contains(OpenFlags::TRUNCATE) { + opts.truncate(true).write(true); + } + if flags.contains(DescriptorFlags::READ) { + opts.read(true); + open_mode |= OpenMode::READ; + } + if flags.contains(DescriptorFlags::WRITE) { + opts.write(true); + open_mode |= OpenMode::WRITE; + } else { + // If not opened write, open read. This way the OS lets us open + // the file, but we can use perms to reject use of the file later. + opts.read(true); + open_mode |= OpenMode::READ; + } + if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { + opts.follow(FollowSymlinks::Yes); + } else { + opts.follow(FollowSymlinks::No); + } + + // These flags are not yet supported in cap-std: + if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC) + || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC) + || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC) + { + return Err(ErrorCode::Unsupported); + } + + if oflags.contains(OpenFlags::DIRECTORY) { + if oflags.contains(OpenFlags::CREATE) + || oflags.contains(OpenFlags::EXCLUSIVE) + || oflags.contains(OpenFlags::TRUNCATE) + { + return Err(ErrorCode::Invalid); + } + } + + // Now enforce this WasiCtx's permissions before letting the OS have + // its shot: + if !self.perms.contains(DirPerms::MUTATE) && create { + return Err(ErrorCode::NotPermitted); + } + if !self.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) { + return Err(ErrorCode::NotPermitted); + } + + // Represents each possible outcome from the spawn_blocking operation. + // This makes sure we don't have to give spawn_blocking any way to + // manipulate the table. + enum OpenResult { + Dir(cap_std::fs::Dir), + File(cap_std::fs::File), + NotDir, + } + + let opened = self + .run_blocking::<_, std::io::Result>(move |d| { + let mut opened = d.open_with(&path, &opts)?; + if opened.metadata()?.is_dir() { + Ok(OpenResult::Dir(cap_std::fs::Dir::from_std_file( + opened.into_std(), + ))) + } else if oflags.contains(OpenFlags::DIRECTORY) { + Ok(OpenResult::NotDir) + } else { + // FIXME cap-std needs a nonblocking open option so that files reads and writes + // are nonblocking. Instead we set it after opening here: + let set_fd_flags = opened.new_set_fd_flags(FdFlags::NONBLOCK)?; + opened.set_fd_flags(set_fd_flags)?; + Ok(OpenResult::File(opened)) + } + }) + .await?; + + match opened { + OpenResult::Dir(dir) => Ok(Descriptor::Dir(Dir::new( + dir, + self.perms, + self.file_perms, + open_mode, + allow_blocking_current_thread, + ))), + + OpenResult::File(file) => Ok(Descriptor::File(File::new( + file, + self.file_perms, + open_mode, + allow_blocking_current_thread, + ))), + + OpenResult::NotDir => Err(ErrorCode::NotDirectory), + } + } + + pub(crate) async fn readlink_at(&self, path: String) -> Result { + if !self.perms.contains(DirPerms::READ) { + return Err(ErrorCode::NotPermitted); + } + let link = self.run_blocking(move |d| d.read_link(&path)).await?; + link.into_os_string() + .into_string() + .or(Err(ErrorCode::IllegalByteSequence)) + } + + pub(crate) async fn remove_directory_at(&self, path: String) -> Result<(), ErrorCode> { + if !self.perms.contains(DirPerms::MUTATE) { + return Err(ErrorCode::NotPermitted); + } + self.run_blocking(move |d| d.remove_dir(&path)).await?; + Ok(()) + } + + pub(crate) async fn rename_at( + &self, + old_path: String, + new_dir: &Self, + new_path: String, + ) -> Result<(), ErrorCode> { + if !self.perms.contains(DirPerms::MUTATE) { + return Err(ErrorCode::NotPermitted); + } + if !new_dir.perms.contains(DirPerms::MUTATE) { + return Err(ErrorCode::NotPermitted); + } + let new_dir_handle = Arc::clone(&new_dir.dir); + self.run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path)) + .await?; + Ok(()) + } + + pub(crate) async fn symlink_at( + &self, + src_path: String, + dest_path: String, + ) -> Result<(), ErrorCode> { + // On windows, Dir.symlink is provided by DirExt + #[cfg(windows)] + use cap_fs_ext::DirExt; + + if !self.perms.contains(DirPerms::MUTATE) { + return Err(ErrorCode::NotPermitted); + } + self.run_blocking(move |d| d.symlink(&src_path, &dest_path)) + .await?; + Ok(()) + } + + pub(crate) async fn unlink_file_at(&self, path: String) -> Result<(), ErrorCode> { + use cap_fs_ext::DirExt; + + if !self.perms.contains(DirPerms::MUTATE) { + return Err(ErrorCode::NotPermitted); + } + self.run_blocking(move |d| d.remove_file_or_symlink(&path)) + .await?; + Ok(()) + } + + pub(crate) async fn metadata_hash_at( + &self, + path_flags: PathFlags, + path: String, + ) -> Result { + // No permissions check on metadata: if dir opened, allowed to stat it + let meta = self + .run_blocking(move |d| { + if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { + d.metadata(path) + } else { + d.symlink_metadata(path) + } + }) + .await?; + Ok(MetadataHashValue::from(&meta)) + } +} + +impl WasiFilesystemCtxView<'_> { + pub(crate) fn get_directories( + &mut self, + ) -> wasmtime::Result, String)>> { + let preopens = self.ctx.preopens.clone(); + let mut results = Vec::with_capacity(preopens.len()); + for (dir, name) in preopens { + let fd = self + .table + .push(Descriptor::Dir(dir)) + .with_context(|| format!("failed to push preopen {name}"))?; + results.push((fd, name)); + } + Ok(results) + } +} diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 53d098b388..6b03329669 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -16,24 +16,28 @@ pub mod cli; pub mod clocks; mod ctx; mod error; -mod filesystem; +pub mod filesystem; +#[cfg(feature = "p1")] +pub mod p0; +#[cfg(feature = "p1")] +pub mod p1; +// FIXME: should gate this module on the `p2` feature but that will require more +// internal refactoring to get that aligned right. +// #[cfg(feature = "p2")] pub mod p2; #[cfg(feature = "p3")] pub mod p3; -#[cfg(feature = "preview1")] -pub mod preview0; -#[cfg(feature = "preview1")] -pub mod preview1; pub mod random; pub mod runtime; pub mod sockets; +mod view; pub use self::clocks::{HostMonotonicClock, HostWallClock}; -pub(crate) use self::ctx::WasiCtxBuilder; +pub use self::ctx::{WasiCtx, WasiCtxBuilder}; pub use self::error::{I32Exit, TrappableError}; pub use self::filesystem::{DirPerms, FilePerms, OpenMode}; pub use self::random::{Deterministic, thread_rng}; -pub use self::sockets::{AllowedNetworkUses, SocketAddrUse}; +pub use self::view::{WasiCtxView, WasiView}; #[doc(no_inline)] pub use async_trait::async_trait; #[doc(no_inline)] diff --git a/crates/wasi/src/preview0.rs b/crates/wasi/src/p0.rs similarity index 97% rename from crates/wasi/src/preview0.rs rename to crates/wasi/src/p0.rs index 7cfe4baa58..cbff399a23 100644 --- a/crates/wasi/src/preview0.rs +++ b/crates/wasi/src/p0.rs @@ -1,12 +1,12 @@ //! Bindings for WASIp0 aka Preview 0 aka `wasi_unstable`. //! //! This module is purely here for backwards compatibility in the Wasmtime CLI. -//! You probably want to use [`preview1`](crate::preview1) instead. +//! You probably want to use [`p1`](crate::p1) instead. -use crate::preview0::types::Error; -use crate::preview1::WasiP1Ctx; -use crate::preview1::types as snapshot1_types; -use crate::preview1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1; +use crate::p0::types::Error; +use crate::p1::WasiP1Ctx; +use crate::p1::types as snapshot1_types; +use crate::p1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1; use wiggle::{GuestError, GuestMemory, GuestPtr}; pub fn add_to_linker_async( @@ -24,7 +24,7 @@ pub fn add_to_linker_sync( } wiggle::from_witx!({ - witx: ["witx/preview0/wasi_unstable.witx"], + witx: ["witx/p0/wasi_unstable.witx"], async: { wasi_unstable::{ fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size, @@ -42,7 +42,7 @@ mod sync { use std::future::Future; wiggle::wasmtime_integration!({ - witx: ["witx/preview0/wasi_unstable.witx"], + witx: ["witx/p0/wasi_unstable.witx"], target: super, block_on[in_tokio]: { wasi_unstable::{ @@ -500,11 +500,11 @@ impl wasi_unstable::WasiUnstable for T { Ok(()) } - // The representation of `SubscriptionClock` is different in preview0 and - // preview1 so a bit of a hack is employed here. The change was to remove a + // The representation of `SubscriptionClock` is different in p0 and + // p1 so a bit of a hack is employed here. The change was to remove a // field from `SubscriptionClock` so to implement this without copying too - // much the `subs` field is overwritten with preview1-compatible structures - // and then the preview1 implementation is used. Before returning though + // much the `subs` field is overwritten with p1-compatible structures + // and then the p1 implementation is used. Before returning though // the old values are restored to pretend like we didn't overwrite them. // // Surely no one would pass overlapping pointers to this API right? @@ -611,7 +611,7 @@ impl wasi_unstable::WasiUnstable for T { fn assert_iovec_array_same() { // NB: this isn't enough to assert the types are the same, but it's - // something. Additionally preview1 and preview0 aren't changing any more + // something. Additionally p1 and p0 aren't changing any more // and it's been manually verified that these two types are the same, so // it's ok to cast between them. assert_eq!( diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/p1.rs similarity index 91% rename from crates/wasi/src/preview1.rs rename to crates/wasi/src/p1.rs index bf9261f6f7..5f5a8fd992 100644 --- a/crates/wasi/src/preview1.rs +++ b/crates/wasi/src/p1.rs @@ -35,8 +35,8 @@ //! //! ```no_run //! use wasmtime::{Result, Engine, Linker, Module, Store}; -//! use wasmtime_wasi::preview1::{self, WasiP1Ctx}; -//! use wasmtime_wasi::p2::WasiCtxBuilder; +//! use wasmtime_wasi::p1::{self, WasiP1Ctx}; +//! use wasmtime_wasi::WasiCtxBuilder; //! //! // An example of executing a WASIp1 "command" //! fn main() -> Result<()> { @@ -45,7 +45,7 @@ //! let module = Module::from_file(&engine, &args[0])?; //! //! let mut linker: Linker = Linker::new(&engine); -//! preview1::add_to_linker_async(&mut linker, |t| t)?; +//! p1::add_to_linker_async(&mut linker, |t| t)?; //! let pre = linker.instantiate_pre(&module)?; //! //! let wasi_ctx = WasiCtxBuilder::new() @@ -63,16 +63,19 @@ //! } //! ``` -use crate::ResourceTable; +use crate::cli::WasiCliView as _; +use crate::clocks::WasiClocksView as _; +use crate::filesystem::WasiFilesystemView as _; use crate::p2::bindings::{ cli::{ stderr::Host as _, stdin::Host as _, stdout::Host as _, terminal_input, terminal_output, terminal_stderr::Host as _, terminal_stdin::Host as _, terminal_stdout::Host as _, }, clocks::{monotonic_clock, wall_clock}, - filesystem::{preopens::Host as _, types as filesystem}, + filesystem::types as filesystem, }; -use crate::p2::{FsError, IsATTY, WasiCtx, WasiImpl, WasiView}; +use crate::p2::{FsError, IsATTY}; +use crate::{ResourceTable, WasiCtx, WasiCtxView, WasiView}; use anyhow::{Context, bail}; use std::collections::{BTreeMap, BTreeSet, HashSet, btree_map}; use std::mem::{self, size_of, size_of_val}; @@ -82,7 +85,6 @@ use std::sync::atomic::{AtomicU64, Ordering}; use system_interface::fs::FileIoExt; use wasmtime::component::Resource; use wasmtime_wasi_io::{ - IoImpl, IoView, bindings::wasi::io::streams, streams::{StreamError, StreamResult}, }; @@ -112,8 +114,8 @@ use wasmtime_wasi_io::bindings::wasi::io::poll::Host as _; /// /// ```no_run /// use wasmtime::{Result, Linker}; -/// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; -/// use wasmtime_wasi::p2::WasiCtxBuilder; +/// use wasmtime_wasi::p1::{self, WasiP1Ctx}; +/// use wasmtime_wasi::WasiCtxBuilder; /// /// struct MyState { /// // ... custom state as necessary ... @@ -135,14 +137,14 @@ use wasmtime_wasi_io::bindings::wasi::io::poll::Host as _; /// } /// /// fn add_to_linker(linker: &mut Linker) -> Result<()> { -/// preview1::add_to_linker_sync(linker, |my_state| &mut my_state.wasi)?; +/// p1::add_to_linker_sync(linker, |my_state| &mut my_state.wasi)?; /// Ok(()) /// } /// ``` pub struct WasiP1Ctx { table: ResourceTable, wasi: WasiCtx, - adapter: WasiPreview1Adapter, + adapter: WasiP1Adapter, } impl WasiP1Ctx { @@ -150,32 +152,23 @@ impl WasiP1Ctx { Self { table: ResourceTable::new(), wasi, - adapter: WasiPreview1Adapter::new(), + adapter: WasiP1Adapter::new(), } } - - fn as_wasi_impl(&mut self) -> WasiImpl<&mut Self> { - WasiImpl(IoImpl(self)) - } - fn as_io_impl(&mut self) -> IoImpl<&mut Self> { - IoImpl(self) - } } -impl IoView for WasiP1Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} impl WasiView for WasiP1Ctx { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi, + table: &mut self.table, + } } } #[derive(Debug)] struct File { - /// The handle to the preview2 descriptor of type [`crate::p2::filesystem::Descriptor::File`]. + /// The handle to the preview2 descriptor of type [`crate::filesystem::Descriptor::File`]. fd: Resource, /// The current-position pointer. @@ -190,7 +183,7 @@ struct File { blocking_mode: BlockingMode, } -/// NB: preview1 files always use blocking writes regardless of what +/// NB: p1 files always use blocking writes regardless of what /// they're configured to use since OSes don't have nonblocking /// reads/writes anyway. This behavior originated in the first /// implementation of WASIp1 where flags were propagated to the @@ -266,19 +259,19 @@ enum Descriptor { stream: Resource, isatty: IsATTY, }, - /// A fd of type [`crate::p2::filesystem::Descriptor::Dir`] + /// A fd of type [`crate::filesystem::Descriptor::Dir`] Directory { fd: Resource, /// The path this directory was preopened as. /// `None` means this directory was opened using `open-at`. preopen_path: Option, }, - /// A fd of type [`crate::p2::filesystem::Descriptor::File`] + /// A fd of type [`crate::filesystem::Descriptor::File`] File(File), } #[derive(Debug, Default)] -struct WasiPreview1Adapter { +struct WasiP1Adapter { descriptors: Option, } @@ -290,19 +283,21 @@ struct Descriptors { impl Descriptors { /// Initializes [Self] using `preopens` - fn new(mut host: WasiImpl<&mut WasiP1Ctx>) -> Result { + fn new(host: &mut WasiP1Ctx) -> Result { let mut descriptors = Self::default(); descriptors.push(Descriptor::Stdin { stream: host + .cli() .get_stdin() .context("failed to call `get-stdin`") .map_err(types::Error::trap)?, isatty: if let Some(term_in) = host + .cli() .get_terminal_stdin() .context("failed to call `get-terminal-stdin`") .map_err(types::Error::trap)? { - terminal_input::HostTerminalInput::drop(&mut host, term_in) + terminal_input::HostTerminalInput::drop(&mut host.cli(), term_in) .context("failed to call `drop-terminal-input`") .map_err(types::Error::trap)?; IsATTY::Yes @@ -312,15 +307,17 @@ impl Descriptors { })?; descriptors.push(Descriptor::Stdout { stream: host + .cli() .get_stdout() .context("failed to call `get-stdout`") .map_err(types::Error::trap)?, isatty: if let Some(term_out) = host + .cli() .get_terminal_stdout() .context("failed to call `get-terminal-stdout`") .map_err(types::Error::trap)? { - terminal_output::HostTerminalOutput::drop(&mut host, term_out) + terminal_output::HostTerminalOutput::drop(&mut host.cli(), term_out) .context("failed to call `drop-terminal-output`") .map_err(types::Error::trap)?; IsATTY::Yes @@ -330,15 +327,17 @@ impl Descriptors { })?; descriptors.push(Descriptor::Stderr { stream: host + .cli() .get_stderr() .context("failed to call `get-stderr`") .map_err(types::Error::trap)?, isatty: if let Some(term_out) = host + .cli() .get_terminal_stderr() .context("failed to call `get-terminal-stderr`") .map_err(types::Error::trap)? { - terminal_output::HostTerminalOutput::drop(&mut host, term_out) + terminal_output::HostTerminalOutput::drop(&mut host.cli(), term_out) .context("failed to call `drop-terminal-output`") .map_err(types::Error::trap)?; IsATTY::Yes @@ -348,6 +347,7 @@ impl Descriptors { })?; for dir in host + .filesystem() .get_directories() .context("failed to call `get-directories`") .map_err(types::Error::trap)? @@ -394,28 +394,28 @@ impl Descriptors { } } -impl WasiPreview1Adapter { +impl WasiP1Adapter { fn new() -> Self { Self::default() } } -/// A mutably-borrowed [`WasiPreview1View`] implementation, which provides access to the stored -/// state. It can be thought of as an in-flight [`WasiPreview1Adapter`] transaction, all -/// changes will be recorded in the underlying [`WasiPreview1Adapter`] returned by +/// A mutably-borrowed `WasiP1Ctx`, which provides access to the stored +/// state. It can be thought of as an in-flight [`WasiP1Adapter`] transaction, all +/// changes will be recorded in the underlying [`WasiP1Adapter`] returned by /// [`WasiPreview1View::adapter_mut`] on [`Drop`] of this struct. // NOTE: This exists for the most part just due to the fact that `bindgen` generates methods with // `&mut self` receivers and so this struct lets us extend the lifetime of the `&mut self` borrow // of the [`WasiPreview1View`] to provide means to return mutably and immutably borrowed [`Descriptors`] // without having to rely on something like `Arc>`, while also being able to -// call methods like [`Descriptor::is_file`] and hiding complexity from preview1 method implementations. +// call methods like [`Descriptor::is_file`] and hiding complexity from p1 method implementations. struct Transaction<'a> { view: &'a mut WasiP1Ctx, descriptors: Descriptors, } impl Drop for Transaction<'_> { - /// Record changes in the [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] + /// Record changes in the [`WasiP1Adapter`] . fn drop(&mut self) { let descriptors = mem::take(&mut self.descriptors); self.view.adapter.descriptors = Some(descriptors); @@ -503,13 +503,13 @@ impl Transaction<'_> { } impl WasiP1Ctx { - /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] + /// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`Transaction`] on success fn transact(&mut self) -> Result, types::Error> { let descriptors = if let Some(descriptors) = self.adapter.descriptors.take() { descriptors } else { - Descriptors::new(self.as_wasi_impl())? + Descriptors::new(self)? }; Ok(Transaction { view: self, @@ -517,7 +517,7 @@ impl WasiP1Ctx { }) } - /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] + /// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`filesystem::Descriptor`] corresponding to `fd` fn get_fd(&mut self, fd: types::Fd) -> Result, types::Error> { let st = self.transact()?; @@ -525,7 +525,7 @@ impl WasiP1Ctx { Ok(fd) } - /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] + /// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] of [`crate::p2::filesystem::File`] type fn get_file_fd( @@ -537,7 +537,7 @@ impl WasiP1Ctx { Ok(fd) } - /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] + /// Lazily initializes [`WasiP1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] or [`Descriptor::PreopenDirectory`] /// of [`crate::p2::filesystem::Dir`] type @@ -578,7 +578,7 @@ impl WasiP1Ctx { let pos = position.load(Ordering::Relaxed); let append = *append; drop(t); - let f = self.table().get(&fd)?.file()?; + let f = self.table.get(&fd)?.file()?; let buf = first_non_empty_ciovec(memory, ciovs)?; let do_write = move |f: &cap_std::fs::File, buf: &[u8]| match (append, write) { @@ -610,7 +610,7 @@ impl WasiP1Ctx { // position is left unmodified. if let FdWrite::AtCur = write { if append { - let len = self.as_wasi_impl().stat(fd).await?; + let len = self.filesystem().stat(fd).await?; position.store(len.size, Ordering::Relaxed); } else { let pos = pos @@ -632,7 +632,7 @@ impl WasiP1Ctx { drop(t); let buf = first_non_empty_ciovec(memory, ciovs)?; let n = BlockingMode::Blocking - .write(memory, &mut self.as_io_impl(), stream, buf) + .write(memory, &mut self.table, stream, buf) .await? .try_into()?; Ok(n) @@ -674,7 +674,7 @@ enum FdWrite { /// /// ```no_run /// use wasmtime::{Result, Linker, Engine, Config}; -/// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +/// use wasmtime_wasi::p1::{self, WasiP1Ctx}; /// /// fn main() -> Result<()> { /// let mut config = Config::new(); @@ -682,7 +682,7 @@ enum FdWrite { /// let engine = Engine::new(&config)?; /// /// let mut linker: Linker = Linker::new(&engine); -/// preview1::add_to_linker_async(&mut linker, |cx| cx)?; +/// p1::add_to_linker_async(&mut linker, |cx| cx)?; /// /// // ... continue to add more to `linker` as necessary and use it ... /// @@ -694,7 +694,7 @@ enum FdWrite { /// /// ```no_run /// use wasmtime::{Result, Linker, Engine, Config}; -/// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +/// use wasmtime_wasi::p1::{self, WasiP1Ctx}; /// /// struct MyState { /// // .. other custom state here .. @@ -708,7 +708,7 @@ enum FdWrite { /// let engine = Engine::new(&config)?; /// /// let mut linker: Linker = Linker::new(&engine); -/// preview1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?; +/// p1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?; /// /// // ... continue to add more to `linker` as necessary and use it ... /// @@ -719,7 +719,7 @@ pub fn add_to_linker_async( linker: &mut wasmtime::Linker, f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static, ) -> anyhow::Result<()> { - crate::preview1::wasi_snapshot_preview1::add_to_linker(linker, f) + crate::p1::wasi_snapshot_preview1::add_to_linker(linker, f) } /// Adds synchronous versions of all WASIp1 functions to the @@ -748,7 +748,7 @@ pub fn add_to_linker_async( /// /// ```no_run /// use wasmtime::{Result, Linker, Engine, Config}; -/// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +/// use wasmtime_wasi::p1::{self, WasiP1Ctx}; /// /// fn main() -> Result<()> { /// let mut config = Config::new(); @@ -756,7 +756,7 @@ pub fn add_to_linker_async( /// let engine = Engine::new(&config)?; /// /// let mut linker: Linker = Linker::new(&engine); -/// preview1::add_to_linker_async(&mut linker, |cx| cx)?; +/// p1::add_to_linker_async(&mut linker, |cx| cx)?; /// /// // ... continue to add more to `linker` as necessary and use it ... /// @@ -768,7 +768,7 @@ pub fn add_to_linker_async( /// /// ```no_run /// use wasmtime::{Result, Linker, Engine, Config}; -/// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +/// use wasmtime_wasi::p1::{self, WasiP1Ctx}; /// /// struct MyState { /// // .. other custom state here .. @@ -782,7 +782,7 @@ pub fn add_to_linker_async( /// let engine = Engine::new(&config)?; /// /// let mut linker: Linker = Linker::new(&engine); -/// preview1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?; +/// p1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?; /// /// // ... continue to add more to `linker` as necessary and use it ... /// @@ -801,7 +801,7 @@ pub fn add_to_linker_sync( // None of the generated modules, traits, or types should be used externally // to this module. wiggle::from_witx!({ - witx: ["witx/preview1/wasi_snapshot_preview1.witx"], + witx: ["witx/p1/wasi_snapshot_preview1.witx"], async: { wasi_snapshot_preview1::{ fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size, @@ -819,7 +819,7 @@ pub(crate) mod sync { use std::future::Future; wiggle::wasmtime_integration!({ - witx: ["witx/preview1/wasi_snapshot_preview1.witx"], + witx: ["witx/p1/wasi_snapshot_preview1.witx"], target: super, block_on[in_tokio]: { wasi_snapshot_preview1::{ @@ -953,7 +953,7 @@ impl TryFrom for types::Filetype { filesystem::DescriptorType::Directory => Ok(types::Filetype::Directory), filesystem::DescriptorType::BlockDevice => Ok(types::Filetype::BlockDevice), filesystem::DescriptorType::CharacterDevice => Ok(types::Filetype::CharacterDevice), - // preview1 never had a FIFO code. + // p1 never had a FIFO code. filesystem::DescriptorType::Fifo => Ok(types::Filetype::Unknown), // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and // FILETYPE_SOCKET_DGRAM. @@ -975,6 +975,38 @@ impl From for types::Filetype { } } +impl From for types::Errno { + fn from(code: crate::filesystem::ErrorCode) -> Self { + match code { + crate::filesystem::ErrorCode::Access => types::Errno::Acces, + crate::filesystem::ErrorCode::Already => types::Errno::Already, + crate::filesystem::ErrorCode::BadDescriptor => types::Errno::Badf, + crate::filesystem::ErrorCode::Busy => types::Errno::Busy, + crate::filesystem::ErrorCode::Exist => types::Errno::Exist, + crate::filesystem::ErrorCode::FileTooLarge => types::Errno::Fbig, + crate::filesystem::ErrorCode::IllegalByteSequence => types::Errno::Ilseq, + crate::filesystem::ErrorCode::InProgress => types::Errno::Inprogress, + crate::filesystem::ErrorCode::Interrupted => types::Errno::Intr, + crate::filesystem::ErrorCode::Invalid => types::Errno::Inval, + crate::filesystem::ErrorCode::Io => types::Errno::Io, + crate::filesystem::ErrorCode::IsDirectory => types::Errno::Isdir, + crate::filesystem::ErrorCode::Loop => types::Errno::Loop, + crate::filesystem::ErrorCode::TooManyLinks => types::Errno::Mlink, + crate::filesystem::ErrorCode::NameTooLong => types::Errno::Nametoolong, + crate::filesystem::ErrorCode::NoEntry => types::Errno::Noent, + crate::filesystem::ErrorCode::InsufficientMemory => types::Errno::Nomem, + crate::filesystem::ErrorCode::InsufficientSpace => types::Errno::Nospc, + crate::filesystem::ErrorCode::Unsupported => types::Errno::Notsup, + crate::filesystem::ErrorCode::NotDirectory => types::Errno::Notdir, + crate::filesystem::ErrorCode::NotEmpty => types::Errno::Notempty, + crate::filesystem::ErrorCode::Overflow => types::Errno::Overflow, + crate::filesystem::ErrorCode::NotPermitted => types::Errno::Perm, + crate::filesystem::ErrorCode::Pipe => types::Errno::Pipe, + crate::filesystem::ErrorCode::InvalidSeek => types::Errno::Spipe, + } + } +} + impl From for types::Errno { fn from(code: filesystem::ErrorCode) -> Self { match code { @@ -1058,6 +1090,12 @@ impl From for types::Error { } } +impl From for types::Error { + fn from(code: crate::filesystem::ErrorCode) -> Self { + types::Errno::from(code).into() + } +} + impl From for types::Error { fn from(err: wasmtime::component::ResourceTableError) -> Self { types::Error::trap(err.into()) @@ -1125,7 +1163,7 @@ fn first_non_empty_iovec( #[async_trait::async_trait] // Implement the WasiSnapshotPreview1 trait using only the traits that are // required for T, i.e., in terms of the preview 2 wit interface, and state -// stored in the WasiPreview1Adapter struct. +// stored in the WasiP1Adapter struct. impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { #[instrument(skip(self, memory))] fn args_get( @@ -1134,7 +1172,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { argv: GuestPtr>, argv_buf: GuestPtr, ) -> Result<(), types::Error> { - self.as_wasi_impl() + self.cli() .get_arguments() .context("failed to call `get-arguments`") .map_err(types::Error::trap)? @@ -1157,7 +1195,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { _memory: &mut GuestMemory<'_>, ) -> Result<(types::Size, types::Size), types::Error> { let args = self - .as_wasi_impl() + .cli() .get_arguments() .context("failed to call `get-arguments`") .map_err(types::Error::trap)?; @@ -1178,7 +1216,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { environ: GuestPtr>, environ_buf: GuestPtr, ) -> Result<(), types::Error> { - self.as_wasi_impl() + self.cli() .get_environment() .context("failed to call `get-environment`") .map_err(types::Error::trap)? @@ -1206,7 +1244,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { _memory: &mut GuestMemory<'_>, ) -> Result<(types::Size, types::Size), types::Error> { let environ = self - .as_wasi_impl() + .cli() .get_environment() .context("failed to call `get-environment`") .map_err(types::Error::trap)?; @@ -1226,15 +1264,13 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { id: types::Clockid, ) -> Result { let res = match id { - types::Clockid::Realtime => wall_clock::Host::resolution(&mut self.as_wasi_impl()) + types::Clockid::Realtime => wall_clock::Host::resolution(&mut self.clocks()) .context("failed to call `wall_clock::resolution`") .map_err(types::Error::trap)? .try_into()?, - types::Clockid::Monotonic => { - monotonic_clock::Host::resolution(&mut self.as_wasi_impl()) - .context("failed to call `monotonic_clock::resolution`") - .map_err(types::Error::trap)? - } + types::Clockid::Monotonic => monotonic_clock::Host::resolution(&mut self.clocks()) + .context("failed to call `monotonic_clock::resolution`") + .map_err(types::Error::trap)?, types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => { return Err(types::Errno::Badf.into()); } @@ -1250,11 +1286,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { _precision: types::Timestamp, ) -> Result { let now = match id { - types::Clockid::Realtime => wall_clock::Host::now(&mut self.as_wasi_impl()) + types::Clockid::Realtime => wall_clock::Host::now(&mut self.clocks()) .context("failed to call `wall_clock::now`") .map_err(types::Error::trap)? .try_into()?, - types::Clockid::Monotonic => monotonic_clock::Host::now(&mut self.as_wasi_impl()) + types::Clockid::Monotonic => monotonic_clock::Host::now(&mut self.clocks()) .context("failed to call `monotonic_clock::now`") .map_err(types::Error::trap)?, types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => { @@ -1274,7 +1310,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { advice: types::Advice, ) -> Result<(), types::Error> { let fd = self.get_file_fd(fd)?; - self.as_wasi_impl() + self.filesystem() .advise(fd, offset, len, advice.into()) .await?; Ok(()) @@ -1311,17 +1347,17 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { }; match desc { Descriptor::Stdin { stream, .. } => { - streams::HostInputStream::drop(&mut self.as_io_impl(), stream) + streams::HostInputStream::drop(&mut self.table, stream) .await .context("failed to call `drop` on `input-stream`") } Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => { - streams::HostOutputStream::drop(&mut self.as_io_impl(), stream) + streams::HostOutputStream::drop(&mut self.table, stream) .await .context("failed to call `drop` on `output-stream`") } Descriptor::File(File { fd, .. }) | Descriptor::Directory { fd, .. } => { - filesystem::HostDescriptor::drop(&mut self.as_wasi_impl(), fd) + filesystem::HostDescriptor::drop(&mut self.filesystem(), fd) .context("failed to call `drop`") } } @@ -1337,7 +1373,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { fd: types::Fd, ) -> Result<(), types::Error> { let fd = self.get_file_fd(fd)?; - self.as_wasi_impl().sync_data(fd).await?; + self.filesystem().sync_data(fd).await?; Ok(()) } @@ -1420,9 +1456,9 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { .. }) => (fd.borrowed(), *blocking_mode, *append), }; - let flags = self.as_wasi_impl().get_flags(fd.borrowed()).await?; + let flags = self.filesystem().get_flags(fd.borrowed()).await?; let fs_filetype = self - .as_wasi_impl() + .filesystem() .get_type(fd.borrowed()) .await? .try_into() @@ -1536,8 +1572,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { data_access_timestamp, data_modification_timestamp, status_change_timestamp, - } = self.as_wasi_impl().stat(fd.borrowed()).await?; - let metadata_hash = self.as_wasi_impl().metadata_hash(fd).await?; + } = self.filesystem().stat(fd.borrowed()).await?; + let metadata_hash = self.filesystem().metadata_hash(fd).await?; let filetype = type_.try_into().map_err(types::Error::trap)?; let zero = wall_clock::Datetime { seconds: 0, @@ -1570,7 +1606,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { size: types::Filesize, ) -> Result<(), types::Error> { let fd = self.get_file_fd(fd)?; - self.as_wasi_impl().set_size(fd, size).await?; + self.filesystem().set_size(fd, size).await?; Ok(()) } @@ -1597,7 +1633,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { )?; let fd = self.get_fd(fd)?; - self.as_wasi_impl().set_times(fd, atim, mtim).await?; + self.filesystem().set_times(fd, atim, mtim).await?; Ok(()) } @@ -1625,7 +1661,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { let position = position.clone(); drop(t); let pos = position.load(Ordering::Relaxed); - let file = self.table().get(&fd)?.file()?; + let file = self.table.get(&fd)?.file()?; let iov = first_non_empty_iovec(memory, iovs)?; let bytes_read = match (file.as_blocking_file(), memory.as_slice_mut(iov)?) { // Try to read directly into wasm memory where possible @@ -1667,7 +1703,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { drop(t); let buf = first_non_empty_iovec(memory, iovs)?; let read = BlockingMode::Blocking - .read(&mut self.as_io_impl(), stream, buf.len().try_into()?) + .read(&mut self.table, stream, buf.len().try_into()?) .await?; if read.len() > buf.len().try_into()? { return Err(types::Errno::Range.into()); @@ -1702,15 +1738,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { drop(t); let buf = first_non_empty_iovec(memory, iovs)?; - let stream = self.as_wasi_impl().read_via_stream(fd, offset)?; + let stream = self.filesystem().read_via_stream(fd, offset)?; let read = blocking_mode - .read( - &mut self.as_io_impl(), - stream.borrowed(), - buf.len().try_into()?, - ) + .read(&mut self.table, stream.borrowed(), buf.len().try_into()?) .await; - streams::HostInputStream::drop(&mut self.as_io_impl(), stream) + streams::HostInputStream::drop(&mut self.table, stream) .await .map_err(|e| types::Error::trap(e))?; (buf, read?) @@ -1845,7 +1877,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { .checked_add_signed(offset) .ok_or(types::Errno::Inval)?, types::Whence::End => { - let filesystem::DescriptorStat { size, .. } = self.as_wasi_impl().stat(fd).await?; + let filesystem::DescriptorStat { size, .. } = self.filesystem().stat(fd).await?; size.checked_add_signed(offset).ok_or(types::Errno::Inval)? } _ => return Err(types::Errno::Inval.into()), @@ -1863,7 +1895,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { fd: types::Fd, ) -> Result<(), types::Error> { let fd = self.get_file_fd(fd)?; - self.as_wasi_impl().sync(fd).await?; + self.filesystem().sync(fd).await?; Ok(()) } @@ -1892,8 +1924,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { cookie: types::Dircookie, ) -> Result { let fd = self.get_dir_fd(fd)?; - let stream = self.as_wasi_impl().read_directory(fd.borrowed()).await?; - let dir_metadata_hash = self.as_wasi_impl().metadata_hash(fd.borrowed()).await?; + let stream = self.filesystem().read_directory(fd.borrowed()).await?; + let dir_metadata_hash = self.filesystem().metadata_hash(fd.borrowed()).await?; let cookie = cookie.try_into().map_err(|_| types::Errno::Overflow)?; let head = [ @@ -1919,7 +1951,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { let mut dir = Vec::new(); for (entry, d_next) in self - .table() + .table // remove iterator from table and use it directly: .delete(stream)? .into_iter() @@ -1927,7 +1959,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { { let filesystem::DirectoryEntry { type_, name } = entry?; let metadata_hash = self - .as_wasi_impl() + .filesystem() .metadata_hash_at(fd.borrowed(), filesystem::PathFlags::empty(), name.clone()) .await?; let d_type = type_.try_into().map_err(types::Error::trap)?; @@ -1990,7 +2022,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { ) -> Result<(), types::Error> { let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(memory, path)?; - self.as_wasi_impl() + self.filesystem() .create_directory_at(dirfd.borrowed(), path) .await?; Ok(()) @@ -2016,11 +2048,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { data_modification_timestamp, status_change_timestamp, } = self - .as_wasi_impl() + .filesystem() .stat_at(dirfd.borrowed(), flags.into(), path.clone()) .await?; let metadata_hash = self - .as_wasi_impl() + .filesystem() .metadata_hash_at(dirfd, flags.into(), path) .await?; let filetype = type_.try_into().map_err(types::Error::trap)?; @@ -2069,7 +2101,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(memory, path)?; - self.as_wasi_impl() + self.filesystem() .set_times_at(dirfd, flags.into(), path, atim, mtim) .await?; Ok(()) @@ -2091,7 +2123,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { let target_fd = self.get_dir_fd(target_fd)?; let src_path = read_string(memory, src_path)?; let target_path = read_string(memory, target_path)?; - self.as_wasi_impl() + self.filesystem() .link_at(src_fd, src_flags.into(), src_path, target_fd, target_path) .await?; Ok(()) @@ -2138,16 +2170,16 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { }; drop(t); let fd = self - .as_wasi_impl() + .filesystem() .open_at(dirfd, dirflags.into(), path, oflags.into(), flags) .await?; let mut t = self.transact()?; - let desc = match t.view.table().get(&fd)? { - crate::p2::filesystem::Descriptor::Dir(_) => Descriptor::Directory { + let desc = match t.view.table.get(&fd)? { + crate::filesystem::Descriptor::Dir(_) => Descriptor::Directory { fd, preopen_path: None, }, - crate::p2::filesystem::Descriptor::File(_) => Descriptor::File(File { + crate::filesystem::Descriptor::File(_) => Descriptor::File(File { fd, position: Default::default(), append: fdflags.contains(types::Fdflags::APPEND), @@ -2172,7 +2204,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(memory, path)?; let mut path = self - .as_wasi_impl() + .filesystem() .readlink_at(dirfd, path) .await? .into_bytes(); @@ -2194,7 +2226,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { ) -> Result<(), types::Error> { let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(memory, path)?; - self.as_wasi_impl().remove_directory_at(dirfd, path).await?; + self.filesystem().remove_directory_at(dirfd, path).await?; Ok(()) } @@ -2213,7 +2245,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { let dest_fd = self.get_dir_fd(dest_fd)?; let src_path = read_string(memory, src_path)?; let dest_path = read_string(memory, dest_path)?; - self.as_wasi_impl() + self.filesystem() .rename_at(src_fd, src_path, dest_fd, dest_path) .await?; Ok(()) @@ -2230,7 +2262,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { let dirfd = self.get_dir_fd(dirfd)?; let src_path = read_string(memory, src_path)?; let dest_path = read_string(memory, dest_path)?; - self.as_wasi_impl() + self.filesystem() .symlink_at(dirfd.borrowed(), src_path, dest_path) .await?; Ok(()) @@ -2245,7 +2277,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { ) -> Result<(), types::Error> { let dirfd = self.get_dir_fd(dirfd)?; let path = memory.as_cow_str(path)?.into_owned(); - self.as_wasi_impl() + self.filesystem() .unlink_file_at(dirfd.borrowed(), path) .await?; Ok(()) @@ -2260,7 +2292,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { nsubscriptions: types::Size, ) -> Result { if nsubscriptions == 0 { - // Indefinite sleeping is not supported in preview1. + // Indefinite sleeping is not supported in p1. return Err(types::Errno::Inval.into()); } @@ -2275,7 +2307,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { if !clocksub .flags .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) - && self.ctx().allow_blocking_current_thread + && self.wasi.filesystem.allow_blocking_current_thread { std::thread::sleep(std::time::Duration::from_nanos(clocksub.timeout)); memory.write( @@ -2314,7 +2346,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { types::Clockid::Monotonic => (timeout, absolute), types::Clockid::Realtime if !absolute => (timeout, false), types::Clockid::Realtime => { - let now = wall_clock::Host::now(&mut self.as_wasi_impl()) + let now = wall_clock::Host::now(&mut self.clocks()) .context("failed to call `wall_clock::now`") .map_err(types::Error::trap)?; @@ -2337,11 +2369,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { _ => return Err(types::Errno::Inval.into()), }; if absolute { - monotonic_clock::Host::subscribe_instant(&mut self.as_wasi_impl(), timeout) + monotonic_clock::Host::subscribe_instant(&mut self.clocks(), timeout) .context("failed to call `monotonic_clock::subscribe_instant`") .map_err(types::Error::trap)? } else { - monotonic_clock::Host::subscribe_duration(&mut self.as_wasi_impl(), timeout) + monotonic_clock::Host::subscribe_duration(&mut self.clocks(), timeout) .context("failed to call `monotonic_clock::subscribe_duration`") .map_err(types::Error::trap)? } @@ -2358,13 +2390,13 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { let pos = position.load(Ordering::Relaxed); let fd = fd.borrowed(); drop(t); - self.as_wasi_impl().read_via_stream(fd, pos)? + self.filesystem().read_via_stream(fd, pos)? } // TODO: Support sockets _ => return Err(types::Errno::Badf.into()), } }; - streams::HostInputStream::subscribe(&mut self.as_io_impl(), stream) + streams::HostInputStream::subscribe(&mut self.table, stream) .context("failed to call `subscribe` on `input-stream`") .map_err(types::Error::trap)? } @@ -2388,17 +2420,17 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { let append = *append; drop(t); if append { - self.as_wasi_impl().append_via_stream(fd)? + self.filesystem().append_via_stream(fd)? } else { let pos = position.load(Ordering::Relaxed); - self.as_wasi_impl().write_via_stream(fd, pos)? + self.filesystem().write_via_stream(fd, pos)? } } // TODO: Support sockets _ => return Err(types::Errno::Badf.into()), } }; - streams::HostOutputStream::subscribe(&mut self.as_io_impl(), stream) + streams::HostOutputStream::subscribe(&mut self.table, stream) .context("failed to call `subscribe` on `output-stream`") .map_err(types::Error::trap)? } @@ -2406,7 +2438,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { pollables.push(p); } let ready: HashSet<_> = self - .as_io_impl() + .table .poll(pollables) .await .context("failed to call `poll-oneoff`") @@ -2451,7 +2483,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { let fd = fd.borrowed(); let position = position.clone(); drop(t); - match self.as_wasi_impl().stat(fd).await? { + match self.filesystem().stat(fd).await? { filesystem::DescriptorStat { size, .. } => { let pos = position.load(Ordering::Relaxed); let nbytes = size.saturating_sub(pos); @@ -2564,7 +2596,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { fd: types::Fd, flags: types::Fdflags, ) -> Result { - tracing::warn!("preview1 sock_accept is not implemented"); + tracing::warn!("p1 sock_accept is not implemented"); self.transact()?.get_descriptor(fd)?; Err(types::Errno::Notsock.into()) } @@ -2577,7 +2609,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { ri_data: types::IovecArray, ri_flags: types::Riflags, ) -> Result<(types::Size, types::Roflags), types::Error> { - tracing::warn!("preview1 sock_recv is not implemented"); + tracing::warn!("p1 sock_recv is not implemented"); self.transact()?.get_descriptor(fd)?; Err(types::Errno::Notsock.into()) } @@ -2590,7 +2622,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { si_data: types::CiovecArray, _si_flags: types::Siflags, ) -> Result { - tracing::warn!("preview1 sock_send is not implemented"); + tracing::warn!("p1 sock_send is not implemented"); self.transact()?.get_descriptor(fd)?; Err(types::Errno::Notsock.into()) } @@ -2602,7 +2634,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { fd: types::Fd, how: types::Sdflags, ) -> Result<(), types::Error> { - tracing::warn!("preview1 sock_shutdown is not implemented"); + tracing::warn!("p1 sock_shutdown is not implemented"); self.transact()?.get_descriptor(fd)?; Err(types::Errno::Notsock.into()) } diff --git a/crates/wasi/src/p2/bindings.rs b/crates/wasi/src/p2/bindings.rs index 7cfdd1956e..5fb4e0d266 100644 --- a/crates/wasi/src/p2/bindings.rs +++ b/crates/wasi/src/p2/bindings.rs @@ -15,7 +15,7 @@ //! done using the `with` option to [`bindgen!`]: //! //! ```rust -//! use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView}; +//! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; //! use wasmtime::{Result, Engine, Config}; //! use wasmtime::component::{Linker, ResourceTable, HasSelf}; //! @@ -53,11 +53,10 @@ //! } //! } //! -//! impl IoView for MyState { -//! fn table(&mut self) -> &mut ResourceTable { &mut self.table } -//! } //! impl WasiView for MyState { -//! fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +//! fn ctx(&mut self) -> WasiCtxView<'_> { +//! WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +//! } //! } //! //! fn main() -> Result<()> { @@ -86,7 +85,7 @@ /// done using the `with` option to `bindgen!`: /// /// ```rust -/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime::{Result, Engine}; /// use wasmtime::component::{Linker, ResourceTable, HasSelf}; /// @@ -126,11 +125,10 @@ /// } /// } /// -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } /// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn ctx(&mut self) -> WasiCtxView<'_> { +/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +/// } /// } /// /// fn main() -> Result<()> { @@ -170,12 +168,12 @@ pub mod sync { // Configure the resource types of the bound interfaces here // to be the same as the async versions of the resources, that // way everything has the same type. - "wasi:filesystem/types/descriptor": super::super::filesystem::types::Descriptor, + "wasi:filesystem/types/descriptor": crate::filesystem::Descriptor, "wasi:filesystem/types/directory-entry-stream": super::super::filesystem::types::DirectoryEntryStream, "wasi:sockets/tcp/tcp-socket": super::super::sockets::tcp::TcpSocket, "wasi:sockets/udp/incoming-datagram-stream": super::super::sockets::udp::IncomingDatagramStream, "wasi:sockets/udp/outgoing-datagram-stream": super::super::sockets::udp::OutgoingDatagramStream, - "wasi:sockets/udp/udp-socket": super::super::sockets::udp::UdpSocket, + "wasi:sockets/udp/udp-socket": crate::sockets::UdpSocket, // Error host trait from wasmtime-wasi-io is synchronous, so we can alias it "wasi:io/error": wasmtime_wasi_io::bindings::wasi::io::error, @@ -210,7 +208,7 @@ pub mod sync { /// ```no_run /// use wasmtime::{Engine, Result, Store, Config}; /// use wasmtime::component::{ResourceTable, Linker, Component}; - /// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView, WasiCtxBuilder}; + /// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime_wasi::p2::bindings::sync::Command; /// /// // This example is an example shim of executing a component based on the @@ -230,7 +228,7 @@ pub mod sync { /// /// // Configure a `WasiCtx` based on this program's environment. Then /// // build a `Store` to instantiate into. - /// let mut builder = WasiCtxBuilder::new(); + /// let mut builder = WasiCtx::builder(); /// builder.inherit_stdio().inherit_env().args(&args[2..]); /// let mut store = Store::new( /// &engine, @@ -254,11 +252,10 @@ pub mod sync { /// table: ResourceTable, /// } /// - /// impl IoView for MyState { - /// fn table(&mut self) -> &mut ResourceTable { &mut self.table } - /// } /// impl WasiView for MyState { - /// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } + /// fn ctx(&mut self) -> WasiCtxView<'_> { + /// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } + /// } /// } /// ``` /// @@ -275,7 +272,7 @@ pub mod sync { /// ```no_run /// use wasmtime::{Engine, Result, Store, Config}; /// use wasmtime::component::{ResourceTable, Linker, Component}; - /// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView, WasiCtxBuilder}; + /// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime_wasi::p2::bindings::sync::CommandPre; /// /// // This example is an example shim of executing a component based on the @@ -296,7 +293,7 @@ pub mod sync { /// /// // Configure a `WasiCtx` based on this program's environment. Then /// // build a `Store` to instantiate into. - /// let mut builder = WasiCtxBuilder::new(); + /// let mut builder = WasiCtx::builder(); /// builder.inherit_stdio().inherit_env().args(&args); /// let mut store = Store::new( /// &engine, @@ -320,11 +317,10 @@ pub mod sync { /// table: ResourceTable, /// } /// - /// impl IoView for MyState { - /// fn table(&mut self) -> &mut ResourceTable { &mut self.table } - /// } /// impl WasiView for MyState { - /// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } + /// fn ctx(&mut self) -> WasiCtxView<'_> { + /// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } + /// } /// } /// ``` /// @@ -397,13 +393,13 @@ mod async_io { // Configure all other resources to be concrete types defined in // this crate "wasi:sockets/network/network": crate::p2::network::Network, - "wasi:sockets/tcp/tcp-socket": crate::p2::tcp::TcpSocket, - "wasi:sockets/udp/udp-socket": crate::p2::udp::UdpSocket, + "wasi:sockets/tcp/tcp-socket": crate::sockets::TcpSocket, + "wasi:sockets/udp/udp-socket": crate::sockets::UdpSocket, "wasi:sockets/udp/incoming-datagram-stream": crate::p2::udp::IncomingDatagramStream, "wasi:sockets/udp/outgoing-datagram-stream": crate::p2::udp::OutgoingDatagramStream, "wasi:sockets/ip-name-lookup/resolve-address-stream": crate::p2::ip_name_lookup::ResolveAddressStream, "wasi:filesystem/types/directory-entry-stream": crate::p2::filesystem::ReaddirIterator, - "wasi:filesystem/types/descriptor": crate::p2::filesystem::Descriptor, + "wasi:filesystem/types/descriptor": crate::filesystem::Descriptor, "wasi:cli/terminal-input/terminal-input": crate::p2::stdio::TerminalInput, "wasi:cli/terminal-output/terminal-output": crate::p2::stdio::TerminalOutput, }, @@ -432,7 +428,7 @@ pub use self::async_io::wasi::*; /// ```no_run /// use wasmtime::{Engine, Result, Store, Config}; /// use wasmtime::component::{ResourceTable, Linker, Component}; -/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView, WasiCtxBuilder}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime_wasi::p2::bindings::Command; /// /// // This example is an example shim of executing a component based on the @@ -455,7 +451,7 @@ pub use self::async_io::wasi::*; /// /// // Configure a `WasiCtx` based on this program's environment. Then /// // build a `Store` to instantiate into. -/// let mut builder = WasiCtxBuilder::new(); +/// let mut builder = WasiCtx::builder(); /// builder.inherit_stdio().inherit_env().args(&args); /// let mut store = Store::new( /// &engine, @@ -479,11 +475,10 @@ pub use self::async_io::wasi::*; /// table: ResourceTable, /// } /// -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } /// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn ctx(&mut self) -> WasiCtxView<'_> { +/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +/// } /// } /// ``` /// @@ -500,7 +495,7 @@ pub use self::async_io::Command; /// ```no_run /// use wasmtime::{Engine, Result, Store, Config}; /// use wasmtime::component::{ResourceTable, Linker, Component}; -/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView, WasiCtxBuilder}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime_wasi::p2::bindings::CommandPre; /// /// // This example is an example shim of executing a component based on the @@ -524,7 +519,7 @@ pub use self::async_io::Command; /// /// // Configure a `WasiCtx` based on this program's environment. Then /// // build a `Store` to instantiate into. -/// let mut builder = WasiCtxBuilder::new(); +/// let mut builder = WasiCtx::builder(); /// builder.inherit_stdio().inherit_env().args(&args); /// let mut store = Store::new( /// &engine, @@ -548,11 +543,10 @@ pub use self::async_io::Command; /// table: ResourceTable, /// } /// -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } /// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn ctx(&mut self) -> WasiCtxView<'_> { +/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +/// } /// } /// ``` /// diff --git a/crates/wasi/src/p2/ctx.rs b/crates/wasi/src/p2/ctx.rs deleted file mode 100644 index 2ef7fe41a6..0000000000 --- a/crates/wasi/src/p2/ctx.rs +++ /dev/null @@ -1,567 +0,0 @@ -use crate::cli::WasiCliCtx; -use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx}; -use crate::p2::filesystem::Dir; -use crate::p2::pipe; -use crate::p2::stdio::{self, StdinStream, StdoutStream}; -use crate::random::WasiRandomCtx; -use crate::sockets::{AllowedNetworkUses, SocketAddrCheck, SocketAddrUse, WasiSocketsCtx}; -use crate::{DirPerms, FilePerms, OpenMode}; -use anyhow::Result; -use cap_rand::RngCore; -use cap_std::ambient_authority; -use std::future::Future; -use std::mem; -use std::net::SocketAddr; -use std::path::Path; -use std::pin::Pin; - -/// Builder-style structure used to create a [`WasiCtx`]. -/// -/// This type is used to create a [`WasiCtx`] that is considered per-[`Store`] -/// state. The [`build`][WasiCtxBuilder::build] method is used to finish the -/// building process and produce a finalized [`WasiCtx`]. -/// -/// # Examples -/// -/// ``` -/// use wasmtime_wasi::p2::{WasiCtxBuilder, WasiCtx}; -/// -/// let mut wasi = WasiCtxBuilder::new(); -/// wasi.arg("./foo.wasm"); -/// wasi.arg("--help"); -/// wasi.env("FOO", "bar"); -/// -/// let wasi: WasiCtx = wasi.build(); -/// ``` -/// -/// [`Store`]: wasmtime::Store -pub struct WasiCtxBuilder { - common: crate::WasiCtxBuilder, Box>, - preopens: Vec<(Dir, String)>, - built: bool, -} - -impl WasiCtxBuilder { - /// Creates a builder for a new context with default parameters set. - /// - /// The current defaults are: - /// - /// * stdin is closed - /// * stdout and stderr eat all input and it doesn't go anywhere - /// * no env vars - /// * no arguments - /// * no preopens - /// * clocks use the host implementation of wall/monotonic clocks - /// * RNGs are all initialized with random state and suitable generator - /// quality to satisfy the requirements of WASI APIs. - /// * TCP/UDP are allowed but all addresses are denied by default. - /// * `wasi:sockets/ip-name-lookup` is denied by default. - /// - /// These defaults can all be updated via the various builder configuration - /// methods below. - pub fn new() -> Self { - Self { - common: crate::WasiCtxBuilder::new( - Box::new(pipe::ClosedInputStream), - Box::new(pipe::SinkOutputStream), - Box::new(pipe::SinkOutputStream), - ), - preopens: Vec::new(), - built: false, - } - } - - /// Provides a custom implementation of stdin to use. - /// - /// By default stdin is closed but an example of using the host's native - /// stdin looks like: - /// - /// ``` - /// use wasmtime_wasi::p2::{stdin, WasiCtxBuilder}; - /// - /// let mut wasi = WasiCtxBuilder::new(); - /// wasi.stdin(stdin()); - /// ``` - /// - /// Note that inheriting the process's stdin can also be done through - /// [`inherit_stdin`](WasiCtxBuilder::inherit_stdin). - pub fn stdin(&mut self, stdin: impl StdinStream + 'static) -> &mut Self { - self.common.stdin(Box::new(stdin)); - self - } - - /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout. - pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) -> &mut Self { - self.common.stdout(Box::new(stdout)); - self - } - - /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr. - pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) -> &mut Self { - self.common.stderr(Box::new(stderr)); - self - } - - /// Configures this context's stdin stream to read the host process's - /// stdin. - /// - /// Note that concurrent reads of stdin can produce surprising results so - /// when using this it's typically best to have a single wasm instance in - /// the process using this. - pub fn inherit_stdin(&mut self) -> &mut Self { - self.stdin(stdio::stdin()) - } - - /// Configures this context's stdout stream to write to the host process's - /// stdout. - /// - /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) - /// multiple instances printing to stdout works well. - pub fn inherit_stdout(&mut self) -> &mut Self { - self.stdout(stdio::stdout()) - } - - /// Configures this context's stderr stream to write to the host process's - /// stderr. - /// - /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) - /// multiple instances printing to stderr works well. - pub fn inherit_stderr(&mut self) -> &mut Self { - self.stderr(stdio::stderr()) - } - - /// Configures all of stdin, stdout, and stderr to be inherited from the - /// host process. - /// - /// See [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) for some rationale - /// on why this should only be done in situations of - /// one-instance-per-process. - pub fn inherit_stdio(&mut self) -> &mut Self { - self.inherit_stdin().inherit_stdout().inherit_stderr() - } - - /// Configures whether or not blocking operations made through this - /// `WasiCtx` are allowed to block the current thread. - /// - /// WASI is currently implemented on top of the Rust - /// [Tokio](https://tokio.rs/) library. While most WASI APIs are - /// non-blocking some are instead blocking from the perspective of - /// WebAssembly. For example opening a file is a blocking operation with - /// respect to WebAssembly but it's implemented as an asynchronous operation - /// on the host. This is currently done with Tokio's - /// [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html). - /// - /// When WebAssembly is used in a synchronous context, for example when - /// [`Config::async_support`] is disabled, then this asynchronous operation - /// is quickly turned back into a synchronous operation with a `block_on` in - /// Rust. This switching back-and-forth between a blocking a non-blocking - /// context can have overhead, and this option exists to help alleviate this - /// overhead. - /// - /// This option indicates that for WASI functions that are blocking from the - /// perspective of WebAssembly it's ok to block the native thread as well. - /// This means that this back-and-forth between async and sync won't happen - /// and instead blocking operations are performed on-thread (such as opening - /// a file). This can improve the performance of WASI operations when async - /// support is disabled. - /// - /// [`Config::async_support`]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support - pub fn allow_blocking_current_thread(&mut self, enable: bool) -> &mut Self { - self.common.allow_blocking_current_thread(enable); - self - } - - /// Appends multiple environment variables at once for this builder. - /// - /// All environment variables are appended to the list of environment - /// variables that this builder will configure. - /// - /// At this time environment variables are not deduplicated and if the same - /// key is set twice then the guest will see two entries for the same key. - /// - /// # Examples - /// - /// ``` - /// use wasmtime_wasi::p2::WasiCtxBuilder; - /// - /// let mut wasi = WasiCtxBuilder::new(); - /// wasi.envs(&[ - /// ("FOO", "bar"), - /// ("HOME", "/somewhere"), - /// ]); - /// ``` - pub fn envs(&mut self, env: &[(impl AsRef, impl AsRef)]) -> &mut Self { - self.common.envs(env); - self - } - - /// Appends a single environment variable for this builder. - /// - /// At this time environment variables are not deduplicated and if the same - /// key is set twice then the guest will see two entries for the same key. - /// - /// # Examples - /// - /// ``` - /// use wasmtime_wasi::p2::WasiCtxBuilder; - /// - /// let mut wasi = WasiCtxBuilder::new(); - /// wasi.env("FOO", "bar"); - /// ``` - pub fn env(&mut self, k: impl AsRef, v: impl AsRef) -> &mut Self { - self.common.env(k, v); - self - } - - /// Configures all environment variables to be inherited from the calling - /// process into this configuration. - /// - /// This will use [`envs`](WasiCtxBuilder::envs) to append all host-defined - /// environment variables. - pub fn inherit_env(&mut self) -> &mut Self { - self.common.inherit_env(); - self - } - - /// Appends a list of arguments to the argument array to pass to wasm. - pub fn args(&mut self, args: &[impl AsRef]) -> &mut Self { - self.common.args(args); - self - } - - /// Appends a single argument to get passed to wasm. - pub fn arg(&mut self, arg: impl AsRef) -> &mut Self { - self.common.arg(arg); - self - } - - /// Appends all host process arguments to the list of arguments to get - /// passed to wasm. - pub fn inherit_args(&mut self) -> &mut Self { - self.common.inherit_args(); - self - } - - /// Configures a "preopened directory" to be available to WebAssembly. - /// - /// By default WebAssembly does not have access to the filesystem because - /// there are no preopened directories. All filesystem operations, such as - /// opening a file, are done through a preexisting handle. This means that - /// to provide WebAssembly access to a directory it must be configured - /// through this API. - /// - /// WASI will also prevent access outside of files provided here. For - /// example `..` can't be used to traverse up from the `host_path` provided here - /// to the containing directory. - /// - /// * `host_path` - a path to a directory on the host to open and make - /// accessible to WebAssembly. Note that the name of this directory in the - /// guest is configured with `guest_path` below. - /// * `guest_path` - the name of the preopened directory from WebAssembly's - /// perspective. Note that this does not need to match the host's name for - /// the directory. - /// * `dir_perms` - this is the permissions that wasm will have to operate on - /// `guest_path`. This can be used, for example, to provide readonly access to a - /// directory. - /// * `file_perms` - similar to `dir_perms` but corresponds to the maximum set - /// of permissions that can be used for any file in this directory. - /// - /// # Errors - /// - /// This method will return an error if `host_path` cannot be opened. - /// - /// # Examples - /// - /// ``` - /// use wasmtime_wasi::p2::WasiCtxBuilder; - /// use wasmtime_wasi::{DirPerms, FilePerms}; - /// - /// # fn main() {} - /// # fn foo() -> wasmtime::Result<()> { - /// let mut wasi = WasiCtxBuilder::new(); - /// - /// // Make `./host-directory` available in the guest as `.` - /// wasi.preopened_dir("./host-directory", ".", DirPerms::all(), FilePerms::all()); - /// - /// // Make `./readonly` available in the guest as `./ro` - /// wasi.preopened_dir("./readonly", "./ro", DirPerms::READ, FilePerms::READ); - /// # Ok(()) - /// # } - /// ``` - pub fn preopened_dir( - &mut self, - host_path: impl AsRef, - guest_path: impl AsRef, - dir_perms: DirPerms, - file_perms: FilePerms, - ) -> Result<&mut Self> { - let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?; - let mut open_mode = OpenMode::empty(); - if dir_perms.contains(DirPerms::READ) { - open_mode |= OpenMode::READ; - } - if dir_perms.contains(DirPerms::MUTATE) { - open_mode |= OpenMode::WRITE; - } - self.preopens.push(( - Dir::new( - dir, - dir_perms, - file_perms, - open_mode, - self.common.allow_blocking_current_thread, - ), - guest_path.as_ref().to_owned(), - )); - Ok(self) - } - - /// Set the generator for the `wasi:random/random` number generator to the - /// custom generator specified. - /// - /// Note that contexts have a default RNG configured which is a suitable - /// generator for WASI and is configured with a random seed per-context. - /// - /// Guest code may rely on this random number generator to produce fresh - /// unpredictable random data in order to maintain its security invariants, - /// and ideally should use the insecure random API otherwise, so using any - /// prerecorded or otherwise predictable data may compromise security. - pub fn secure_random(&mut self, random: impl RngCore + Send + 'static) -> &mut Self { - self.common.secure_random(random); - self - } - - /// Configures the generator for `wasi:random/insecure`. - /// - /// The `insecure_random` generator provided will be used for all randomness - /// requested by the `wasi:random/insecure` interface. - pub fn insecure_random(&mut self, insecure_random: impl RngCore + Send + 'static) -> &mut Self { - self.common.insecure_random(insecure_random); - self - } - - /// Configures the seed to be returned from `wasi:random/insecure-seed` to - /// the specified custom value. - /// - /// By default this number is randomly generated when a builder is created. - pub fn insecure_random_seed(&mut self, insecure_random_seed: u128) -> &mut Self { - self.common.insecure_random_seed(insecure_random_seed); - self - } - - /// Configures `wasi:clocks/wall-clock` to use the `clock` specified. - /// - /// By default the host's wall clock is used. - pub fn wall_clock(&mut self, clock: impl HostWallClock + 'static) -> &mut Self { - self.common.wall_clock(clock); - self - } - - /// Configures `wasi:clocks/monotonic-clock` to use the `clock` specified. - /// - /// By default the host's monotonic clock is used. - pub fn monotonic_clock(&mut self, clock: impl HostMonotonicClock + 'static) -> &mut Self { - self.common.monotonic_clock(clock); - self - } - - /// Allow all network addresses accessible to the host. - /// - /// This method will inherit all network addresses meaning that any address - /// can be bound by the guest or connected to by the guest using any - /// protocol. - /// - /// See also [`WasiCtxBuilder::socket_addr_check`]. - pub fn inherit_network(&mut self) -> &mut Self { - self.common.inherit_network(); - self - } - - /// A check that will be called for each socket address that is used. - /// - /// Returning `true` will permit socket connections to the `SocketAddr`, - /// while returning `false` will reject the connection. - pub fn socket_addr_check(&mut self, check: F) -> &mut Self - where - F: Fn(SocketAddr, SocketAddrUse) -> Pin + Send + Sync>> - + Send - + Sync - + 'static, - { - self.common.socket_addr_check(check); - self - } - - /// Allow usage of `wasi:sockets/ip-name-lookup` - /// - /// By default this is disabled. - pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self { - self.common.allow_ip_name_lookup(enable); - self - } - - /// Allow usage of UDP. - /// - /// This is enabled by default, but can be disabled if UDP should be blanket - /// disabled. - pub fn allow_udp(&mut self, enable: bool) -> &mut Self { - self.common.allow_udp(enable); - self - } - - /// Allow usage of TCP - /// - /// This is enabled by default, but can be disabled if TCP should be blanket - /// disabled. - pub fn allow_tcp(&mut self, enable: bool) -> &mut Self { - self.common.allow_tcp(enable); - self - } - - /// Uses the configured context so far to construct the final [`WasiCtx`]. - /// - /// Note that each `WasiCtxBuilder` can only be used to "build" once, and - /// calling this method twice will panic. - /// - /// # Panics - /// - /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be - /// used to create only a single [`WasiCtx`]. Repeated usage of this method - /// is not allowed and should use a second builder instead. - pub fn build(&mut self) -> WasiCtx { - assert!(!self.built); - - let Self { - common: - crate::WasiCtxBuilder { - cli: - WasiCliCtx { - environment: env, - arguments: args, - initial_cwd: _, - stdin, - stdout, - stderr, - }, - clocks: - WasiClocksCtx { - wall_clock, - monotonic_clock, - }, - random, - sockets: - WasiSocketsCtx { - socket_addr_check, - allowed_network_uses, - }, - allow_blocking_current_thread, - }, - preopens, - built: _, - } = mem::replace(self, Self::new()); - self.built = true; - - WasiCtx { - stdin, - stdout, - stderr, - env, - args, - preopens, - socket_addr_check, - random, - wall_clock, - monotonic_clock, - allowed_network_uses, - allow_blocking_current_thread, - } - } - - /// Builds a WASIp1 context instead of a [`WasiCtx`]. - /// - /// This method is the same as [`build`](WasiCtxBuilder::build) but it - /// creates a [`WasiP1Ctx`] instead. This is intended for use with the - /// [`preview1`] module of this crate - /// - /// [`WasiP1Ctx`]: crate::preview1::WasiP1Ctx - /// [`preview1`]: crate::preview1 - /// - /// # Panics - /// - /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be - /// used to create only a single [`WasiCtx`] or [`WasiP1Ctx`]. Repeated - /// usage of this method is not allowed and should use a second builder - /// instead. - #[cfg(feature = "preview1")] - pub fn build_p1(&mut self) -> crate::preview1::WasiP1Ctx { - let wasi = self.build(); - crate::preview1::WasiP1Ctx::new(wasi) - } -} - -/// Per-[`Store`] state which holds state necessary to implement WASI from this -/// crate. -/// -/// This structure is created through [`WasiCtxBuilder`] and is stored within -/// the `T` of [`Store`][`Store`]. Access to the structure is provided -/// through the [`WasiView`](crate::p2::WasiView) trait as an implementation on `T`. -/// -/// Note that this structure itself does not have any accessors, it's here for -/// internal use within the `wasmtime-wasi` crate's implementation of -/// bindgen-generated traits. -/// -/// [`Store`]: wasmtime::Store -/// -/// # Example -/// -/// ``` -/// use wasmtime_wasi::ResourceTable; -/// use wasmtime_wasi::p2::{WasiCtx, WasiView, IoView, WasiCtxBuilder}; -/// -/// struct MyState { -/// ctx: WasiCtx, -/// table: ResourceTable, -/// } -/// -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } -/// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } -/// } -/// -/// impl MyState { -/// fn new() -> MyState { -/// let mut wasi = WasiCtxBuilder::new(); -/// wasi.arg("./foo.wasm"); -/// wasi.arg("--help"); -/// wasi.env("FOO", "bar"); -/// -/// MyState { -/// ctx: wasi.build(), -/// table: ResourceTable::new(), -/// } -/// } -/// } -/// ``` -pub struct WasiCtx { - pub(crate) random: WasiRandomCtx, - pub(crate) wall_clock: Box, - pub(crate) monotonic_clock: Box, - pub(crate) env: Vec<(String, String)>, - pub(crate) args: Vec, - pub(crate) preopens: Vec<(Dir, String)>, - pub(crate) stdin: Box, - pub(crate) stdout: Box, - pub(crate) stderr: Box, - pub(crate) socket_addr_check: SocketAddrCheck, - pub(crate) allowed_network_uses: AllowedNetworkUses, - pub(crate) allow_blocking_current_thread: bool, -} - -impl WasiCtx { - /// Convenience function for calling [`WasiCtxBuilder::new`]. - pub fn builder() -> WasiCtxBuilder { - WasiCtxBuilder::new() - } -} diff --git a/crates/wasi/src/p2/filesystem.rs b/crates/wasi/src/p2/filesystem.rs index 8c3c7c4bfa..608349dcbe 100644 --- a/crates/wasi/src/p2/filesystem.rs +++ b/crates/wasi/src/p2/filesystem.rs @@ -1,217 +1,64 @@ +use crate::TrappableError; +use crate::filesystem::File; use crate::p2::bindings::filesystem::types; use crate::p2::{InputStream, OutputStream, Pollable, StreamError, StreamResult}; -use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking}; -use crate::{DirPerms, FilePerms, OpenMode, TrappableError}; +use crate::runtime::AbortOnDropJoinHandle; use anyhow::anyhow; use bytes::{Bytes, BytesMut}; use std::io; use std::mem; -use std::sync::Arc; pub type FsResult = Result; pub type FsError = TrappableError; -impl From for FsError { - fn from(error: wasmtime::component::ResourceTableError) -> Self { - Self::trap(error) +impl From for types::ErrorCode { + fn from(error: crate::filesystem::ErrorCode) -> Self { + match error { + crate::filesystem::ErrorCode::Access => Self::Access, + crate::filesystem::ErrorCode::Already => Self::Already, + crate::filesystem::ErrorCode::BadDescriptor => Self::BadDescriptor, + crate::filesystem::ErrorCode::Busy => Self::Busy, + crate::filesystem::ErrorCode::Exist => Self::Exist, + crate::filesystem::ErrorCode::FileTooLarge => Self::FileTooLarge, + crate::filesystem::ErrorCode::IllegalByteSequence => Self::IllegalByteSequence, + crate::filesystem::ErrorCode::InProgress => Self::InProgress, + crate::filesystem::ErrorCode::Interrupted => Self::Interrupted, + crate::filesystem::ErrorCode::Invalid => Self::Invalid, + crate::filesystem::ErrorCode::Io => Self::Io, + crate::filesystem::ErrorCode::IsDirectory => Self::IsDirectory, + crate::filesystem::ErrorCode::Loop => Self::Loop, + crate::filesystem::ErrorCode::TooManyLinks => Self::TooManyLinks, + crate::filesystem::ErrorCode::NameTooLong => Self::NameTooLong, + crate::filesystem::ErrorCode::NoEntry => Self::NoEntry, + crate::filesystem::ErrorCode::InsufficientMemory => Self::InsufficientMemory, + crate::filesystem::ErrorCode::InsufficientSpace => Self::InsufficientSpace, + crate::filesystem::ErrorCode::NotDirectory => Self::NotDirectory, + crate::filesystem::ErrorCode::NotEmpty => Self::NotEmpty, + crate::filesystem::ErrorCode::Unsupported => Self::Unsupported, + crate::filesystem::ErrorCode::Overflow => Self::Overflow, + crate::filesystem::ErrorCode::NotPermitted => Self::NotPermitted, + crate::filesystem::ErrorCode::Pipe => Self::Pipe, + crate::filesystem::ErrorCode::InvalidSeek => Self::InvalidSeek, + } } } -impl From for FsError { - fn from(error: io::Error) -> Self { +impl From for FsError { + fn from(error: crate::filesystem::ErrorCode) -> Self { types::ErrorCode::from(error).into() } } -pub enum Descriptor { - File(File), - Dir(Dir), -} - -impl Descriptor { - pub fn file(&self) -> Result<&File, types::ErrorCode> { - match self { - Descriptor::File(f) => Ok(f), - Descriptor::Dir(_) => Err(types::ErrorCode::BadDescriptor), - } - } - - pub fn dir(&self) -> Result<&Dir, types::ErrorCode> { - match self { - Descriptor::Dir(d) => Ok(d), - Descriptor::File(_) => Err(types::ErrorCode::NotDirectory), - } - } - - pub fn is_file(&self) -> bool { - match self { - Descriptor::File(_) => true, - Descriptor::Dir(_) => false, - } - } - - pub fn is_dir(&self) -> bool { - match self { - Descriptor::File(_) => false, - Descriptor::Dir(_) => true, - } - } -} - -#[derive(Clone)] -pub struct File { - /// The operating system File this struct is mediating access to. - /// - /// Wrapped in an Arc because the same underlying file is used for - /// implementing the stream types. A copy is also needed for - /// [`spawn_blocking`]. - /// - /// [`spawn_blocking`]: Self::spawn_blocking - pub file: Arc, - /// Permissions to enforce on access to the file. These permissions are - /// specified by a user of the `crate::p2::WasiCtxBuilder`, and are - /// enforced prior to any enforced by the underlying operating system. - pub perms: FilePerms, - /// The mode the file was opened under: bits for reading, and writing. - /// Required to correctly report the DescriptorFlags, because cap-std - /// doesn't presently provide a cross-platform equivalent of reading the - /// oflags back out using fcntl. - pub open_mode: OpenMode, - - allow_blocking_current_thread: bool, -} - -impl File { - pub fn new( - file: cap_std::fs::File, - perms: FilePerms, - open_mode: OpenMode, - allow_blocking_current_thread: bool, - ) -> Self { - Self { - file: Arc::new(file), - perms, - open_mode, - allow_blocking_current_thread, - } - } - - /// Execute the blocking `body` function. - /// - /// Depending on how the WasiCtx was configured, the body may either be: - /// - Executed directly on the current thread. In this case the `async` - /// signature of this method is effectively a lie and the returned - /// Future will always be immediately Ready. Or: - /// - Spawned on a background thread using [`tokio::task::spawn_blocking`] - /// and immediately awaited. - /// - /// Intentionally blocking the executor thread might seem unorthodox, but is - /// not actually a problem for specific workloads. See: - /// - [`crate::p2::WasiCtxBuilder::allow_blocking_current_thread`] - /// - [Poor performance of wasmtime file I/O maybe because tokio](https://github.com/bytecodealliance/wasmtime/issues/7973) - /// - [Implement opt-in for enabling WASI to block the current thread](https://github.com/bytecodealliance/wasmtime/pull/8190) - pub(crate) async fn run_blocking(&self, body: F) -> R - where - F: FnOnce(&cap_std::fs::File) -> R + Send + 'static, - R: Send + 'static, - { - match self.as_blocking_file() { - Some(file) => body(file), - None => self.spawn_blocking(body).await, - } - } - - pub(crate) fn spawn_blocking(&self, body: F) -> AbortOnDropJoinHandle - where - F: FnOnce(&cap_std::fs::File) -> R + Send + 'static, - R: Send + 'static, - { - let f = self.file.clone(); - spawn_blocking(move || body(&f)) - } - - /// Returns `Some` when the current thread is allowed to block in filesystem - /// operations, and otherwise returns `None` to indicate that - /// `spawn_blocking` must be used. - pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> { - if self.allow_blocking_current_thread { - Some(&self.file) - } else { - None - } +impl From for FsError { + fn from(error: wasmtime::component::ResourceTableError) -> Self { + Self::trap(error) } } -#[derive(Clone)] -pub struct Dir { - /// The operating system file descriptor this struct is mediating access - /// to. - /// - /// Wrapped in an Arc because a copy is needed for [`spawn_blocking`]. - /// - /// [`spawn_blocking`]: Self::spawn_blocking - pub dir: Arc, - /// Permissions to enforce on access to this directory. These permissions - /// are specified by a user of the `crate::p2::WasiCtxBuilder`, and - /// are enforced prior to any enforced by the underlying operating system. - /// - /// These permissions are also enforced on any directories opened under - /// this directory. - pub perms: DirPerms, - /// Permissions to enforce on any files opened under this directory. - pub file_perms: FilePerms, - /// The mode the directory was opened under: bits for reading, and writing. - /// Required to correctly report the DescriptorFlags, because cap-std - /// doesn't presently provide a cross-platform equivalent of reading the - /// oflags back out using fcntl. - pub open_mode: OpenMode, - - allow_blocking_current_thread: bool, -} - -impl Dir { - pub fn new( - dir: cap_std::fs::Dir, - perms: DirPerms, - file_perms: FilePerms, - open_mode: OpenMode, - allow_blocking_current_thread: bool, - ) -> Self { - Dir { - dir: Arc::new(dir), - perms, - file_perms, - open_mode, - allow_blocking_current_thread, - } - } - - /// Execute the blocking `body` function. - /// - /// Depending on how the WasiCtx was configured, the body may either be: - /// - Executed directly on the current thread. In this case the `async` - /// signature of this method is effectively a lie and the returned - /// Future will always be immediately Ready. Or: - /// - Spawned on a background thread using [`tokio::task::spawn_blocking`] - /// and immediately awaited. - /// - /// Intentionally blocking the executor thread might seem unorthodox, but is - /// not actually a problem for specific workloads. See: - /// - [`crate::p2::WasiCtxBuilder::allow_blocking_current_thread`] - /// - [Poor performance of wasmtime file I/O maybe because tokio](https://github.com/bytecodealliance/wasmtime/issues/7973) - /// - [Implement opt-in for enabling WASI to block the current thread](https://github.com/bytecodealliance/wasmtime/pull/8190) - pub(crate) async fn run_blocking(&self, body: F) -> R - where - F: FnOnce(&cap_std::fs::Dir) -> R + Send + 'static, - R: Send + 'static, - { - if self.allow_blocking_current_thread { - body(&self.dir) - } else { - let d = self.dir.clone(); - spawn_blocking(move || body(&d)).await - } +impl From for FsError { + fn from(error: io::Error) -> Self { + types::ErrorCode::from(error).into() } } diff --git a/crates/wasi/src/p2/host/clocks.rs b/crates/wasi/src/p2/host/clocks.rs index 70cd4c21f7..95dee36fcf 100644 --- a/crates/wasi/src/p2/host/clocks.rs +++ b/crates/wasi/src/p2/host/clocks.rs @@ -1,33 +1,54 @@ +use crate::clocks::WasiClocksCtxView; +use crate::p2::DynPollable; use crate::p2::bindings::{ clocks::monotonic_clock::{self, Duration as WasiDuration, Instant}, clocks::wall_clock::{self, Datetime}, }; -use crate::p2::{DynPollable, IoView, WasiImpl, WasiView}; use cap_std::time::SystemTime; use std::time::Duration; use wasmtime::component::Resource; use wasmtime_wasi_io::poll::{Pollable, subscribe}; +impl From for Datetime { + fn from( + crate::clocks::Datetime { + seconds, + nanoseconds, + }: crate::clocks::Datetime, + ) -> Self { + Self { + seconds, + nanoseconds, + } + } +} + +impl From for crate::clocks::Datetime { + fn from( + Datetime { + seconds, + nanoseconds, + }: Datetime, + ) -> Self { + Self { + seconds, + nanoseconds, + } + } +} + impl TryFrom for Datetime { - type Error = anyhow::Error; + type Error = wasmtime::Error; fn try_from(time: SystemTime) -> Result { - let duration = - time.duration_since(SystemTime::from_std(std::time::SystemTime::UNIX_EPOCH))?; - - Ok(Datetime { - seconds: duration.as_secs(), - nanoseconds: duration.subsec_nanos(), - }) + let time = crate::clocks::Datetime::try_from(time)?; + Ok(time.into()) } } -impl wall_clock::Host for WasiImpl -where - T: WasiView, -{ +impl wall_clock::Host for WasiClocksCtxView<'_> { fn now(&mut self) -> anyhow::Result { - let now = self.ctx().wall_clock.now(); + let now = self.ctx.wall_clock.now(); Ok(Datetime { seconds: now.as_secs(), nanoseconds: now.subsec_nanos(), @@ -35,7 +56,7 @@ where } fn resolution(&mut self) -> anyhow::Result { - let res = self.ctx().wall_clock.resolution(); + let res = self.ctx.wall_clock.resolution(); Ok(Datetime { seconds: res.as_secs(), nanoseconds: res.subsec_nanos(), @@ -62,33 +83,30 @@ fn subscribe_to_duration( subscribe(table, sleep) } -impl monotonic_clock::Host for WasiImpl -where - T: WasiView, -{ +impl monotonic_clock::Host for WasiClocksCtxView<'_> { fn now(&mut self) -> anyhow::Result { - Ok(self.ctx().monotonic_clock.now()) + Ok(self.ctx.monotonic_clock.now()) } fn resolution(&mut self) -> anyhow::Result { - Ok(self.ctx().monotonic_clock.resolution()) + Ok(self.ctx.monotonic_clock.resolution()) } fn subscribe_instant(&mut self, when: Instant) -> anyhow::Result> { - let clock_now = self.ctx().monotonic_clock.now(); + let clock_now = self.ctx.monotonic_clock.now(); let duration = if when > clock_now { Duration::from_nanos(when - clock_now) } else { Duration::from_nanos(0) }; - subscribe_to_duration(&mut self.table(), duration) + subscribe_to_duration(self.table, duration) } fn subscribe_duration( &mut self, duration: WasiDuration, ) -> anyhow::Result> { - subscribe_to_duration(&mut self.table(), Duration::from_nanos(duration)) + subscribe_to_duration(self.table, Duration::from_nanos(duration)) } } diff --git a/crates/wasi/src/p2/host/env.rs b/crates/wasi/src/p2/host/env.rs index 02652e3274..0ca8d1c703 100644 --- a/crates/wasi/src/p2/host/env.rs +++ b/crates/wasi/src/p2/host/env.rs @@ -1,18 +1,14 @@ +use crate::cli::WasiCliCtxView; use crate::p2::bindings::cli::environment; -use crate::p2::{WasiImpl, WasiView}; -impl environment::Host for WasiImpl -where - T: WasiView, -{ +impl environment::Host for WasiCliCtxView<'_> { fn get_environment(&mut self) -> anyhow::Result> { - Ok(self.ctx().env.clone()) + Ok(self.ctx.environment.clone()) } fn get_arguments(&mut self) -> anyhow::Result> { - Ok(self.ctx().args.clone()) + Ok(self.ctx.arguments.clone()) } fn initial_cwd(&mut self) -> anyhow::Result> { - // FIXME: expose cwd in builder and save in ctx - Ok(None) + Ok(self.ctx.initial_cwd.clone()) } } diff --git a/crates/wasi/src/p2/host/exit.rs b/crates/wasi/src/p2/host/exit.rs index b55c4dd394..3e5253f4b8 100644 --- a/crates/wasi/src/p2/host/exit.rs +++ b/crates/wasi/src/p2/host/exit.rs @@ -1,10 +1,8 @@ use crate::I32Exit; -use crate::p2::{WasiImpl, WasiView, bindings::cli::exit}; +use crate::cli::WasiCliCtxView; +use crate::p2::bindings::cli::exit; -impl exit::Host for WasiImpl -where - T: WasiView, -{ +impl exit::Host for WasiCliCtxView<'_> { fn exit(&mut self, status: Result<(), ()>) -> anyhow::Result<()> { let status = match status { Ok(()) => 0, diff --git a/crates/wasi/src/p2/host/filesystem.rs b/crates/wasi/src/p2/host/filesystem.rs index 9938055491..7e10d6d665 100644 --- a/crates/wasi/src/p2/host/filesystem.rs +++ b/crates/wasi/src/p2/host/filesystem.rs @@ -1,42 +1,24 @@ +use crate::filesystem::{Descriptor, WasiFilesystemCtxView}; use crate::p2::bindings::clocks::wall_clock; use crate::p2::bindings::filesystem::preopens; use crate::p2::bindings::filesystem::types::{ self, ErrorCode, HostDescriptor, HostDirectoryEntryStream, }; -use crate::p2::filesystem::{ - Descriptor, Dir, File, FileInputStream, FileOutputStream, ReaddirIterator, -}; -use crate::p2::{FsError, FsResult, IoView, WasiImpl, WasiView}; -use crate::{DirPerms, FilePerms, OpenMode}; -use anyhow::Context; +use crate::p2::filesystem::{FileInputStream, FileOutputStream, ReaddirIterator}; +use crate::p2::{FsError, FsResult}; +use crate::{DirPerms, FilePerms}; use wasmtime::component::Resource; use wasmtime_wasi_io::streams::{DynInputStream, DynOutputStream}; mod sync; -impl preopens::Host for WasiImpl -where - T: WasiView, -{ - fn get_directories( - &mut self, - ) -> Result, String)>, anyhow::Error> { - let mut results = Vec::new(); - for (dir, name) in self.ctx().preopens.clone() { - let fd = self - .table() - .push(Descriptor::Dir(dir)) - .with_context(|| format!("failed to push preopen {name}"))?; - results.push((fd, name)); - } - Ok(results) +impl preopens::Host for WasiFilesystemCtxView<'_> { + fn get_directories(&mut self) -> wasmtime::Result, String)>> { + self.get_directories() } } -impl types::Host for WasiImpl -where - T: WasiView, -{ +impl types::Host for WasiFilesystemCtxView<'_> { fn convert_error_code(&mut self, err: FsError) -> anyhow::Result { err.downcast() } @@ -45,7 +27,7 @@ where &mut self, err: Resource, ) -> anyhow::Result> { - let err = self.table().get(&err)?; + let err = self.table.get(&err)?; // Currently `err` always comes from the stream implementation which // uses standard reads/writes so only check for `std::io::Error` here. @@ -57,10 +39,7 @@ where } } -impl HostDescriptor for WasiImpl -where - T: WasiView, -{ +impl HostDescriptor for WasiFilesystemCtxView<'_> { async fn advise( &mut self, fd: Resource, @@ -68,112 +47,33 @@ where len: types::Filesize, advice: types::Advice, ) -> FsResult<()> { - use system_interface::fs::{Advice as A, FileIoExt}; - use types::Advice; - - let advice = match advice { - Advice::Normal => A::Normal, - Advice::Sequential => A::Sequential, - Advice::Random => A::Random, - Advice::WillNeed => A::WillNeed, - Advice::DontNeed => A::DontNeed, - Advice::NoReuse => A::NoReuse, - }; - - let f = self.table().get(&fd)?.file()?; - f.run_blocking(move |f| f.advise(offset, len, advice)) - .await?; + let f = self.table.get(&fd)?.file()?; + f.advise(offset, len, advice.into()).await?; Ok(()) } async fn sync_data(&mut self, fd: Resource) -> FsResult<()> { - let descriptor = self.table().get(&fd)?; - - match descriptor { - Descriptor::File(f) => { - match f.run_blocking(|f| f.sync_data()).await { - Ok(()) => Ok(()), - // On windows, `sync_data` uses `FileFlushBuffers` which fails with - // `ERROR_ACCESS_DENIED` if the file is not upen for writing. Ignore - // this error, for POSIX compatibility. - #[cfg(windows)] - Err(e) - if e.raw_os_error() - == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) => - { - Ok(()) - } - Err(e) => Err(e.into()), - } - } - Descriptor::Dir(d) => { - d.run_blocking(|d| Ok(d.open(std::path::Component::CurDir)?.sync_data()?)) - .await - } - } + let descriptor = self.table.get(&fd)?; + descriptor.sync_data().await?; + Ok(()) } async fn get_flags( &mut self, fd: Resource, ) -> FsResult { - use system_interface::fs::{FdFlags, GetSetFdFlags}; - use types::DescriptorFlags; - - fn get_from_fdflags(flags: FdFlags) -> DescriptorFlags { - let mut out = DescriptorFlags::empty(); - if flags.contains(FdFlags::DSYNC) { - out |= DescriptorFlags::REQUESTED_WRITE_SYNC; - } - if flags.contains(FdFlags::RSYNC) { - out |= DescriptorFlags::DATA_INTEGRITY_SYNC; - } - if flags.contains(FdFlags::SYNC) { - out |= DescriptorFlags::FILE_INTEGRITY_SYNC; - } - out - } - - let descriptor = self.table().get(&fd)?; - match descriptor { - Descriptor::File(f) => { - let flags = f.run_blocking(|f| f.get_fd_flags()).await?; - let mut flags = get_from_fdflags(flags); - if f.open_mode.contains(OpenMode::READ) { - flags |= DescriptorFlags::READ; - } - if f.open_mode.contains(OpenMode::WRITE) { - flags |= DescriptorFlags::WRITE; - } - Ok(flags) - } - Descriptor::Dir(d) => { - let flags = d.run_blocking(|d| d.get_fd_flags()).await?; - let mut flags = get_from_fdflags(flags); - if d.open_mode.contains(OpenMode::READ) { - flags |= DescriptorFlags::READ; - } - if d.open_mode.contains(OpenMode::WRITE) { - flags |= DescriptorFlags::MUTATE_DIRECTORY; - } - Ok(flags) - } - } + let descriptor = self.table.get(&fd)?; + let flags = descriptor.get_flags().await?; + Ok(flags.into()) } async fn get_type( &mut self, fd: Resource, ) -> FsResult { - let descriptor = self.table().get(&fd)?; - - match descriptor { - Descriptor::File(f) => { - let meta = f.run_blocking(|f| f.metadata()).await?; - Ok(descriptortype_from(meta.file_type())) - } - Descriptor::Dir(_) => Ok(types::DescriptorType::Directory), - } + let descriptor = self.table.get(&fd)?; + let ty = descriptor.get_type().await?; + Ok(ty.into()) } async fn set_size( @@ -181,11 +81,8 @@ where fd: Resource, size: types::Filesize, ) -> FsResult<()> { - let f = self.table().get(&fd)?.file()?; - if !f.perms.contains(FilePerms::WRITE) { - Err(ErrorCode::NotPermitted)?; - } - f.run_blocking(move |f| f.set_len(size)).await?; + let f = self.table.get(&fd)?.file()?; + f.set_size(size).await?; Ok(()) } @@ -195,29 +92,11 @@ where atim: types::NewTimestamp, mtim: types::NewTimestamp, ) -> FsResult<()> { - use fs_set_times::SetTimes; - - let descriptor = self.table().get(&fd)?; - match descriptor { - Descriptor::File(f) => { - if !f.perms.contains(FilePerms::WRITE) { - return Err(ErrorCode::NotPermitted.into()); - } - let atim = systemtimespec_from(atim)?; - let mtim = systemtimespec_from(mtim)?; - f.run_blocking(|f| f.set_times(atim, mtim)).await?; - Ok(()) - } - Descriptor::Dir(d) => { - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted.into()); - } - let atim = systemtimespec_from(atim)?; - let mtim = systemtimespec_from(mtim)?; - d.run_blocking(|d| d.set_times(atim, mtim)).await?; - Ok(()) - } - } + let descriptor = self.table.get(&fd)?; + let atim = systemtimespec_from(atim)?; + let mtim = systemtimespec_from(mtim)?; + descriptor.set_times(atim, mtim).await?; + Ok(()) } async fn read( @@ -229,9 +108,7 @@ where use std::io::IoSliceMut; use system_interface::fs::FileIoExt; - let table = self.table(); - - let f = table.get(&fd)?.file()?; + let f = self.table.get(&fd)?.file()?; if !f.perms.contains(FilePerms::READ) { return Err(ErrorCode::NotPermitted.into()); } @@ -263,8 +140,7 @@ where use std::io::IoSlice; use system_interface::fs::FileIoExt; - let table = self.table(); - let f = table.get(&fd)?.file()?; + let f = self.table.get(&fd)?.file()?; if !f.perms.contains(FilePerms::WRITE) { return Err(ErrorCode::NotPermitted.into()); } @@ -280,8 +156,7 @@ where &mut self, fd: Resource, ) -> FsResult> { - let table = self.table(); - let d = table.get(&fd)?.dir()?; + let d = self.table.get(&fd)?.dir()?; if !d.perms.contains(DirPerms::READ) { return Err(ErrorCode::NotPermitted.into()); } @@ -338,34 +213,13 @@ where Err(ReaddirError::Io(e)) => Err(e.into()), Err(ReaddirError::IllegalSequence) => Err(ErrorCode::IllegalByteSequence.into()), }); - Ok(table.push(ReaddirIterator::new(entries))?) + Ok(self.table.push(ReaddirIterator::new(entries))?) } async fn sync(&mut self, fd: Resource) -> FsResult<()> { - let descriptor = self.table().get(&fd)?; - - match descriptor { - Descriptor::File(f) => { - match f.run_blocking(|f| f.sync_all()).await { - Ok(()) => Ok(()), - // On windows, `sync_data` uses `FileFlushBuffers` which fails with - // `ERROR_ACCESS_DENIED` if the file is not upen for writing. Ignore - // this error, for POSIX compatibility. - #[cfg(windows)] - Err(e) - if e.raw_os_error() - == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) => - { - Ok(()) - } - Err(e) => Err(e.into()), - } - } - Descriptor::Dir(d) => { - d.run_blocking(|d| Ok(d.open(std::path::Component::CurDir)?.sync_all()?)) - .await - } - } + let descriptor = self.table.get(&fd)?; + descriptor.sync().await?; + Ok(()) } async fn create_directory_at( @@ -373,29 +227,15 @@ where fd: Resource, path: String, ) -> FsResult<()> { - let table = self.table(); - let d = table.get(&fd)?.dir()?; - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted.into()); - } - d.run_blocking(move |d| d.create_dir(&path)).await?; + let d = self.table.get(&fd)?.dir()?; + d.create_directory_at(path).await?; Ok(()) } async fn stat(&mut self, fd: Resource) -> FsResult { - let descriptor = self.table().get(&fd)?; - match descriptor { - Descriptor::File(f) => { - // No permissions check on stat: if opened, allowed to stat it - let meta = f.run_blocking(|f| f.metadata()).await?; - Ok(descriptorstat_from(meta)) - } - Descriptor::Dir(d) => { - // No permissions check on stat: if opened, allowed to stat it - let meta = d.run_blocking(|d| d.dir_metadata()).await?; - Ok(descriptorstat_from(meta)) - } - } + let descriptor = self.table.get(&fd)?; + let stat = descriptor.stat().await?; + Ok(stat.into()) } async fn stat_at( @@ -404,18 +244,9 @@ where path_flags: types::PathFlags, path: String, ) -> FsResult { - let table = self.table(); - let d = table.get(&fd)?.dir()?; - if !d.perms.contains(DirPerms::READ) { - return Err(ErrorCode::NotPermitted.into()); - } - - let meta = if symlink_follow(path_flags) { - d.run_blocking(move |d| d.metadata(&path)).await? - } else { - d.run_blocking(move |d| d.symlink_metadata(&path)).await? - }; - Ok(descriptorstat_from(meta)) + let d = self.table.get(&fd)?.dir()?; + let stat = d.stat_at(path_flags.into(), path).await?; + Ok(stat.into()) } async fn set_times_at( @@ -426,34 +257,10 @@ where atim: types::NewTimestamp, mtim: types::NewTimestamp, ) -> FsResult<()> { - use cap_fs_ext::DirExt; - - let table = self.table(); - let d = table.get(&fd)?.dir()?; - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted.into()); - } + let d = self.table.get(&fd)?.dir()?; let atim = systemtimespec_from(atim)?; let mtim = systemtimespec_from(mtim)?; - if symlink_follow(path_flags) { - d.run_blocking(move |d| { - d.set_times( - &path, - atim.map(cap_fs_ext::SystemTimeSpec::from_std), - mtim.map(cap_fs_ext::SystemTimeSpec::from_std), - ) - }) - .await?; - } else { - d.run_blocking(move |d| { - d.set_symlink_times( - &path, - atim.map(cap_fs_ext::SystemTimeSpec::from_std), - mtim.map(cap_fs_ext::SystemTimeSpec::from_std), - ) - }) - .await?; - } + d.set_times_at(path_flags.into(), path, atim, mtim).await?; Ok(()) } @@ -466,21 +273,10 @@ where new_descriptor: Resource, new_path: String, ) -> FsResult<()> { - let table = self.table(); - let old_dir = table.get(&fd)?.dir()?; - if !old_dir.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted.into()); - } - let new_dir = table.get(&new_descriptor)?.dir()?; - if !new_dir.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted.into()); - } - if symlink_follow(old_path_flags) { - return Err(ErrorCode::Invalid.into()); - } - let new_dir_handle = std::sync::Arc::clone(&new_dir.dir); + let old_dir = self.table.get(&fd)?.dir()?; + let new_dir = self.table.get(&new_descriptor)?.dir()?; old_dir - .run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path)) + .link_at(old_path_flags.into(), old_path, new_dir, new_path) .await?; Ok(()) } @@ -493,150 +289,27 @@ where oflags: types::OpenFlags, flags: types::DescriptorFlags, ) -> FsResult> { - use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt}; - use system_interface::fs::{FdFlags, GetSetFdFlags}; - use types::{DescriptorFlags, OpenFlags}; - - let allow_blocking_current_thread = self.ctx().allow_blocking_current_thread; - let table = self.table(); - let d = table.get(&fd)?.dir()?; - if !d.perms.contains(DirPerms::READ) { - Err(ErrorCode::NotPermitted)?; - } - - if !d.perms.contains(DirPerms::MUTATE) { - if oflags.contains(OpenFlags::CREATE) || oflags.contains(OpenFlags::TRUNCATE) { - Err(ErrorCode::NotPermitted)?; - } - if flags.contains(DescriptorFlags::WRITE) { - Err(ErrorCode::NotPermitted)?; - } - } - - // Track whether we are creating file, for permission check: - let mut create = false; - // Track open mode, for permission check and recording in created descriptor: - let mut open_mode = OpenMode::empty(); - // Construct the OpenOptions to give the OS: - let mut opts = cap_std::fs::OpenOptions::new(); - opts.maybe_dir(true); - - if oflags.contains(OpenFlags::CREATE) { - if oflags.contains(OpenFlags::EXCLUSIVE) { - opts.create_new(true); - } else { - opts.create(true); - } - create = true; - opts.write(true); - open_mode |= OpenMode::WRITE; - } - - if oflags.contains(OpenFlags::TRUNCATE) { - opts.truncate(true).write(true); - } - if flags.contains(DescriptorFlags::READ) { - opts.read(true); - open_mode |= OpenMode::READ; - } - if flags.contains(DescriptorFlags::WRITE) { - opts.write(true); - open_mode |= OpenMode::WRITE; - } else { - // If not opened write, open read. This way the OS lets us open - // the file, but we can use perms to reject use of the file later. - opts.read(true); - open_mode |= OpenMode::READ; - } - if symlink_follow(path_flags) { - opts.follow(FollowSymlinks::Yes); - } else { - opts.follow(FollowSymlinks::No); - } - - // These flags are not yet supported in cap-std: - if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC) - || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC) - || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC) - { - Err(ErrorCode::Unsupported)?; - } - - if oflags.contains(OpenFlags::DIRECTORY) { - if oflags.contains(OpenFlags::CREATE) - || oflags.contains(OpenFlags::EXCLUSIVE) - || oflags.contains(OpenFlags::TRUNCATE) - { - Err(ErrorCode::Invalid)?; - } - } - - // Now enforce this WasiCtx's permissions before letting the OS have - // its shot: - if !d.perms.contains(DirPerms::MUTATE) && create { - Err(ErrorCode::NotPermitted)?; - } - if !d.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) { - Err(ErrorCode::NotPermitted)?; - } - - // Represents each possible outcome from the spawn_blocking operation. - // This makes sure we don't have to give spawn_blocking any way to - // manipulate the table. - enum OpenResult { - Dir(cap_std::fs::Dir), - File(cap_std::fs::File), - NotDir, - } - - let opened = d - .run_blocking::<_, std::io::Result>(move |d| { - let mut opened = d.open_with(&path, &opts)?; - if opened.metadata()?.is_dir() { - Ok(OpenResult::Dir(cap_std::fs::Dir::from_std_file( - opened.into_std(), - ))) - } else if oflags.contains(OpenFlags::DIRECTORY) { - Ok(OpenResult::NotDir) - } else { - // FIXME cap-std needs a nonblocking open option so that files reads and writes - // are nonblocking. Instead we set it after opening here: - let set_fd_flags = opened.new_set_fd_flags(FdFlags::NONBLOCK)?; - opened.set_fd_flags(set_fd_flags)?; - Ok(OpenResult::File(opened)) - } - }) + let d = self.table.get(&fd)?.dir()?; + let fd = d + .open_at( + path_flags.into(), + path, + oflags.into(), + flags.into(), + self.ctx.allow_blocking_current_thread, + ) .await?; - - match opened { - OpenResult::Dir(dir) => Ok(table.push(Descriptor::Dir(Dir::new( - dir, - d.perms, - d.file_perms, - open_mode, - allow_blocking_current_thread, - )))?), - - OpenResult::File(file) => Ok(table.push(Descriptor::File(File::new( - file, - d.file_perms, - open_mode, - allow_blocking_current_thread, - )))?), - - OpenResult::NotDir => Err(ErrorCode::NotDirectory.into()), - } + let fd = self.table.push(fd)?; + Ok(fd) } fn drop(&mut self, fd: Resource) -> anyhow::Result<()> { - let table = self.table(); - // The Drop will close the file/dir, but if the close syscall // blocks the thread, I will face god and walk backwards into hell. // tokio::fs::File just uses std::fs::File's Drop impl to close, so // it doesn't appear anyone else has found this to be a problem. // (Not that they could solve it without async drop...) - table.delete(fd)?; + self.table.delete(fd)?; Ok(()) } @@ -646,16 +319,9 @@ where fd: Resource, path: String, ) -> FsResult { - let table = self.table(); - let d = table.get(&fd)?.dir()?; - if !d.perms.contains(DirPerms::READ) { - return Err(ErrorCode::NotPermitted.into()); - } - let link = d.run_blocking(move |d| d.read_link(&path)).await?; - Ok(link - .into_os_string() - .into_string() - .map_err(|_| ErrorCode::IllegalByteSequence)?) + let d = self.table.get(&fd)?.dir()?; + let path = d.readlink_at(path).await?; + Ok(path) } async fn remove_directory_at( @@ -663,12 +329,9 @@ where fd: Resource, path: String, ) -> FsResult<()> { - let table = self.table(); - let d = table.get(&fd)?.dir()?; - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted.into()); - } - Ok(d.run_blocking(move |d| d.remove_dir(&path)).await?) + let d = self.table.get(&fd)?.dir()?; + d.remove_directory_at(path).await?; + Ok(()) } async fn rename_at( @@ -678,19 +341,10 @@ where new_fd: Resource, new_path: String, ) -> FsResult<()> { - let table = self.table(); - let old_dir = table.get(&fd)?.dir()?; - if !old_dir.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted.into()); - } - let new_dir = table.get(&new_fd)?.dir()?; - if !new_dir.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted.into()); - } - let new_dir_handle = std::sync::Arc::clone(&new_dir.dir); - Ok(old_dir - .run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path)) - .await?) + let old_dir = self.table.get(&fd)?.dir()?; + let new_dir = self.table.get(&new_fd)?.dir()?; + old_dir.rename_at(old_path, new_dir, new_path).await?; + Ok(()) } async fn symlink_at( @@ -699,17 +353,9 @@ where src_path: String, dest_path: String, ) -> FsResult<()> { - // On windows, Dir.symlink is provided by DirExt - #[cfg(windows)] - use cap_fs_ext::DirExt; - - let table = self.table(); - let d = table.get(&fd)?.dir()?; - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted.into()); - } - Ok(d.run_blocking(move |d| d.symlink(&src_path, &dest_path)) - .await?) + let d = self.table.get(&fd)?.dir()?; + d.symlink_at(src_path, dest_path).await?; + Ok(()) } async fn unlink_file_at( @@ -717,15 +363,9 @@ where fd: Resource, path: String, ) -> FsResult<()> { - use cap_fs_ext::DirExt; - - let table = self.table(); - let d = table.get(&fd)?.dir()?; - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted.into()); - } - Ok(d.run_blocking(move |d| d.remove_file_or_symlink(&path)) - .await?) + let d = self.table.get(&fd)?.dir()?; + d.unlink_file_at(path).await?; + Ok(()) } fn read_via_stream( @@ -734,7 +374,7 @@ where offset: types::Filesize, ) -> FsResult> { // Trap if fd lookup fails: - let f = self.table().get(&fd)?.file()?; + let f = self.table.get(&fd)?.file()?; if !f.perms.contains(FilePerms::READ) { Err(types::ErrorCode::BadDescriptor)?; @@ -744,7 +384,7 @@ where let reader: DynInputStream = Box::new(FileInputStream::new(f, offset)); // Insert the stream view into the table. Trap if the table is full. - let index = self.table().push(reader)?; + let index = self.table.push(reader)?; Ok(index) } @@ -755,7 +395,7 @@ where offset: types::Filesize, ) -> FsResult> { // Trap if fd lookup fails: - let f = self.table().get(&fd)?.file()?; + let f = self.table.get(&fd)?.file()?; if !f.perms.contains(FilePerms::WRITE) { Err(types::ErrorCode::BadDescriptor)?; @@ -766,7 +406,7 @@ where let writer: DynOutputStream = Box::new(writer); // Insert the stream view into the table. Trap if the table is full. - let index = self.table().push(writer)?; + let index = self.table.push(writer)?; Ok(index) } @@ -776,7 +416,7 @@ where fd: Resource, ) -> FsResult> { // Trap if fd lookup fails: - let f = self.table().get(&fd)?.file()?; + let f = self.table.get(&fd)?.file()?; if !f.perms.contains(FilePerms::WRITE) { Err(types::ErrorCode::BadDescriptor)?; @@ -787,7 +427,7 @@ where let appender: DynOutputStream = Box::new(appender); // Insert the stream view into the table. Trap if the table is full. - let index = self.table().push(appender)?; + let index = self.table.push(appender)?; Ok(index) } @@ -797,35 +437,17 @@ where a: Resource, b: Resource, ) -> anyhow::Result { - use cap_fs_ext::MetadataExt; - let descriptor_a = self.table().get(&a)?; - let meta_a = get_descriptor_metadata(descriptor_a).await?; - let descriptor_b = self.table().get(&b)?; - let meta_b = get_descriptor_metadata(descriptor_b).await?; - if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() { - // MetadataHashValue does not derive eq, so use a pair of - // comparisons to check equality: - debug_assert_eq!( - calculate_metadata_hash(&meta_a).upper, - calculate_metadata_hash(&meta_b).upper - ); - debug_assert_eq!( - calculate_metadata_hash(&meta_a).lower, - calculate_metadata_hash(&meta_b).lower - ); - Ok(true) - } else { - // Hash collisions are possible, so don't assert the negative here - Ok(false) - } + let descriptor_a = self.table.get(&a)?; + let descriptor_b = self.table.get(&b)?; + descriptor_a.is_same_object(descriptor_b).await } async fn metadata_hash( &mut self, fd: Resource, ) -> FsResult { - let descriptor_a = self.table().get(&fd)?; - let meta = get_descriptor_metadata(descriptor_a).await?; - Ok(calculate_metadata_hash(&meta)) + let fd = self.table.get(&fd)?; + let meta = fd.metadata_hash().await?; + Ok(meta.into()) } async fn metadata_hash_at( &mut self, @@ -833,76 +455,160 @@ where path_flags: types::PathFlags, path: String, ) -> FsResult { - let table = self.table(); - let d = table.get(&fd)?.dir()?; - // No permissions check on metadata: if dir opened, allowed to stat it - let meta = d - .run_blocking(move |d| { - if symlink_follow(path_flags) { - d.metadata(path) - } else { - d.symlink_metadata(path) - } - }) - .await?; - Ok(calculate_metadata_hash(&meta)) + let d = self.table.get(&fd)?.dir()?; + let meta = d.metadata_hash_at(path_flags.into(), path).await?; + Ok(meta.into()) } } -impl HostDirectoryEntryStream for WasiImpl -where - T: WasiView, -{ +impl HostDirectoryEntryStream for WasiFilesystemCtxView<'_> { async fn read_directory_entry( &mut self, stream: Resource, ) -> FsResult> { - let table = self.table(); - let readdir = table.get(&stream)?; + let readdir = self.table.get(&stream)?; readdir.next() } fn drop(&mut self, stream: Resource) -> anyhow::Result<()> { - self.table().delete(stream)?; + self.table.delete(stream)?; Ok(()) } } -async fn get_descriptor_metadata(fd: &types::Descriptor) -> FsResult { - match fd { - Descriptor::File(f) => { - // No permissions check on metadata: if opened, allowed to stat it - Ok(f.run_blocking(|f| f.metadata()).await?) +impl From for system_interface::fs::Advice { + fn from(advice: types::Advice) -> Self { + match advice { + types::Advice::Normal => Self::Normal, + types::Advice::Sequential => Self::Sequential, + types::Advice::Random => Self::Random, + types::Advice::WillNeed => Self::WillNeed, + types::Advice::DontNeed => Self::DontNeed, + types::Advice::NoReuse => Self::NoReuse, + } + } +} + +impl From for crate::filesystem::OpenFlags { + fn from(flags: types::OpenFlags) -> Self { + let mut out = Self::empty(); + if flags.contains(types::OpenFlags::CREATE) { + out |= Self::CREATE; + } + if flags.contains(types::OpenFlags::DIRECTORY) { + out |= Self::DIRECTORY; + } + if flags.contains(types::OpenFlags::EXCLUSIVE) { + out |= Self::EXCLUSIVE; + } + if flags.contains(types::OpenFlags::TRUNCATE) { + out |= Self::TRUNCATE; + } + out + } +} + +impl From for crate::filesystem::PathFlags { + fn from(flags: types::PathFlags) -> Self { + let mut out = Self::empty(); + if flags.contains(types::PathFlags::SYMLINK_FOLLOW) { + out |= Self::SYMLINK_FOLLOW; + } + out + } +} + +impl From for types::DescriptorFlags { + fn from(flags: crate::filesystem::DescriptorFlags) -> Self { + let mut out = Self::empty(); + if flags.contains(crate::filesystem::DescriptorFlags::READ) { + out |= Self::READ; + } + if flags.contains(crate::filesystem::DescriptorFlags::WRITE) { + out |= Self::WRITE; + } + if flags.contains(crate::filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) { + out |= Self::FILE_INTEGRITY_SYNC; } - Descriptor::Dir(d) => { - // No permissions check on metadata: if opened, allowed to stat it - Ok(d.run_blocking(|d| d.dir_metadata()).await?) + if flags.contains(crate::filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) { + out |= Self::DATA_INTEGRITY_SYNC; } + if flags.contains(crate::filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) { + out |= Self::REQUESTED_WRITE_SYNC; + } + if flags.contains(crate::filesystem::DescriptorFlags::MUTATE_DIRECTORY) { + out |= Self::MUTATE_DIRECTORY; + } + out } } -fn calculate_metadata_hash(meta: &cap_std::fs::Metadata) -> types::MetadataHashValue { - use cap_fs_ext::MetadataExt; - // Without incurring any deps, std provides us with a 64 bit hash - // function: - use std::hash::Hasher; - // Note that this means that the metadata hash (which becomes a preview1 ino) may - // change when a different rustc release is used to build this host implementation: - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - hasher.write_u64(meta.dev()); - hasher.write_u64(meta.ino()); - let lower = hasher.finish(); - // MetadataHashValue has a pair of 64-bit members for representing a - // single 128-bit number. However, we only have 64 bits of entropy. To - // synthesize the upper 64 bits, lets xor the lower half with an arbitrary - // constant, in this case the 64 bit integer corresponding to the IEEE - // double representation of (a number as close as possible to) pi. - // This seems better than just repeating the same bits in the upper and - // lower parts outright, which could make folks wonder if the struct was - // mangled in the ABI, or worse yet, lead to consumers of this interface - // expecting them to be equal. - let upper = lower ^ 4614256656552045848u64; - types::MetadataHashValue { lower, upper } +impl From for crate::filesystem::DescriptorFlags { + fn from(flags: types::DescriptorFlags) -> Self { + let mut out = Self::empty(); + if flags.contains(types::DescriptorFlags::READ) { + out |= Self::READ; + } + if flags.contains(types::DescriptorFlags::WRITE) { + out |= Self::WRITE; + } + if flags.contains(types::DescriptorFlags::FILE_INTEGRITY_SYNC) { + out |= Self::FILE_INTEGRITY_SYNC; + } + if flags.contains(types::DescriptorFlags::DATA_INTEGRITY_SYNC) { + out |= Self::DATA_INTEGRITY_SYNC; + } + if flags.contains(types::DescriptorFlags::REQUESTED_WRITE_SYNC) { + out |= Self::REQUESTED_WRITE_SYNC; + } + if flags.contains(types::DescriptorFlags::MUTATE_DIRECTORY) { + out |= Self::MUTATE_DIRECTORY; + } + out + } +} + +impl From for types::MetadataHashValue { + fn from( + crate::filesystem::MetadataHashValue { lower, upper }: crate::filesystem::MetadataHashValue, + ) -> Self { + Self { lower, upper } + } +} + +impl From for types::DescriptorStat { + fn from( + crate::filesystem::DescriptorStat { + type_, + link_count, + size, + data_access_timestamp, + data_modification_timestamp, + status_change_timestamp, + }: crate::filesystem::DescriptorStat, + ) -> Self { + Self { + type_: type_.into(), + link_count, + size, + data_access_timestamp: data_access_timestamp.map(Into::into), + data_modification_timestamp: data_modification_timestamp.map(Into::into), + status_change_timestamp: status_change_timestamp.map(Into::into), + } + } +} + +impl From for types::DescriptorType { + fn from(ty: crate::filesystem::DescriptorType) -> Self { + match ty { + crate::filesystem::DescriptorType::Unknown => Self::Unknown, + crate::filesystem::DescriptorType::BlockDevice => Self::BlockDevice, + crate::filesystem::DescriptorType::CharacterDevice => Self::CharacterDevice, + crate::filesystem::DescriptorType::Directory => Self::Directory, + crate::filesystem::DescriptorType::SymbolicLink => Self::SymbolicLink, + crate::filesystem::DescriptorType::RegularFile => Self::RegularFile, + } + } } #[cfg(unix)] @@ -1032,44 +738,26 @@ fn descriptortype_from(ft: cap_std::fs::FileType) -> types::DescriptorType { } } -fn systemtimespec_from(t: types::NewTimestamp) -> FsResult> { - use fs_set_times::SystemTimeSpec; - use types::NewTimestamp; - match t { - NewTimestamp::NoChange => Ok(None), - NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)), - NewTimestamp::Timestamp(st) => Ok(Some(SystemTimeSpec::Absolute(systemtime_from(st)?))), - } -} - -fn systemtime_from(t: wall_clock::Datetime) -> FsResult { - use std::time::{Duration, SystemTime}; - SystemTime::UNIX_EPOCH - .checked_add(Duration::new(t.seconds, t.nanoseconds)) - .ok_or_else(|| ErrorCode::Overflow.into()) -} - -fn datetime_from(t: std::time::SystemTime) -> wall_clock::Datetime { - // FIXME make this infallible or handle errors properly - wall_clock::Datetime::try_from(cap_std::time::SystemTime::from_std(t)).unwrap() +fn systemtime_from(t: wall_clock::Datetime) -> Result { + std::time::SystemTime::UNIX_EPOCH + .checked_add(core::time::Duration::new(t.seconds, t.nanoseconds)) + .ok_or(ErrorCode::Overflow) } -fn descriptorstat_from(meta: cap_std::fs::Metadata) -> types::DescriptorStat { - use cap_fs_ext::MetadataExt; - types::DescriptorStat { - type_: descriptortype_from(meta.file_type()), - link_count: meta.nlink(), - size: meta.len(), - data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(), - data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(), - status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(), +fn systemtimespec_from( + t: types::NewTimestamp, +) -> Result, ErrorCode> { + use fs_set_times::SystemTimeSpec; + match t { + types::NewTimestamp::NoChange => Ok(None), + types::NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)), + types::NewTimestamp::Timestamp(st) => { + let st = systemtime_from(st)?; + Ok(Some(SystemTimeSpec::Absolute(st))) + } } } -fn symlink_follow(path_flags: types::PathFlags) -> bool { - path_flags.contains(types::PathFlags::SYMLINK_FOLLOW) -} - #[cfg(test)] mod test { use super::*; diff --git a/crates/wasi/src/p2/host/filesystem/sync.rs b/crates/wasi/src/p2/host/filesystem/sync.rs index 4294f43f0d..f20a986bf9 100644 --- a/crates/wasi/src/p2/host/filesystem/sync.rs +++ b/crates/wasi/src/p2/host/filesystem/sync.rs @@ -1,14 +1,12 @@ +use crate::filesystem::WasiFilesystemCtxView; use crate::p2::bindings::filesystem::types as async_filesystem; use crate::p2::bindings::sync::filesystem::types as sync_filesystem; use crate::p2::bindings::sync::io::streams; -use crate::p2::{FsError, FsResult, WasiImpl, WasiView}; +use crate::p2::{FsError, FsResult}; use crate::runtime::in_tokio; use wasmtime::component::Resource; -impl sync_filesystem::Host for WasiImpl -where - T: WasiView, -{ +impl sync_filesystem::Host for WasiFilesystemCtxView<'_> { fn convert_error_code(&mut self, err: FsError) -> anyhow::Result { Ok(async_filesystem::Host::convert_error_code(self, err)?.into()) } @@ -21,10 +19,7 @@ where } } -impl sync_filesystem::HostDescriptor for WasiImpl -where - T: WasiView, -{ +impl sync_filesystem::HostDescriptor for WasiFilesystemCtxView<'_> { fn advise( &mut self, fd: Resource, @@ -308,10 +303,7 @@ where } } -impl sync_filesystem::HostDirectoryEntryStream for WasiImpl -where - T: WasiView, -{ +impl sync_filesystem::HostDirectoryEntryStream for WasiFilesystemCtxView<'_> { fn read_directory_entry( &mut self, stream: Resource, diff --git a/crates/wasi/src/p2/host/instance_network.rs b/crates/wasi/src/p2/host/instance_network.rs index 85c5a78018..9029181d0a 100644 --- a/crates/wasi/src/p2/host/instance_network.rs +++ b/crates/wasi/src/p2/host/instance_network.rs @@ -1,18 +1,15 @@ use crate::p2::bindings::sockets::instance_network; use crate::p2::network::Network; -use crate::p2::{IoView, WasiImpl, WasiView}; +use crate::sockets::WasiSocketsCtxView; use wasmtime::component::Resource; -impl instance_network::Host for WasiImpl -where - T: WasiView, -{ +impl instance_network::Host for WasiSocketsCtxView<'_> { fn instance_network(&mut self) -> Result, anyhow::Error> { let network = Network { - socket_addr_check: self.ctx().socket_addr_check.clone(), - allow_ip_name_lookup: self.ctx().allowed_network_uses.ip_name_lookup, + socket_addr_check: self.ctx.socket_addr_check.clone(), + allow_ip_name_lookup: self.ctx.allowed_network_uses.ip_name_lookup, }; - let network = self.table().push(network)?; + let network = self.table.push(network)?; Ok(network) } } diff --git a/crates/wasi/src/p2/host/io.rs b/crates/wasi/src/p2/host/io.rs index 2289801b4d..1baa67e01d 100644 --- a/crates/wasi/src/p2/host/io.rs +++ b/crates/wasi/src/p2/host/io.rs @@ -1,10 +1,10 @@ use crate::p2::{ - IoImpl, IoView, StreamError, StreamResult, + StreamError, StreamResult, bindings::sync::io::poll::Pollable, bindings::sync::io::streams::{self, InputStream, OutputStream}, }; use crate::runtime::in_tokio; -use wasmtime::component::Resource; +use wasmtime::component::{Resource, ResourceTable}; use wasmtime_wasi_io::bindings::wasi::io::streams::{ self as async_streams, Host as AsyncHost, HostInputStream as AsyncHostInputStream, HostOutputStream as AsyncHostOutputStream, @@ -19,19 +19,13 @@ impl From for streams::StreamError { } } -impl streams::Host for IoImpl -where - T: IoView, -{ +impl streams::Host for ResourceTable { fn convert_stream_error(&mut self, err: StreamError) -> anyhow::Result { Ok(AsyncHost::convert_stream_error(self, err)?.into()) } } -impl streams::HostOutputStream for IoImpl -where - T: IoView, -{ +impl streams::HostOutputStream for ResourceTable { fn drop(&mut self, stream: Resource) -> anyhow::Result<()> { in_tokio(async { AsyncHostOutputStream::drop(self, stream).await }) } @@ -104,10 +98,7 @@ where } } -impl streams::HostInputStream for IoImpl -where - T: IoView, -{ +impl streams::HostInputStream for ResourceTable { fn drop(&mut self, stream: Resource) -> anyhow::Result<()> { in_tokio(async { AsyncHostInputStream::drop(self, stream).await }) } diff --git a/crates/wasi/src/p2/host/network.rs b/crates/wasi/src/p2/host/network.rs index 30059aed83..e0fd31f477 100644 --- a/crates/wasi/src/p2/host/network.rs +++ b/crates/wasi/src/p2/host/network.rs @@ -1,24 +1,22 @@ +use crate::p2::SocketError; use crate::p2::bindings::sockets::network::{ self, ErrorCode, IpAddress, IpAddressFamily, IpSocketAddress, Ipv4SocketAddress, Ipv6SocketAddress, }; -use crate::p2::{IoView, SocketError, WasiImpl, WasiView}; +use crate::sockets::WasiSocketsCtxView; use crate::sockets::util::{from_ipv4_addr, from_ipv6_addr, to_ipv4_addr, to_ipv6_addr}; use anyhow::Error; use rustix::io::Errno; use std::io; use wasmtime::component::Resource; -impl network::Host for WasiImpl -where - T: WasiView, -{ +impl network::Host for WasiSocketsCtxView<'_> { fn convert_error_code(&mut self, error: SocketError) -> anyhow::Result { error.downcast() } fn network_error_code(&mut self, err: Resource) -> anyhow::Result> { - let err = self.table().get(&err)?; + let err = self.table.get(&err)?; if let Some(err) = err.downcast_ref::() { return Ok(Some(ErrorCode::from(err))); @@ -28,14 +26,9 @@ where } } -impl crate::p2::bindings::sockets::network::HostNetwork for WasiImpl -where - T: WasiView, -{ +impl crate::p2::bindings::sockets::network::HostNetwork for WasiSocketsCtxView<'_> { fn drop(&mut self, this: Resource) -> Result<(), anyhow::Error> { - let table = self.table(); - - table.delete(this)?; + self.table.delete(this)?; Ok(()) } diff --git a/crates/wasi/src/p2/host/tcp.rs b/crates/wasi/src/p2/host/tcp.rs index f5c723227c..f806db07de 100644 --- a/crates/wasi/src/p2/host/tcp.rs +++ b/crates/wasi/src/p2/host/tcp.rs @@ -1,33 +1,26 @@ use crate::p2::bindings::{ - sockets::network::{IpAddressFamily, IpSocketAddress, Network}, + sockets::network::{ErrorCode, IpAddressFamily, IpSocketAddress, Network}, sockets::tcp::{self, ShutdownType}, }; -use crate::p2::{SocketResult, WasiImpl, WasiView}; -use crate::sockets::{SocketAddrUse, SocketAddressFamily}; +use crate::p2::{Pollable, SocketResult}; +use crate::sockets::{SocketAddrUse, TcpSocket, WasiSocketsCtxView}; use std::net::SocketAddr; -use std::time::Duration; use wasmtime::component::Resource; use wasmtime_wasi_io::{ - IoView, poll::DynPollable, streams::{DynInputStream, DynOutputStream}, }; -impl tcp::Host for WasiImpl where T: WasiView {} +impl tcp::Host for WasiSocketsCtxView<'_> {} -impl crate::p2::host::tcp::tcp::HostTcpSocket for WasiImpl -where - T: WasiView, -{ +impl crate::p2::host::tcp::tcp::HostTcpSocket for WasiSocketsCtxView<'_> { async fn start_bind( &mut self, - this: Resource, + this: Resource, network: Resource, local_address: IpSocketAddress, ) -> SocketResult<()> { - self.ctx().allowed_network_uses.check_allowed_tcp()?; - let table = self.table(); - let network = table.get(&network)?; + let network = self.table.get(&network)?; let local_address: SocketAddr = local_address.into(); // Ensure that we're allowed to connect to this address. @@ -36,27 +29,24 @@ where .await?; // Bind to the address. - table.get_mut(&this)?.start_bind(local_address)?; + self.table.get_mut(&this)?.start_bind(local_address)?; Ok(()) } - fn finish_bind(&mut self, this: Resource) -> SocketResult<()> { - let table = self.table(); - let socket = table.get_mut(&this)?; - - socket.finish_bind() + fn finish_bind(&mut self, this: Resource) -> SocketResult<()> { + let socket = self.table.get_mut(&this)?; + socket.finish_bind()?; + Ok(()) } async fn start_connect( &mut self, - this: Resource, + this: Resource, network: Resource, remote_address: IpSocketAddress, ) -> SocketResult<()> { - self.ctx().allowed_network_uses.check_allowed_tcp()?; - let table = self.table(); - let network = table.get(&network)?; + let network = self.table.get(&network)?; let remote_address: SocketAddr = remote_address.into(); // Ensure that we're allowed to connect to this address. @@ -65,259 +55,233 @@ where .await?; // Start connection - table.get_mut(&this)?.start_connect(remote_address)?; + let socket = self.table.get_mut(&this)?; + let future = socket + .start_connect(&remote_address)? + .connect(remote_address); + socket.set_pending_connect(future)?; Ok(()) } fn finish_connect( &mut self, - this: Resource, + this: Resource, ) -> SocketResult<(Resource, Resource)> { - let table = self.table(); - let socket = table.get_mut(&this)?; - - let (input, output) = socket.finish_connect()?; - - let input_stream = self.table().push_child(input, &this)?; - let output_stream = self.table().push_child(output, &this)?; - - Ok((input_stream, output_stream)) + let socket = self.table.get_mut(&this)?; + + let result = socket + .take_pending_connect()? + .ok_or(ErrorCode::WouldBlock)?; + socket.finish_connect(result)?; + let (input, output) = socket.p2_streams()?; + let input = self.table.push_child(input, &this)?; + let output = self.table.push_child(output, &this)?; + Ok((input, output)) } - fn start_listen(&mut self, this: Resource) -> SocketResult<()> { - self.ctx().allowed_network_uses.check_allowed_tcp()?; - let table = self.table(); - let socket = table.get_mut(&this)?; + fn start_listen(&mut self, this: Resource) -> SocketResult<()> { + let socket = self.table.get_mut(&this)?; - socket.start_listen() + socket.start_listen()?; + Ok(()) } - fn finish_listen(&mut self, this: Resource) -> SocketResult<()> { - let table = self.table(); - let socket = table.get_mut(&this)?; - socket.finish_listen() + fn finish_listen(&mut self, this: Resource) -> SocketResult<()> { + let socket = self.table.get_mut(&this)?; + socket.finish_listen()?; + Ok(()) } fn accept( &mut self, - this: Resource, + this: Resource, ) -> SocketResult<( - Resource, + Resource, Resource, Resource, )> { - self.ctx().allowed_network_uses.check_allowed_tcp()?; - let table = self.table(); - let socket = table.get_mut(&this)?; + let socket = self.table.get_mut(&this)?; - let (tcp_socket, input, output) = socket.accept()?; + let mut tcp_socket = socket.accept()?.ok_or(ErrorCode::WouldBlock)?; + let (input, output) = tcp_socket.p2_streams()?; - let tcp_socket = self.table().push(tcp_socket)?; - let input_stream = self.table().push_child(input, &tcp_socket)?; - let output_stream = self.table().push_child(output, &tcp_socket)?; + let tcp_socket = self.table.push(tcp_socket)?; + let input_stream = self.table.push_child(input, &tcp_socket)?; + let output_stream = self.table.push_child(output, &tcp_socket)?; Ok((tcp_socket, input_stream, output_stream)) } - fn local_address(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - - socket.local_address().map(Into::into) + fn local_address(&mut self, this: Resource) -> SocketResult { + let socket = self.table.get(&this)?; + Ok(socket.local_address()?.into()) } - fn remote_address(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - - socket.remote_address().map(Into::into) + fn remote_address(&mut self, this: Resource) -> SocketResult { + let socket = self.table.get(&this)?; + Ok(socket.remote_address()?.into()) } - fn is_listening(&mut self, this: Resource) -> Result { - let table = self.table(); - let socket = table.get(&this)?; + fn is_listening(&mut self, this: Resource) -> Result { + let socket = self.table.get(&this)?; Ok(socket.is_listening()) } fn address_family( &mut self, - this: Resource, + this: Resource, ) -> Result { - let table = self.table(); - let socket = table.get(&this)?; - - match socket.address_family() { - SocketAddressFamily::Ipv4 => Ok(IpAddressFamily::Ipv4), - SocketAddressFamily::Ipv6 => Ok(IpAddressFamily::Ipv6), - } + let socket = self.table.get(&this)?; + Ok(socket.address_family().into()) } fn set_listen_backlog_size( &mut self, - this: Resource, + this: Resource, value: u64, ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get_mut(&this)?; - - // Silently clamp backlog size. This is OK for us to do, because operating systems do this too. - let value = value.try_into().unwrap_or(u32::MAX); - - socket.set_listen_backlog_size(value) + let socket = self.table.get_mut(&this)?; + socket.set_listen_backlog_size(value)?; + Ok(()) } - fn keep_alive_enabled(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - socket.keep_alive_enabled() + fn keep_alive_enabled(&mut self, this: Resource) -> SocketResult { + let socket = self.table.get(&this)?; + Ok(socket.keep_alive_enabled()?) } fn set_keep_alive_enabled( &mut self, - this: Resource, + this: Resource, value: bool, ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get(&this)?; - socket.set_keep_alive_enabled(value) + let socket = self.table.get(&this)?; + socket.set_keep_alive_enabled(value)?; + Ok(()) } - fn keep_alive_idle_time(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - Ok(socket.keep_alive_idle_time()?.as_nanos() as u64) + fn keep_alive_idle_time(&mut self, this: Resource) -> SocketResult { + let socket = self.table.get(&this)?; + Ok(socket.keep_alive_idle_time()?) } fn set_keep_alive_idle_time( &mut self, - this: Resource, + this: Resource, value: u64, ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get_mut(&this)?; - socket.set_keep_alive_idle_time(value) + let socket = self.table.get_mut(&this)?; + socket.set_keep_alive_idle_time(value)?; + Ok(()) } - fn keep_alive_interval(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - Ok(socket.keep_alive_interval()?.as_nanos() as u64) + fn keep_alive_interval(&mut self, this: Resource) -> SocketResult { + let socket = self.table.get(&this)?; + Ok(socket.keep_alive_interval()?) } fn set_keep_alive_interval( &mut self, - this: Resource, + this: Resource, value: u64, ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get(&this)?; - socket.set_keep_alive_interval(Duration::from_nanos(value)) + let socket = self.table.get(&this)?; + socket.set_keep_alive_interval(value)?; + Ok(()) } - fn keep_alive_count(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - socket.keep_alive_count() + fn keep_alive_count(&mut self, this: Resource) -> SocketResult { + let socket = self.table.get(&this)?; + Ok(socket.keep_alive_count()?) } - fn set_keep_alive_count( - &mut self, - this: Resource, - value: u32, - ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get(&this)?; - socket.set_keep_alive_count(value) + fn set_keep_alive_count(&mut self, this: Resource, value: u32) -> SocketResult<()> { + let socket = self.table.get(&this)?; + socket.set_keep_alive_count(value)?; + Ok(()) } - fn hop_limit(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - socket.hop_limit() + fn hop_limit(&mut self, this: Resource) -> SocketResult { + let socket = self.table.get(&this)?; + Ok(socket.hop_limit()?) } - fn set_hop_limit(&mut self, this: Resource, value: u8) -> SocketResult<()> { - let table = self.table(); - let socket = table.get_mut(&this)?; - socket.set_hop_limit(value) + fn set_hop_limit(&mut self, this: Resource, value: u8) -> SocketResult<()> { + let socket = self.table.get_mut(&this)?; + socket.set_hop_limit(value)?; + Ok(()) } - fn receive_buffer_size(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - + fn receive_buffer_size(&mut self, this: Resource) -> SocketResult { + let socket = self.table.get(&this)?; Ok(socket.receive_buffer_size()?) } fn set_receive_buffer_size( &mut self, - this: Resource, + this: Resource, value: u64, ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get_mut(&this)?; - socket.set_receive_buffer_size(value) + let socket = self.table.get_mut(&this)?; + socket.set_receive_buffer_size(value)?; + Ok(()) } - fn send_buffer_size(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - + fn send_buffer_size(&mut self, this: Resource) -> SocketResult { + let socket = self.table.get(&this)?; Ok(socket.send_buffer_size()?) } - fn set_send_buffer_size( - &mut self, - this: Resource, - value: u64, - ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get_mut(&this)?; - socket.set_send_buffer_size(value) + fn set_send_buffer_size(&mut self, this: Resource, value: u64) -> SocketResult<()> { + let socket = self.table.get_mut(&this)?; + socket.set_send_buffer_size(value)?; + Ok(()) } - fn subscribe( - &mut self, - this: Resource, - ) -> anyhow::Result> { - wasmtime_wasi_io::poll::subscribe(self.table(), this) + fn subscribe(&mut self, this: Resource) -> anyhow::Result> { + wasmtime_wasi_io::poll::subscribe(self.table, this) } fn shutdown( &mut self, - this: Resource, + this: Resource, shutdown_type: ShutdownType, ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get(&this)?; + let socket = self.table.get(&this)?; let how = match shutdown_type { ShutdownType::Receive => std::net::Shutdown::Read, ShutdownType::Send => std::net::Shutdown::Write, ShutdownType::Both => std::net::Shutdown::Both, }; - socket.shutdown(how) - } - fn drop(&mut self, this: Resource) -> Result<(), anyhow::Error> { - let table = self.table(); + let state = socket.p2_streaming_state()?; + state.shutdown(how)?; + Ok(()) + } + fn drop(&mut self, this: Resource) -> Result<(), anyhow::Error> { // As in the filesystem implementation, we assume closing a socket // doesn't block. - let dropped = table.delete(this)?; + let dropped = self.table.delete(this)?; drop(dropped); Ok(()) } } -pub mod sync { - use wasmtime::component::Resource; +#[async_trait::async_trait] +impl Pollable for TcpSocket { + async fn ready(&mut self) { + ::ready(self).await; + } +} +pub mod sync { use crate::p2::{ - SocketError, WasiImpl, WasiView, + SocketError, bindings::{ sockets::{ network::Network, @@ -330,13 +294,12 @@ pub mod sync { }, }; use crate::runtime::in_tokio; + use crate::sockets::WasiSocketsCtxView; + use wasmtime::component::Resource; - impl tcp::Host for WasiImpl where T: WasiView {} + impl tcp::Host for WasiSocketsCtxView<'_> {} - impl HostTcpSocket for WasiImpl - where - T: WasiView, - { + impl HostTcpSocket for WasiSocketsCtxView<'_> { fn start_bind( &mut self, self_: Resource, diff --git a/crates/wasi/src/p2/host/tcp_create_socket.rs b/crates/wasi/src/p2/host/tcp_create_socket.rs index 09dd5c07ea..10fcc75edc 100644 --- a/crates/wasi/src/p2/host/tcp_create_socket.rs +++ b/crates/wasi/src/p2/host/tcp_create_socket.rs @@ -1,18 +1,24 @@ +use crate::p2::SocketResult; use crate::p2::bindings::{sockets::network::IpAddressFamily, sockets::tcp_create_socket}; -use crate::p2::tcp::TcpSocket; -use crate::p2::{IoView, SocketResult, WasiImpl, WasiView}; +use crate::sockets::{SocketAddressFamily, TcpSocket, WasiSocketsCtxView}; use wasmtime::component::Resource; -impl tcp_create_socket::Host for WasiImpl -where - T: WasiView, -{ +impl tcp_create_socket::Host for WasiSocketsCtxView<'_> { fn create_tcp_socket( &mut self, address_family: IpAddressFamily, ) -> SocketResult> { - let socket = TcpSocket::new(address_family.into())?; - let socket = self.table().push(socket)?; + let socket = TcpSocket::new(self.ctx, address_family.into())?; + let socket = self.table.push(socket)?; Ok(socket) } } + +impl From for SocketAddressFamily { + fn from(family: IpAddressFamily) -> SocketAddressFamily { + match family { + IpAddressFamily::Ipv4 => Self::Ipv4, + IpAddressFamily::Ipv6 => Self::Ipv6, + } + } +} diff --git a/crates/wasi/src/p2/host/udp.rs b/crates/wasi/src/p2/host/udp.rs index 553c422e18..23a3b53c1e 100644 --- a/crates/wasi/src/p2/host/udp.rs +++ b/crates/wasi/src/p2/host/udp.rs @@ -1,81 +1,41 @@ use crate::p2::bindings::sockets::network::{ErrorCode, IpAddressFamily, IpSocketAddress, Network}; use crate::p2::bindings::sockets::udp; -use crate::p2::udp::{IncomingDatagramStream, OutgoingDatagramStream, SendState, UdpState}; -use crate::p2::{IoView, Pollable, SocketError, SocketResult, WasiImpl, WasiView}; -use crate::sockets::util::{ - get_ip_ttl, get_ipv6_unicast_hops, is_valid_address_family, is_valid_remote_address, - receive_buffer_size, send_buffer_size, set_receive_buffer_size, set_send_buffer_size, - set_unicast_hop_limit, udp_bind, udp_disconnect, +use crate::p2::udp::{IncomingDatagramStream, OutgoingDatagramStream, SendState}; +use crate::p2::{Pollable, SocketError, SocketResult}; +use crate::sockets::util::{is_valid_address_family, is_valid_remote_address}; +use crate::sockets::{ + MAX_UDP_DATAGRAM_SIZE, SocketAddrUse, SocketAddressFamily, UdpSocket, WasiSocketsCtxView, }; -use crate::sockets::{MAX_UDP_DATAGRAM_SIZE, SocketAddrUse, SocketAddressFamily}; use anyhow::anyhow; use async_trait::async_trait; -use io_lifetimes::AsSocketlike; -use rustix::io::Errno; use std::net::SocketAddr; use tokio::io::Interest; use wasmtime::component::Resource; use wasmtime_wasi_io::poll::DynPollable; -impl udp::Host for WasiImpl where T: WasiView {} +impl udp::Host for WasiSocketsCtxView<'_> {} -impl udp::HostUdpSocket for WasiImpl -where - T: WasiView, -{ +impl udp::HostUdpSocket for WasiSocketsCtxView<'_> { async fn start_bind( &mut self, this: Resource, network: Resource, local_address: IpSocketAddress, ) -> SocketResult<()> { - self.ctx().allowed_network_uses.check_allowed_udp()?; - let table = self.table(); + let local_address = SocketAddr::from(local_address); + let check = self.table.get(&network)?.socket_addr_check.clone(); + check.check(local_address, SocketAddrUse::UdpBind).await?; - match table.get(&this)?.udp_state { - UdpState::Default => {} - UdpState::BindStarted => return Err(ErrorCode::ConcurrencyConflict.into()), - UdpState::Bound | UdpState::Connected => return Err(ErrorCode::InvalidState.into()), - } - - // Set the socket addr check on the socket so later functions have access to it through the socket handle - let check = table.get(&network)?.socket_addr_check.clone(); - table - .get_mut(&this)? - .socket_addr_check - .replace(check.clone()); - - let socket = table.get(&this)?; - let local_address: SocketAddr = local_address.into(); - - if !is_valid_address_family(local_address.ip(), socket.family) { - return Err(ErrorCode::InvalidArgument.into()); - } - - { - check.check(local_address, SocketAddrUse::UdpBind).await?; - - // Perform the OS bind call. - udp_bind(socket.udp_socket(), local_address)?; - } - - let socket = table.get_mut(&this)?; - socket.udp_state = UdpState::BindStarted; + let socket = self.table.get_mut(&this)?; + socket.bind(local_address)?; + socket.set_socket_addr_check(Some(check)); Ok(()) } fn finish_bind(&mut self, this: Resource) -> SocketResult<()> { - let table = self.table(); - let socket = table.get_mut(&this)?; - - match socket.udp_state { - UdpState::BindStarted => { - socket.udp_state = UdpState::Bound; - Ok(()) - } - _ => Err(ErrorCode::NotInProgress.into()), - } + self.table.get_mut(&this)?.finish_bind()?; + Ok(()) } async fn stream( @@ -86,9 +46,8 @@ where Resource, Resource, )> { - let table = self.table(); - - let has_active_streams = table + let has_active_streams = self + .table .iter_children(&this)? .any(|c| c.is::() || c.is::()); @@ -96,12 +55,11 @@ where return Err(SocketError::trap(anyhow!("UDP streams not dropped yet"))); } - let socket = table.get_mut(&this)?; + let socket = self.table.get_mut(&this)?; let remote_address = remote_address.map(SocketAddr::from); - match socket.udp_state { - UdpState::Bound | UdpState::Connected => {} - _ => return Err(ErrorCode::InvalidState.into()), + if !socket.is_bound() { + return Err(ErrorCode::InvalidState.into()); } // We disconnect & (re)connect in two distinct steps for two reasons: @@ -111,112 +69,58 @@ where // if there isn't a disconnect in between. // Step #1: Disconnect - if let UdpState::Connected = socket.udp_state { - udp_disconnect(socket.udp_socket())?; - socket.udp_state = UdpState::Bound; + if socket.is_connected() { + socket.disconnect()?; } // Step #2: (Re)connect if let Some(connect_addr) = remote_address { - let Some(check) = socket.socket_addr_check.as_ref() else { + let Some(check) = socket.socket_addr_check() else { return Err(ErrorCode::InvalidState.into()); }; - if !is_valid_remote_address(connect_addr) - || !is_valid_address_family(connect_addr.ip(), socket.family) - { - return Err(ErrorCode::InvalidArgument.into()); - } check.check(connect_addr, SocketAddrUse::UdpConnect).await?; - - rustix::net::connect(socket.udp_socket(), &connect_addr).map_err( - |error| match error { - Errno::AFNOSUPPORT => ErrorCode::InvalidArgument, // See `bind` implementation. - Errno::INPROGRESS => { - tracing::debug!( - "UDP connect returned EINPROGRESS, which should never happen" - ); - ErrorCode::Unknown - } - _ => ErrorCode::from(error), - }, - )?; - socket.udp_state = UdpState::Connected; + socket.connect(connect_addr)?; } let incoming_stream = IncomingDatagramStream { - inner: socket.inner.clone(), + inner: socket.socket().clone(), remote_address, }; let outgoing_stream = OutgoingDatagramStream { - inner: socket.inner.clone(), + inner: socket.socket().clone(), remote_address, - family: socket.family, + family: socket.address_family(), send_state: SendState::Idle, - socket_addr_check: socket.socket_addr_check.clone(), + socket_addr_check: socket.socket_addr_check().cloned(), }; Ok(( - self.table().push_child(incoming_stream, &this)?, - self.table().push_child(outgoing_stream, &this)?, + self.table.push_child(incoming_stream, &this)?, + self.table.push_child(outgoing_stream, &this)?, )) } fn local_address(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - - match socket.udp_state { - UdpState::Default => return Err(ErrorCode::InvalidState.into()), - UdpState::BindStarted => return Err(ErrorCode::ConcurrencyConflict.into()), - _ => {} - } - - let addr = socket - .udp_socket() - .as_socketlike_view::() - .local_addr()?; - Ok(addr.into()) + let socket = self.table.get(&this)?; + Ok(socket.local_address()?.into()) } fn remote_address(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - - match socket.udp_state { - UdpState::Connected => {} - _ => return Err(ErrorCode::InvalidState.into()), - } - - let addr = socket - .udp_socket() - .as_socketlike_view::() - .peer_addr()?; - Ok(addr.into()) + let socket = self.table.get(&this)?; + Ok(socket.remote_address()?.into()) } fn address_family( &mut self, this: Resource, ) -> Result { - let table = self.table(); - let socket = table.get(&this)?; - - match socket.family { - SocketAddressFamily::Ipv4 => Ok(IpAddressFamily::Ipv4), - SocketAddressFamily::Ipv6 => Ok(IpAddressFamily::Ipv6), - } + let socket = self.table.get(&this)?; + Ok(socket.address_family().into()) } fn unicast_hop_limit(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - - let ttl = match socket.family { - SocketAddressFamily::Ipv4 => get_ip_ttl(socket.udp_socket())?, - SocketAddressFamily::Ipv6 => get_ipv6_unicast_hops(socket.udp_socket())?, - }; - - Ok(ttl) + let socket = self.table.get(&this)?; + Ok(socket.unicast_hop_limit()?) } fn set_unicast_hop_limit( @@ -224,20 +128,14 @@ where this: Resource, value: u8, ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get(&this)?; - - set_unicast_hop_limit(socket.udp_socket(), socket.family, value)?; - + let socket = self.table.get(&this)?; + socket.set_unicast_hop_limit(value)?; Ok(()) } fn receive_buffer_size(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - - let value = receive_buffer_size(socket.udp_socket())?; - Ok(value) + let socket = self.table.get(&this)?; + Ok(socket.receive_buffer_size()?) } fn set_receive_buffer_size( @@ -245,56 +143,44 @@ where this: Resource, value: u64, ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get(&this)?; - - set_receive_buffer_size(socket.udp_socket(), value)?; + let socket = self.table.get(&this)?; + socket.set_receive_buffer_size(value)?; Ok(()) } fn send_buffer_size(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let socket = table.get(&this)?; - - let value = send_buffer_size(socket.udp_socket())?; - Ok(value) + let socket = self.table.get(&this)?; + Ok(socket.send_buffer_size()?) } - fn set_send_buffer_size( - &mut self, - this: Resource, - value: u64, - ) -> SocketResult<()> { - let table = self.table(); - let socket = table.get(&this)?; - - set_send_buffer_size(socket.udp_socket(), value)?; + fn set_send_buffer_size(&mut self, this: Resource, value: u64) -> SocketResult<()> { + let socket = self.table.get(&this)?; + socket.set_send_buffer_size(value)?; Ok(()) } - fn subscribe( - &mut self, - this: Resource, - ) -> anyhow::Result> { - wasmtime_wasi_io::poll::subscribe(self.table(), this) + fn subscribe(&mut self, this: Resource) -> anyhow::Result> { + wasmtime_wasi_io::poll::subscribe(self.table, this) } fn drop(&mut self, this: Resource) -> Result<(), anyhow::Error> { - let table = self.table(); - // As in the filesystem implementation, we assume closing a socket // doesn't block. - let dropped = table.delete(this)?; + let dropped = self.table.delete(this)?; drop(dropped); Ok(()) } } -impl udp::HostIncomingDatagramStream for WasiImpl -where - T: WasiView, -{ +#[async_trait] +impl Pollable for UdpSocket { + async fn ready(&mut self) { + // None of the socket-level operations block natively + } +} + +impl udp::HostIncomingDatagramStream for WasiSocketsCtxView<'_> { fn receive( &mut self, this: Resource, @@ -322,8 +208,7 @@ where })) } - let table = self.table(); - let stream = table.get(&this)?; + let stream = self.table.get(&this)?; let max_results: usize = max_results.try_into().unwrap_or(usize::MAX); if max_results == 0 { @@ -359,15 +244,13 @@ where &mut self, this: Resource, ) -> anyhow::Result> { - wasmtime_wasi_io::poll::subscribe(self.table(), this) + wasmtime_wasi_io::poll::subscribe(self.table, this) } fn drop(&mut self, this: Resource) -> Result<(), anyhow::Error> { - let table = self.table(); - // As in the filesystem implementation, we assume closing a socket // doesn't block. - let dropped = table.delete(this)?; + let dropped = self.table.delete(this)?; drop(dropped); Ok(()) @@ -385,13 +268,9 @@ impl Pollable for IncomingDatagramStream { } } -impl udp::HostOutgoingDatagramStream for WasiImpl -where - T: WasiView, -{ +impl udp::HostOutgoingDatagramStream for WasiSocketsCtxView<'_> { fn check_send(&mut self, this: Resource) -> SocketResult { - let table = self.table(); - let stream = table.get_mut(&this)?; + let stream = self.table.get_mut(&this)?; let permit = match stream.send_state { SendState::Idle => { @@ -451,8 +330,7 @@ where Ok(()) } - let table = self.table(); - let stream = table.get_mut(&this)?; + let stream = self.table.get_mut(&this)?; match stream.send_state { SendState::Permitted(n) if n >= datagrams.len() => { @@ -500,15 +378,13 @@ where &mut self, this: Resource, ) -> anyhow::Result> { - wasmtime_wasi_io::poll::subscribe(self.table(), this) + wasmtime_wasi_io::poll::subscribe(self.table, this) } fn drop(&mut self, this: Resource) -> Result<(), anyhow::Error> { - let table = self.table(); - // As in the filesystem implementation, we assume closing a socket // doesn't block. - let dropped = table.delete(this)?; + let dropped = self.table.delete(this)?; drop(dropped); Ok(()) @@ -532,11 +408,20 @@ impl Pollable for OutgoingDatagramStream { } } +impl From for IpAddressFamily { + fn from(family: SocketAddressFamily) -> IpAddressFamily { + match family { + SocketAddressFamily::Ipv4 => IpAddressFamily::Ipv4, + SocketAddressFamily::Ipv6 => IpAddressFamily::Ipv6, + } + } +} + pub mod sync { use wasmtime::component::Resource; use crate::p2::{ - SocketError, WasiImpl, WasiView, + SocketError, bindings::{ sockets::{ network::Network, @@ -556,13 +441,11 @@ pub mod sync { }, }; use crate::runtime::in_tokio; + use crate::sockets::WasiSocketsCtxView; - impl udp::Host for WasiImpl where T: WasiView {} + impl udp::Host for WasiSocketsCtxView<'_> {} - impl HostUdpSocket for WasiImpl - where - T: WasiView, - { + impl HostUdpSocket for WasiSocketsCtxView<'_> { fn start_bind( &mut self, self_: Resource, @@ -661,10 +544,7 @@ pub mod sync { } } - impl HostIncomingDatagramStream for WasiImpl - where - T: WasiView, - { + impl HostIncomingDatagramStream for WasiSocketsCtxView<'_> { fn receive( &mut self, self_: Resource, @@ -703,10 +583,7 @@ pub mod sync { } } - impl HostOutgoingDatagramStream for WasiImpl - where - T: WasiView, - { + impl HostOutgoingDatagramStream for WasiSocketsCtxView<'_> { fn check_send( &mut self, self_: Resource, diff --git a/crates/wasi/src/p2/host/udp_create_socket.rs b/crates/wasi/src/p2/host/udp_create_socket.rs index bcacee2545..ac05dc51d8 100644 --- a/crates/wasi/src/p2/host/udp_create_socket.rs +++ b/crates/wasi/src/p2/host/udp_create_socket.rs @@ -1,18 +1,16 @@ +use crate::p2::SocketResult; use crate::p2::bindings::{sockets::network::IpAddressFamily, sockets::udp_create_socket}; -use crate::p2::udp::UdpSocket; -use crate::p2::{IoView, SocketResult, WasiImpl, WasiView}; +use crate::sockets::UdpSocket; +use crate::sockets::WasiSocketsCtxView; use wasmtime::component::Resource; -impl udp_create_socket::Host for WasiImpl -where - T: WasiView, -{ +impl udp_create_socket::Host for WasiSocketsCtxView<'_> { fn create_udp_socket( &mut self, address_family: IpAddressFamily, ) -> SocketResult> { - let socket = UdpSocket::new(address_family.into())?; - let socket = self.table().push(socket)?; + let socket = UdpSocket::new(self.ctx, address_family.into())?; + let socket = self.table.push(socket)?; Ok(socket) } } diff --git a/crates/wasi/src/p2/ip_name_lookup.rs b/crates/wasi/src/p2/ip_name_lookup.rs index b0252fa7c2..b8cc63986d 100644 --- a/crates/wasi/src/p2/ip_name_lookup.rs +++ b/crates/wasi/src/p2/ip_name_lookup.rs @@ -1,7 +1,8 @@ +use crate::p2::SocketError; use crate::p2::bindings::sockets::ip_name_lookup::{Host, HostResolveAddressStream}; use crate::p2::bindings::sockets::network::{ErrorCode, IpAddress, Network}; -use crate::p2::{IoView, SocketError, WasiImpl, WasiView}; use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking}; +use crate::sockets::WasiSocketsCtxView; use anyhow::Result; use std::mem; use std::net::ToSocketAddrs; @@ -17,16 +18,13 @@ pub enum ResolveAddressStream { Done(Result, SocketError>), } -impl Host for WasiImpl -where - T: WasiView, -{ +impl Host for WasiSocketsCtxView<'_> { fn resolve_addresses( &mut self, network: Resource, name: String, ) -> Result, SocketError> { - let network = self.table().get(&network)?; + let network = self.table.get(&network)?; let host = parse_host(&name)?; @@ -35,20 +33,17 @@ where } let task = spawn_blocking(move || blocking_resolve(&host)); - let resource = self.table().push(ResolveAddressStream::Waiting(task))?; + let resource = self.table.push(ResolveAddressStream::Waiting(task))?; Ok(resource) } } -impl HostResolveAddressStream for WasiImpl -where - T: WasiView, -{ +impl HostResolveAddressStream for WasiSocketsCtxView<'_> { fn resolve_next_address( &mut self, resource: Resource, ) -> Result, SocketError> { - let stream: &mut ResolveAddressStream = self.table().get_mut(&resource)?; + let stream: &mut ResolveAddressStream = self.table.get_mut(&resource)?; loop { match stream { ResolveAddressStream::Waiting(future) => { @@ -72,11 +67,11 @@ where &mut self, resource: Resource, ) -> Result> { - subscribe(self.table(), resource) + subscribe(self.table, resource) } fn drop(&mut self, resource: Resource) -> Result<()> { - self.table().delete(resource)?; + self.table.delete(resource)?; Ok(()) } } diff --git a/crates/wasi/src/p2/mod.rs b/crates/wasi/src/p2/mod.rs index cfd602fa49..d5ae2ffcc1 100644 --- a/crates/wasi/src/p2/mod.rs +++ b/crates/wasi/src/p2/mod.rs @@ -36,10 +36,10 @@ //! * [`wasi:sockets/udp-create-socket`] //! * [`wasi:sockets/udp`] //! -//! All traits are implemented in terms of a [`WasiView`] trait which provides -//! access to [`WasiCtx`], which defines the configuration for WASI. -//! The [`WasiView`] trait imples [`IoView`], which provides access to a common -//! [`ResourceTable`], which owns all host-defined component model resources. +//! Most traits are implemented for [`WasiCtxView`] trait which provides +//! access to [`WasiCtx`] and [`ResourceTable`], which defines the configuration +//! for WASI and handle state. The [`WasiView`] trait is used to acquire and +//! construct a [`WasiCtxView`]. //! //! The [`wasmtime-wasi-io`] crate contains implementations of the //! following interfaces, and this module reuses those implementations: @@ -48,8 +48,7 @@ //! * [`wasi:io/poll`] //! * [`wasi:io/streams`] //! -//! These traits are implemented in terms of a [`IoView`] trait, which only -//! provides access to a common [`ResourceTable`]. All aspects of +//! These traits are implemented directly for [`ResourceTable`]. All aspects of //! `wasmtime-wasi-io` that are used by this module are re-exported. Unless you //! are implementing other host functionality that needs to interact with the //! WASI scheduler and don't want to use other functionality provided by @@ -68,52 +67,48 @@ //! //! This module's implementation of WASI is done in terms of an implementation of //! [`WasiView`]. This trait provides a "view" into WASI-related state that is -//! contained within a [`Store`](wasmtime::Store). [`WasiView`] implies the -//! [`IoView`] trait, which provides access to common [`ResourceTable`] which -//! owns all host-implemented component model resources. +//! contained within a [`Store`](wasmtime::Store). //! //! For all of the generated bindings in this module (Host traits), //! implementations are provided looking like: //! //! ``` -//! # use wasmtime_wasi::p2::WasiImpl; +//! # use wasmtime_wasi::WasiCtxView; //! # trait WasiView {} //! # mod bindings { pub mod wasi { pub trait Host {} } } -//! impl bindings::wasi::Host for WasiImpl { +//! impl bindings::wasi::Host for WasiCtxView<'_> { //! // ... //! } //! ``` //! -//! The [`add_to_linker_sync`] and [`add_to_linker_async`] function then require -//! that `T: WasiView` with [`Linker`](wasmtime::component::Linker). +//! where the [`WasiCtxView`] type comes from [`WasiView::ctx`] for the type +//! contained within the `Store`. The [`add_to_linker_sync`] and +//! [`add_to_linker_async`] function then require that `T: WasiView` with +//! [`Linker`](wasmtime::component::Linker). //! -//! To implement the [`WasiView`] and [`IoView`] trait you will first select a +//! To implement the [`WasiView`] trait you will first select a //! `T` to put in `Store` (typically, by defining your own struct). //! Somewhere within `T` you'll store: //! //! * [`ResourceTable`] - created through default constructors. //! * [`WasiCtx`] - created through [`WasiCtxBuilder`]. //! -//! You'll then write implementations of the [`IoView`] and [`WasiView`] -//! traits to access those items in your `T`. For example: +//! You'll then write an implementation of the [`WasiView`] +//! trait to access those items in your `T`. For example: //! ``` //! use wasmtime::component::ResourceTable; -//! use wasmtime_wasi::p2::{WasiCtx, IoView, WasiView}; +//! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; +//! //! struct MyCtx { //! table: ResourceTable, //! wasi: WasiCtx, //! } -//! impl IoView for MyCtx { -//! fn table(&mut self) -> &mut ResourceTable { -//! &mut self.table -//! } -//! } +//! //! impl WasiView for MyCtx { -//! fn ctx(&mut self) -> &mut WasiCtx { -//! &mut self.wasi +//! fn ctx(&mut self) -> WasiCtxView<'_> { +//! WasiCtxView { ctx: &mut self.wasi, table: &mut self.table } //! } //! } -//! //! ``` //! //! # Async and Sync @@ -177,7 +172,7 @@ //! //! Usage of this module is done through a few steps to get everything hooked up: //! -//! 1. First implement [`IoView`] and [`WasiView`] for your type which is the +//! 1. First implement [`WasiView`] for your type which is the //! `T` in `Store`. //! 2. Add WASI interfaces to a `wasmtime::component::Linker`. This is either //! done through top-level functions like [`add_to_linker_sync`] or through @@ -226,10 +221,15 @@ //! [async]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support //! [`ResourceTable`]: wasmtime::component::ResourceTable -use wasmtime::component::{HasData, Linker}; +use crate::WasiView; +use crate::cli::{WasiCli, WasiCliView as _}; +use crate::clocks::{WasiClocks, WasiClocksView as _}; +use crate::filesystem::{WasiFilesystem, WasiFilesystemView as _}; +use crate::random::WasiRandom; +use crate::sockets::{WasiSockets, WasiSocketsView as _}; +use wasmtime::component::{HasData, Linker, ResourceTable}; pub mod bindings; -mod ctx; pub(crate) mod filesystem; mod host; mod ip_name_lookup; @@ -239,18 +239,12 @@ mod poll; mod stdio; mod tcp; mod udp; -mod view; mod write_stream; -pub use self::ctx::{WasiCtx, WasiCtxBuilder}; pub use self::filesystem::{FsError, FsResult}; pub use self::network::{Network, SocketError, SocketResult}; -pub use self::stdio::{ - AsyncStdinStream, AsyncStdoutStream, InputFile, IsATTY, OutputFile, Stderr, Stdin, StdinStream, - Stdout, StdoutStream, stderr, stdin, stdout, -}; -pub use self::view::{WasiImpl, WasiView}; -use crate::random::WasiRandom; +pub use self::stdio::IsATTY; +pub(crate) use tcp::P2TcpStreamingState; // These contents of wasmtime-wasi-io are re-exported by this module for compatibility: // they were originally defined in this module before being factored out, and many // users of this module depend on them at these names. @@ -259,7 +253,6 @@ pub use wasmtime_wasi_io::streams::{ DynInputStream, DynOutputStream, Error as IoError, InputStream, OutputStream, StreamError, StreamResult, }; -pub use wasmtime_wasi_io::{IoImpl, IoView}; /// Add all WASI interfaces from this crate into the `linker` provided. /// @@ -279,7 +272,7 @@ pub use wasmtime_wasi_io::{IoImpl, IoView}; /// ``` /// use wasmtime::{Engine, Result, Store, Config}; /// use wasmtime::component::{ResourceTable, Linker}; -/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView, WasiCtxBuilder}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// /// fn main() -> Result<()> { /// let mut config = Config::new(); @@ -290,7 +283,7 @@ pub use wasmtime_wasi_io::{IoImpl, IoView}; /// wasmtime_wasi::p2::add_to_linker_async(&mut linker)?; /// // ... add any further functionality to `linker` if desired ... /// -/// let mut builder = WasiCtxBuilder::new(); +/// let mut builder = WasiCtx::builder(); /// /// // ... configure `builder` more to add env vars, args, etc ... /// @@ -312,36 +305,34 @@ pub use wasmtime_wasi_io::{IoImpl, IoView}; /// table: ResourceTable, /// } /// -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } /// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn ctx(&mut self) -> WasiCtxView<'_> { +/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +/// } /// } /// ``` -pub fn add_to_linker_async(linker: &mut Linker) -> anyhow::Result<()> { +pub fn add_to_linker_async(linker: &mut Linker) -> anyhow::Result<()> { let options = bindings::LinkOptions::default(); add_to_linker_with_options_async(linker, &options) } /// Similar to [`add_to_linker_async`], but with the ability to enable unstable features. -pub fn add_to_linker_with_options_async( +pub fn add_to_linker_with_options_async( linker: &mut Linker, options: &bindings::LinkOptions, ) -> anyhow::Result<()> { - wasmtime_wasi_io::add_to_linker_async(linker)?; + add_async_io_to_linker(linker)?; add_nonblocking_to_linker(linker, options)?; let l = linker; - let f: fn(&mut T) -> WasiImpl<&mut T> = |t| WasiImpl(IoImpl(t)); - bindings::filesystem::types::add_to_linker::>(l, f)?; - bindings::sockets::tcp::add_to_linker::>(l, f)?; - bindings::sockets::udp::add_to_linker::>(l, f)?; + bindings::filesystem::types::add_to_linker::(l, T::filesystem)?; + bindings::sockets::tcp::add_to_linker::(l, T::sockets)?; + bindings::sockets::udp::add_to_linker::(l, T::sockets)?; Ok(()) } /// Shared functionality for [`add_to_linker_async`] and [`add_to_linker_sync`]. -fn add_nonblocking_to_linker<'a, T: WasiView + 'static, O>( +fn add_nonblocking_to_linker<'a, T: WasiView, O>( linker: &mut Linker, options: &'a O, ) -> anyhow::Result<()> @@ -352,63 +343,59 @@ where use crate::p2::bindings::{cli, clocks, filesystem, random, sockets}; let l = linker; - let f: fn(&mut T) -> WasiImpl<&mut T> = |t| WasiImpl(IoImpl(t)); - clocks::wall_clock::add_to_linker::>(l, f)?; - clocks::monotonic_clock::add_to_linker::>(l, f)?; - filesystem::preopens::add_to_linker::>(l, f)?; - random::random::add_to_linker::(l, |t| &mut t.ctx().random)?; - random::insecure::add_to_linker::(l, |t| &mut t.ctx().random)?; - random::insecure_seed::add_to_linker::(l, |t| &mut t.ctx().random)?; - cli::exit::add_to_linker::>(l, &options.into(), f)?; - cli::environment::add_to_linker::>(l, f)?; - cli::stdin::add_to_linker::>(l, f)?; - cli::stdout::add_to_linker::>(l, f)?; - cli::stderr::add_to_linker::>(l, f)?; - cli::terminal_input::add_to_linker::>(l, f)?; - cli::terminal_output::add_to_linker::>(l, f)?; - cli::terminal_stdin::add_to_linker::>(l, f)?; - cli::terminal_stdout::add_to_linker::>(l, f)?; - cli::terminal_stderr::add_to_linker::>(l, f)?; - sockets::tcp_create_socket::add_to_linker::>(l, f)?; - sockets::udp_create_socket::add_to_linker::>(l, f)?; - sockets::instance_network::add_to_linker::>(l, f)?; - sockets::network::add_to_linker::>(l, &options.into(), f)?; - sockets::ip_name_lookup::add_to_linker::>(l, f)?; + clocks::wall_clock::add_to_linker::(l, T::clocks)?; + clocks::monotonic_clock::add_to_linker::(l, T::clocks)?; + filesystem::preopens::add_to_linker::(l, T::filesystem)?; + random::random::add_to_linker::(l, |t| &mut t.ctx().ctx.random)?; + random::insecure::add_to_linker::(l, |t| &mut t.ctx().ctx.random)?; + random::insecure_seed::add_to_linker::(l, |t| &mut t.ctx().ctx.random)?; + cli::exit::add_to_linker::(l, &options.into(), T::cli)?; + cli::environment::add_to_linker::(l, T::cli)?; + cli::stdin::add_to_linker::(l, T::cli)?; + cli::stdout::add_to_linker::(l, T::cli)?; + cli::stderr::add_to_linker::(l, T::cli)?; + cli::terminal_input::add_to_linker::(l, T::cli)?; + cli::terminal_output::add_to_linker::(l, T::cli)?; + cli::terminal_stdin::add_to_linker::(l, T::cli)?; + cli::terminal_stdout::add_to_linker::(l, T::cli)?; + cli::terminal_stderr::add_to_linker::(l, T::cli)?; + sockets::tcp_create_socket::add_to_linker::(l, T::sockets)?; + sockets::udp_create_socket::add_to_linker::(l, T::sockets)?; + sockets::instance_network::add_to_linker::(l, T::sockets)?; + sockets::network::add_to_linker::(l, &options.into(), T::sockets)?; + sockets::ip_name_lookup::add_to_linker::(l, T::sockets)?; Ok(()) } /// Same as [`add_to_linker_async`] except that this only adds interfaces /// present in the `wasi:http/proxy` world. -pub fn add_to_linker_proxy_interfaces_async( +pub fn add_to_linker_proxy_interfaces_async( linker: &mut Linker, ) -> anyhow::Result<()> { - wasmtime_wasi_io::add_to_linker_async(linker)?; + add_async_io_to_linker(linker)?; add_proxy_interfaces_nonblocking(linker) } /// Same as [`add_to_linker_sync`] except that this only adds interfaces /// present in the `wasi:http/proxy` world. #[doc(hidden)] -pub fn add_to_linker_proxy_interfaces_sync( +pub fn add_to_linker_proxy_interfaces_sync( linker: &mut Linker, ) -> anyhow::Result<()> { add_sync_wasi_io(linker)?; add_proxy_interfaces_nonblocking(linker) } -fn add_proxy_interfaces_nonblocking( - linker: &mut Linker, -) -> anyhow::Result<()> { +fn add_proxy_interfaces_nonblocking(linker: &mut Linker) -> anyhow::Result<()> { use crate::p2::bindings::{cli, clocks, random}; let l = linker; - let f: fn(&mut T) -> WasiImpl<&mut T> = |t| WasiImpl(IoImpl(t)); - clocks::wall_clock::add_to_linker::>(l, f)?; - clocks::monotonic_clock::add_to_linker::>(l, f)?; - random::random::add_to_linker::(l, |t| &mut t.ctx().random)?; - cli::stdin::add_to_linker::>(l, f)?; - cli::stdout::add_to_linker::>(l, f)?; - cli::stderr::add_to_linker::>(l, f)?; + clocks::wall_clock::add_to_linker::(l, T::clocks)?; + clocks::monotonic_clock::add_to_linker::(l, T::clocks)?; + random::random::add_to_linker::(l, |t| &mut t.ctx().ctx.random)?; + cli::stdin::add_to_linker::(l, T::cli)?; + cli::stdout::add_to_linker::(l, T::cli)?; + cli::stderr::add_to_linker::(l, T::cli)?; Ok(()) } @@ -430,7 +417,7 @@ fn add_proxy_interfaces_nonblocking( /// ``` /// use wasmtime::{Engine, Result, Store, Config}; /// use wasmtime::component::{ResourceTable, Linker}; -/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView, WasiCtxBuilder}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// /// fn main() -> Result<()> { /// let engine = Engine::default(); @@ -439,7 +426,7 @@ fn add_proxy_interfaces_nonblocking( /// wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?; /// // ... add any further functionality to `linker` if desired ... /// -/// let mut builder = WasiCtxBuilder::new(); +/// let mut builder = WasiCtx::builder(); /// /// // ... configure `builder` more to add env vars, args, etc ... /// @@ -460,14 +447,13 @@ fn add_proxy_interfaces_nonblocking( /// ctx: WasiCtx, /// table: ResourceTable, /// } -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } /// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } +/// fn ctx(&mut self) -> WasiCtxView<'_> { +/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table } +/// } /// } /// ``` -pub fn add_to_linker_sync( +pub fn add_to_linker_sync( linker: &mut wasmtime::component::Linker, ) -> anyhow::Result<()> { let options = bindings::sync::LinkOptions::default(); @@ -475,7 +461,7 @@ pub fn add_to_linker_sync( } /// Similar to [`add_to_linker_sync`], but with the ability to enable unstable features. -pub fn add_to_linker_with_options_sync( +pub fn add_to_linker_with_options_sync( linker: &mut wasmtime::component::Linker, options: &bindings::sync::LinkOptions, ) -> anyhow::Result<()> { @@ -483,34 +469,39 @@ pub fn add_to_linker_with_options_sync( add_sync_wasi_io(linker)?; let l = linker; - let f: fn(&mut T) -> WasiImpl<&mut T> = |t| WasiImpl(IoImpl(t)); - bindings::sync::filesystem::types::add_to_linker::>(l, f)?; - bindings::sync::sockets::tcp::add_to_linker::>(l, f)?; - bindings::sync::sockets::udp::add_to_linker::>(l, f)?; + bindings::sync::filesystem::types::add_to_linker::(l, T::filesystem)?; + bindings::sync::sockets::tcp::add_to_linker::(l, T::sockets)?; + bindings::sync::sockets::udp::add_to_linker::(l, T::sockets)?; Ok(()) } /// Shared functionality of [`add_to_linker_sync`]` and /// [`add_to_linker_proxy_interfaces_sync`]. -fn add_sync_wasi_io( +fn add_sync_wasi_io( linker: &mut wasmtime::component::Linker, ) -> anyhow::Result<()> { let l = linker; - let f: fn(&mut T) -> IoImpl<&mut T> = |t| IoImpl(t); - wasmtime_wasi_io::bindings::wasi::io::error::add_to_linker::>(l, f)?; - bindings::sync::io::poll::add_to_linker::>(l, f)?; - bindings::sync::io::streams::add_to_linker::>(l, f)?; + wasmtime_wasi_io::bindings::wasi::io::error::add_to_linker::(l, |t| t.ctx().table)?; + bindings::sync::io::poll::add_to_linker::(l, |t| t.ctx().table)?; + bindings::sync::io::streams::add_to_linker::(l, |t| t.ctx().table)?; Ok(()) } -struct HasIo(T); +struct HasIo; -impl HasData for HasIo { - type Data<'a> = IoImpl<&'a mut T>; +impl HasData for HasIo { + type Data<'a> = &'a mut ResourceTable; } -struct HasWasi(T); - -impl HasData for HasWasi { - type Data<'a> = WasiImpl<&'a mut T>; +// FIXME: it's a bit unfortunate that this can't use +// `wasmtime_wasi_io::add_to_linker` and that's because `T: WasiView`, here, +// not `T: IoView`. Ideally we'd have `impl IoView for T` but +// that's not possible with these two traits in separate crates. For now this +// is some small duplication but if this gets worse over time then we'll want +// to massage this. +fn add_async_io_to_linker(l: &mut Linker) -> anyhow::Result<()> { + wasmtime_wasi_io::bindings::wasi::io::error::add_to_linker::(l, |t| t.ctx().table)?; + wasmtime_wasi_io::bindings::wasi::io::poll::add_to_linker::(l, |t| t.ctx().table)?; + wasmtime_wasi_io::bindings::wasi::io::streams::add_to_linker::(l, |t| t.ctx().table)?; + Ok(()) } diff --git a/crates/wasi/src/p2/network.rs b/crates/wasi/src/p2/network.rs index d608624411..f1531c32a3 100644 --- a/crates/wasi/src/p2/network.rs +++ b/crates/wasi/src/p2/network.rs @@ -1,8 +1,7 @@ -use core::net::SocketAddr; - +use crate::TrappableError; use crate::p2::bindings::sockets::network::ErrorCode; -use crate::sockets::SocketAddrCheck; -use crate::{SocketAddrUse, TrappableError}; +use crate::sockets::{SocketAddrCheck, SocketAddrUse}; +use std::net::SocketAddr; pub type SocketResult = Result; @@ -49,17 +48,19 @@ impl From for ErrorCode { crate::sockets::util::ErrorCode::ConnectionReset => Self::ConnectionReset, crate::sockets::util::ErrorCode::ConnectionAborted => Self::ConnectionAborted, crate::sockets::util::ErrorCode::DatagramTooLarge => Self::DatagramTooLarge, + crate::sockets::util::ErrorCode::NotInProgress => Self::NotInProgress, + crate::sockets::util::ErrorCode::ConcurrencyConflict => Self::ConcurrencyConflict, } } } pub struct Network { - pub socket_addr_check: SocketAddrCheck, - pub allow_ip_name_lookup: bool, + pub(crate) socket_addr_check: SocketAddrCheck, + pub(crate) allow_ip_name_lookup: bool, } impl Network { - pub async fn check_socket_addr( + pub(crate) async fn check_socket_addr( &self, addr: SocketAddr, reason: SocketAddrUse, diff --git a/crates/wasi/src/p2/pipe.rs b/crates/wasi/src/p2/pipe.rs index 0d9df29f62..96b94f40cb 100644 --- a/crates/wasi/src/p2/pipe.rs +++ b/crates/wasi/src/p2/pipe.rs @@ -9,7 +9,10 @@ //! use anyhow::anyhow; use bytes::Bytes; +use std::pin::{Pin, pin}; use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; +use tokio::io::{self, AsyncRead, AsyncWrite}; use tokio::sync::mpsc; use wasmtime_wasi_io::{ poll::Pollable, @@ -54,6 +57,20 @@ impl Pollable for MemoryInputPipe { async fn ready(&mut self) {} } +impl AsyncRead for MemoryInputPipe { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut io::ReadBuf<'_>, + ) -> Poll> { + let mut buffer = self.buffer.lock().unwrap(); + let size = buf.remaining().min(buffer.len()); + let read = buffer.split_to(size); + buf.put_slice(&read); + Poll::Ready(Ok(())) + } +} + #[derive(Debug, Clone)] pub struct MemoryOutputPipe { capacity: usize, @@ -110,6 +127,25 @@ impl Pollable for MemoryOutputPipe { async fn ready(&mut self) {} } +impl AsyncWrite for MemoryOutputPipe { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let mut buffer = self.buffer.lock().unwrap(); + let amt = buf.len().min(self.capacity - buffer.len()); + buffer.extend_from_slice(&buf[..amt]); + Poll::Ready(Ok(amt)) + } + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + /// Provides a [`InputStream`] impl from a [`tokio::io::AsyncRead`] impl pub struct AsyncReadStream { closed: bool, @@ -121,9 +157,10 @@ pub struct AsyncReadStream { impl AsyncReadStream { /// Create a [`AsyncReadStream`]. In order to use the [`InputStream`] impl /// provided by this struct, the argument must impl [`tokio::io::AsyncRead`]. - pub fn new(mut reader: T) -> Self { + pub fn new(reader: T) -> Self { let (sender, receiver) = mpsc::channel(1); let join_handle = crate::runtime::spawn(async move { + let mut reader = pin!(reader); loop { use tokio::io::AsyncReadExt; let mut buf = bytes::BytesMut::with_capacity(4096); @@ -149,6 +186,21 @@ impl AsyncReadStream { join_handle: Some(join_handle), } } + pub(crate) fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<()> { + if self.buffer.is_some() || self.closed { + return Poll::Ready(()); + } + match self.receiver.poll_recv(cx) { + Poll::Ready(Some(res)) => { + self.buffer = Some(res); + Poll::Ready(()) + } + Poll::Ready(None) => { + panic!("no more sender for an open AsyncReadStream - should be impossible") + } + Poll::Pending => Poll::Pending, + } + } } #[async_trait::async_trait] @@ -201,18 +253,11 @@ impl InputStream for AsyncReadStream { } } } + #[async_trait::async_trait] impl Pollable for AsyncReadStream { async fn ready(&mut self) { - if self.buffer.is_some() || self.closed { - return; - } - match self.receiver.recv().await { - Some(res) => self.buffer = Some(res), - None => { - panic!("no more sender for an open AsyncReadStream - should be impossible") - } - } + std::future::poll_fn(|cx| self.poll_ready(cx)).await } } diff --git a/crates/wasi/src/p2/poll.rs b/crates/wasi/src/p2/poll.rs index 35a0443043..9cbee5f960 100644 --- a/crates/wasi/src/p2/poll.rs +++ b/crates/wasi/src/p2/poll.rs @@ -1,22 +1,16 @@ use crate::runtime::in_tokio; -use wasmtime_wasi_io::{IoImpl, IoView, bindings::wasi::io::poll as async_poll, poll::DynPollable}; +use wasmtime_wasi_io::{bindings::wasi::io::poll as async_poll, poll::DynPollable}; use anyhow::Result; -use wasmtime::component::Resource; +use wasmtime::component::{Resource, ResourceTable}; -impl crate::p2::bindings::sync::io::poll::Host for IoImpl -where - T: IoView, -{ +impl crate::p2::bindings::sync::io::poll::Host for ResourceTable { fn poll(&mut self, pollables: Vec>) -> Result> { in_tokio(async { async_poll::Host::poll(self, pollables).await }) } } -impl crate::p2::bindings::sync::io::poll::HostPollable for IoImpl -where - T: IoView, -{ +impl crate::p2::bindings::sync::io::poll::HostPollable for ResourceTable { fn ready(&mut self, pollable: Resource) -> Result { in_tokio(async { async_poll::HostPollable::ready(self, pollable).await }) } diff --git a/crates/wasi/src/p2/stdio.rs b/crates/wasi/src/p2/stdio.rs index eeb72c56b7..3184e00623 100644 --- a/crates/wasi/src/p2/stdio.rs +++ b/crates/wasi/src/p2/stdio.rs @@ -1,685 +1,82 @@ -use crate::cli::IsTerminal; +use crate::cli::{IsTerminal, WasiCliCtxView}; use crate::p2::bindings::cli::{ stderr, stdin, stdout, terminal_input, terminal_output, terminal_stderr, terminal_stdin, terminal_stdout, }; -use crate::p2::pipe; -use crate::p2::{ - InputStream, IoView, OutputStream, Pollable, StreamError, StreamResult, WasiImpl, WasiView, -}; -use bytes::Bytes; -use std::sync::Arc; -use tokio::sync::Mutex; use wasmtime::component::Resource; use wasmtime_wasi_io::streams; -/// A trait used to represent the standard input to a guest program. -/// -/// This is used to implement various WASI APIs via the method implementations -/// below. -/// -/// Built-in implementations are provided for [`Stdin`], -/// [`pipe::MemoryInputPipe`], and [`pipe::ClosedInputStream`]. -pub trait StdinStream: IsTerminal + Send { - /// Creates a fresh stream which is reading stdin. - /// - /// Note that the returned stream must share state with all other streams - /// previously created. Guests may create multiple handles to the same stdin - /// and they should all be synchronized in their progress through the - /// program's input. - /// - /// Note that this means that if one handle becomes ready for reading they - /// all become ready for reading. Subsequently if one is read from it may - /// mean that all the others are no longer ready for reading. This is - /// basically a consequence of the way the WIT APIs are designed today. - fn stream(&self) -> Box; -} - -impl StdinStream for pipe::MemoryInputPipe { - fn stream(&self) -> Box { - Box::new(self.clone()) - } -} - -impl IsTerminal for pipe::MemoryInputPipe { - fn is_terminal(&self) -> bool { - false - } -} - -impl StdinStream for pipe::ClosedInputStream { - fn stream(&self) -> Box { - Box::new(*self) - } -} - -impl IsTerminal for pipe::ClosedInputStream { - fn is_terminal(&self) -> bool { - false - } -} - -/// This implementation will yield input streams that block on reads, and -/// reads directly from a file. If truly async input is required, -/// [`AsyncStdinStream`] should be used instead. -pub struct InputFile { - file: Arc, -} - -impl InputFile { - pub fn new(file: std::fs::File) -> Self { - Self { - file: Arc::new(file), - } - } -} - -impl StdinStream for InputFile { - fn stream(&self) -> Box { - Box::new(InputFileStream { - file: Arc::clone(&self.file), - }) - } -} - -impl IsTerminal for InputFile { - fn is_terminal(&self) -> bool { - false - } -} - -struct InputFileStream { - file: Arc, -} - -#[async_trait::async_trait] -impl Pollable for InputFileStream { - async fn ready(&mut self) {} -} - -impl InputStream for InputFileStream { - fn read(&mut self, size: usize) -> StreamResult { - use std::io::Read; - - let mut buf = bytes::BytesMut::zeroed(size); - let bytes_read = self - .file - .read(&mut buf) - .map_err(|e| StreamError::LastOperationFailed(anyhow::anyhow!(e)))?; - if bytes_read == 0 { - return Err(StreamError::Closed); - } - buf.truncate(bytes_read); - StreamResult::Ok(buf.into()) - } -} - -/// An impl of [`StdinStream`] built on top of [`crate::p2::pipe::AsyncReadStream`]. -// -// Note the usage of `tokio::sync::Mutex` here as opposed to a -// `std::sync::Mutex`. This is intentionally done to implement the `Pollable` -// variant of this trait. Note that in doing so we're left with the quandry of -// how to implement methods of `InputStream` since those methods are not -// `async`. They're currently implemented with `try_lock`, which then raises the -// question of what to do on contention. Currently traps are returned. -// -// Why should it be ok to return a trap? In general concurrency/contention -// shouldn't return a trap since it should be able to happen normally. The -// current assumption, though, is that WASI stdin/stdout streams are special -// enough that the contention case should never come up in practice. Currently -// in WASI there is no actually concurrency, there's just the items in a single -// `Store` and that store owns all of its I/O in a single Tokio task. There's no -// means to actually spawn multiple Tokio tasks that use the same store. This -// means at the very least that there's zero parallelism. Due to the lack of -// multiple tasks that also means that there's no concurrency either. -// -// This `AsyncStdinStream` wrapper is only intended to be used by the WASI -// bindings themselves. It's possible for the host to take this and work with it -// on its own task, but that's niche enough it's not designed for. -// -// Overall that means that the guest is either calling `Pollable` or -// `InputStream` methods. This means that there should never be contention -// between the two at this time. This may all change in the future with WASI -// 0.3, but perhaps we'll have a better story for stdio at that time (see the -// doc block on the `OutputStream` impl below) -pub struct AsyncStdinStream(Arc>); - -impl AsyncStdinStream { - pub fn new(s: crate::p2::pipe::AsyncReadStream) -> Self { - Self(Arc::new(Mutex::new(s))) - } -} - -impl StdinStream for AsyncStdinStream { - fn stream(&self) -> Box { - Box::new(Self(self.0.clone())) - } -} - -impl IsTerminal for AsyncStdinStream { - fn is_terminal(&self) -> bool { - false - } -} - -#[async_trait::async_trait] -impl InputStream for AsyncStdinStream { - fn read(&mut self, size: usize) -> Result { - match self.0.try_lock() { - Ok(mut stream) => stream.read(size), - Err(_) => Err(StreamError::trap("concurrent reads are not supported")), - } - } - fn skip(&mut self, size: usize) -> Result { - match self.0.try_lock() { - Ok(mut stream) => stream.skip(size), - Err(_) => Err(StreamError::trap("concurrent skips are not supported")), - } - } - async fn cancel(&mut self) { - // Cancel the inner stream if we're the last reference to it: - if let Some(mutex) = Arc::get_mut(&mut self.0) { - match mutex.try_lock() { - Ok(mut stream) => stream.cancel().await, - Err(_) => {} - } - } - } -} - -#[async_trait::async_trait] -impl Pollable for AsyncStdinStream { - async fn ready(&mut self) { - self.0.lock().await.ready().await - } -} - -mod worker_thread_stdin; -pub use self::worker_thread_stdin::{Stdin, stdin}; - -/// Similar to [`StdinStream`], except for output. -pub trait StdoutStream: IsTerminal + Send { - /// Returns a fresh new stream which can write to this output stream. - /// - /// Note that all output streams should output to the same logical source. - /// This means that it's possible for each independent stream to acquire a - /// separate "permit" to write and then act on that permit. Note that - /// additionally at this time once a permit is "acquired" there's no way to - /// release it, for example you can wait for readiness and then never - /// actually write in WASI. This means that acquisition of a permit for one - /// stream cannot discount the size of a permit another stream could - /// obtain. - /// - /// Implementations must be able to handle this - fn stream(&self) -> Box; -} - -impl StdoutStream for pipe::MemoryOutputPipe { - fn stream(&self) -> Box { - Box::new(self.clone()) - } -} - -impl IsTerminal for pipe::MemoryOutputPipe { - fn is_terminal(&self) -> bool { - false - } -} - -impl StdoutStream for pipe::SinkOutputStream { - fn stream(&self) -> Box { - Box::new(*self) - } -} - -impl IsTerminal for pipe::SinkOutputStream { - fn is_terminal(&self) -> bool { - false - } -} - -impl StdoutStream for pipe::ClosedOutputStream { - fn stream(&self) -> Box { - Box::new(*self) - } -} - -impl IsTerminal for pipe::ClosedOutputStream { - fn is_terminal(&self) -> bool { - false - } -} - -/// This implementation will yield output streams that block on writes, and -/// output directly to a file. If truly async output is required, [`AsyncStdoutStream`] -/// should be used instead. -pub struct OutputFile { - file: Arc, -} - -impl OutputFile { - pub fn new(file: std::fs::File) -> Self { - Self { - file: Arc::new(file), - } - } -} - -impl StdoutStream for OutputFile { - fn stream(&self) -> Box { - Box::new(OutputFileStream { - file: Arc::clone(&self.file), - }) - } -} - -impl IsTerminal for OutputFile { - fn is_terminal(&self) -> bool { - false - } -} - -struct OutputFileStream { - file: Arc, -} - -#[async_trait::async_trait] -impl Pollable for OutputFileStream { - async fn ready(&mut self) {} -} - -impl OutputStream for OutputFileStream { - fn write(&mut self, bytes: Bytes) -> StreamResult<()> { - use std::io::Write; - self.file - .write_all(&bytes) - .map_err(|e| StreamError::LastOperationFailed(anyhow::anyhow!(e))) - } - - fn flush(&mut self) -> StreamResult<()> { - use std::io::Write; - self.file - .flush() - .map_err(|e| StreamError::LastOperationFailed(anyhow::anyhow!(e))) - } - - fn check_write(&mut self) -> StreamResult { - Ok(1024 * 1024) - } -} - -/// This implementation will yield output streams that block on writes, as they -/// inherit the implementation directly from the rust std library. A different -/// implementation of [`StdoutStream`] will be necessary if truly async output -/// streams are required. -pub struct Stdout; - -/// Returns a stream that represents the host's standard out. -/// -/// Suitable for passing to -/// [`WasiCtxBuilder::stdout`](crate::p2::WasiCtxBuilder::stdout). -pub fn stdout() -> Stdout { - Stdout -} - -impl StdoutStream for Stdout { - fn stream(&self) -> Box { - Box::new(StdioOutputStream::Stdout) - } -} - -impl IsTerminal for Stdout { - fn is_terminal(&self) -> bool { - std::io::stdout().is_terminal() - } -} - -/// This implementation will yield output streams that block on writes, as they -/// inherit the implementation directly from the rust std library. A different -/// implementation of [`StdoutStream`] will be necessary if truly async output -/// streams are required. -pub struct Stderr; - -/// Returns a stream that represents the host's standard err. -/// -/// Suitable for passing to -/// [`WasiCtxBuilder::stderr`](crate::p2::WasiCtxBuilder::stderr). -pub fn stderr() -> Stderr { - Stderr -} - -impl StdoutStream for Stderr { - fn stream(&self) -> Box { - Box::new(StdioOutputStream::Stderr) - } -} - -impl IsTerminal for Stderr { - fn is_terminal(&self) -> bool { - std::io::stderr().is_terminal() - } -} - -enum StdioOutputStream { - Stdout, - Stderr, -} - -impl OutputStream for StdioOutputStream { - fn write(&mut self, bytes: Bytes) -> StreamResult<()> { - use std::io::Write; - match self { - StdioOutputStream::Stdout => std::io::stdout().write_all(&bytes), - StdioOutputStream::Stderr => std::io::stderr().write_all(&bytes), - } - .map_err(|e| StreamError::LastOperationFailed(anyhow::anyhow!(e))) - } - - fn flush(&mut self) -> StreamResult<()> { - use std::io::Write; - match self { - StdioOutputStream::Stdout => std::io::stdout().flush(), - StdioOutputStream::Stderr => std::io::stderr().flush(), - } - .map_err(|e| StreamError::LastOperationFailed(anyhow::anyhow!(e))) - } - - fn check_write(&mut self) -> StreamResult { - Ok(1024 * 1024) - } -} - -#[async_trait::async_trait] -impl Pollable for StdioOutputStream { - async fn ready(&mut self) {} -} - -/// A wrapper of [`crate::p2::pipe::AsyncWriteStream`] that implements -/// [`StdoutStream`]. Note that the [`OutputStream`] impl for this is not -/// correct when used for interleaved async IO. -// -// Note that the use of `tokio::sync::Mutex` here is intentional, in addition to -// the `try_lock()` calls below in the implementation of `OutputStream`. For -// more information see the documentation on `AsyncStdinStream`. -pub struct AsyncStdoutStream(Arc>); - -impl AsyncStdoutStream { - pub fn new(s: crate::p2::pipe::AsyncWriteStream) -> Self { - Self(Arc::new(Mutex::new(s))) - } -} - -impl StdoutStream for AsyncStdoutStream { - fn stream(&self) -> Box { - Box::new(Self(self.0.clone())) - } -} - -impl IsTerminal for AsyncStdoutStream { - fn is_terminal(&self) -> bool { - false - } -} - -// This implementation is known to be bogus. All check-writes and writes are -// directed at the same underlying stream. The check-write/write protocol does -// require the size returned by a check-write to be accepted by write, even if -// other side-effects happen between those calls, and this implementation -// permits another view (created by StdoutStream::stream()) of the same -// underlying stream to accept a write which will invalidate a prior -// check-write of another view. -// Ultimately, the Std{in,out}Stream::stream() methods exist because many -// different places in a linked component (which may itself contain many -// modules) may need to access stdio without any coordination to keep those -// accesses all using pointing to the same resource. So, we allow many -// resources to be created. We have the reasonable expectation that programs -// won't attempt to interleave async IO from these disparate uses of stdio. -// If that expectation doesn't turn out to be true, and you find yourself at -// this comment to correct it: sorry about that. -#[async_trait::async_trait] -impl OutputStream for AsyncStdoutStream { - fn check_write(&mut self) -> Result { - match self.0.try_lock() { - Ok(mut stream) => stream.check_write(), - Err(_) => Err(StreamError::trap("concurrent writes are not supported")), - } - } - fn write(&mut self, bytes: Bytes) -> Result<(), StreamError> { - match self.0.try_lock() { - Ok(mut stream) => stream.write(bytes), - Err(_) => Err(StreamError::trap("concurrent writes not supported yet")), - } - } - fn flush(&mut self) -> Result<(), StreamError> { - match self.0.try_lock() { - Ok(mut stream) => stream.flush(), - Err(_) => Err(StreamError::trap("concurrent flushes not supported yet")), - } - } - async fn cancel(&mut self) { - // Cancel the inner stream if we're the last reference to it: - if let Some(mutex) = Arc::get_mut(&mut self.0) { - match mutex.try_lock() { - Ok(mut stream) => stream.cancel().await, - Err(_) => {} - } - } - } -} - -#[async_trait::async_trait] -impl Pollable for AsyncStdoutStream { - async fn ready(&mut self) { - self.0.lock().await.ready().await - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IsATTY { Yes, No, } -impl stdin::Host for WasiImpl -where - T: WasiView, -{ +impl stdin::Host for WasiCliCtxView<'_> { fn get_stdin(&mut self) -> Result, anyhow::Error> { - let stream = self.ctx().stdin.stream(); - Ok(self.table().push(stream)?) + let stream = self.ctx.stdin.p2_stream(); + Ok(self.table.push(stream)?) } } -impl stdout::Host for WasiImpl -where - T: WasiView, -{ +impl stdout::Host for WasiCliCtxView<'_> { fn get_stdout(&mut self) -> Result, anyhow::Error> { - let stream = self.ctx().stdout.stream(); - Ok(self.table().push(stream)?) + let stream = self.ctx.stdout.p2_stream(); + Ok(self.table.push(stream)?) } } -impl stderr::Host for WasiImpl -where - T: WasiView, -{ +impl stderr::Host for WasiCliCtxView<'_> { fn get_stderr(&mut self) -> Result, anyhow::Error> { - let stream = self.ctx().stderr.stream(); - Ok(self.table().push(stream)?) + let stream = self.ctx.stderr.p2_stream(); + Ok(self.table.push(stream)?) } } pub struct TerminalInput; pub struct TerminalOutput; -impl terminal_input::Host for WasiImpl where T: WasiView {} -impl terminal_input::HostTerminalInput for WasiImpl -where - T: WasiView, -{ +impl terminal_input::Host for WasiCliCtxView<'_> {} +impl terminal_input::HostTerminalInput for WasiCliCtxView<'_> { fn drop(&mut self, r: Resource) -> anyhow::Result<()> { - self.table().delete(r)?; + self.table.delete(r)?; Ok(()) } } -impl terminal_output::Host for WasiImpl where T: WasiView {} -impl terminal_output::HostTerminalOutput for WasiImpl -where - T: WasiView, -{ +impl terminal_output::Host for WasiCliCtxView<'_> {} +impl terminal_output::HostTerminalOutput for WasiCliCtxView<'_> { fn drop(&mut self, r: Resource) -> anyhow::Result<()> { - self.table().delete(r)?; + self.table.delete(r)?; Ok(()) } } -impl terminal_stdin::Host for WasiImpl -where - T: WasiView, -{ +impl terminal_stdin::Host for WasiCliCtxView<'_> { fn get_terminal_stdin(&mut self) -> anyhow::Result>> { - if self.ctx().stdin.is_terminal() { - let fd = self.table().push(TerminalInput)?; + if self.ctx.stdin.is_terminal() { + let fd = self.table.push(TerminalInput)?; Ok(Some(fd)) } else { Ok(None) } } } -impl terminal_stdout::Host for WasiImpl -where - T: WasiView, -{ +impl terminal_stdout::Host for WasiCliCtxView<'_> { fn get_terminal_stdout(&mut self) -> anyhow::Result>> { - if self.ctx().stdout.is_terminal() { - let fd = self.table().push(TerminalOutput)?; + if self.ctx.stdout.is_terminal() { + let fd = self.table.push(TerminalOutput)?; Ok(Some(fd)) } else { Ok(None) } } } -impl terminal_stderr::Host for WasiImpl -where - T: WasiView, -{ +impl terminal_stderr::Host for WasiCliCtxView<'_> { fn get_terminal_stderr(&mut self) -> anyhow::Result>> { - if self.ctx().stderr.is_terminal() { - let fd = self.table().push(TerminalOutput)?; + if self.ctx.stderr.is_terminal() { + let fd = self.table.push(TerminalOutput)?; Ok(Some(fd)) } else { Ok(None) } } } - -#[cfg(test)] -mod test { - use crate::p2::stdio::StdoutStream; - use crate::p2::write_stream::AsyncWriteStream; - use crate::p2::{AsyncStdoutStream, OutputStream}; - use anyhow::Result; - use bytes::Bytes; - use tokio::io::AsyncReadExt; - - #[test] - fn memory_stdin_stream() { - // A StdinStream has the property that there are multiple - // InputStreams created, using the stream() method which are each - // views on the same shared state underneath. Consuming input on one - // stream results in consuming that input on all streams. - // - // The simplest way to measure this is to check if the MemoryInputPipe - // impl of StdinStream follows this property. - - let pipe = super::pipe::MemoryInputPipe::new( - "the quick brown fox jumped over the three lazy dogs", - ); - - use super::StdinStream; - - let mut view1 = pipe.stream(); - let mut view2 = pipe.stream(); - - let read1 = view1.read(10).expect("read first 10 bytes"); - assert_eq!(read1, "the quick ".as_bytes(), "first 10 bytes"); - let read2 = view2.read(10).expect("read second 10 bytes"); - assert_eq!(read2, "brown fox ".as_bytes(), "second 10 bytes"); - let read3 = view1.read(10).expect("read third 10 bytes"); - assert_eq!(read3, "jumped ove".as_bytes(), "third 10 bytes"); - let read4 = view2.read(10).expect("read fourth 10 bytes"); - assert_eq!(read4, "r the thre".as_bytes(), "fourth 10 bytes"); - } - - #[tokio::test] - async fn async_stdin_stream() { - // A StdinStream has the property that there are multiple - // InputStreams created, using the stream() method which are each - // views on the same shared state underneath. Consuming input on one - // stream results in consuming that input on all streams. - // - // AsyncStdinStream is a slightly more complex impl of StdinStream - // than the MemoryInputPipe above. We can create an AsyncReadStream - // from a file on the disk, and an AsyncStdinStream from that common - // stream, then check that the same property holds as above. - - let dir = tempfile::tempdir().unwrap(); - let mut path = std::path::PathBuf::from(dir.path()); - path.push("file"); - std::fs::write(&path, "the quick brown fox jumped over the three lazy dogs").unwrap(); - - let file = tokio::fs::File::open(&path) - .await - .expect("open created file"); - let stdin_stream = - super::AsyncStdinStream::new(crate::p2::pipe::AsyncReadStream::new(file)); - - use super::StdinStream; - - let mut view1 = stdin_stream.stream(); - let mut view2 = stdin_stream.stream(); - - view1.ready().await; - - let read1 = view1.read(10).expect("read first 10 bytes"); - assert_eq!(read1, "the quick ".as_bytes(), "first 10 bytes"); - let read2 = view2.read(10).expect("read second 10 bytes"); - assert_eq!(read2, "brown fox ".as_bytes(), "second 10 bytes"); - let read3 = view1.read(10).expect("read third 10 bytes"); - assert_eq!(read3, "jumped ove".as_bytes(), "third 10 bytes"); - let read4 = view2.read(10).expect("read fourth 10 bytes"); - assert_eq!(read4, "r the thre".as_bytes(), "fourth 10 bytes"); - } - - #[tokio::test] - async fn async_stdout_stream_unblocks() { - let (mut read, write) = tokio::io::duplex(32); - let stdout = AsyncStdoutStream::new(AsyncWriteStream::new(32, write)); - - let task = tokio::task::spawn(async move { - let mut stream = stdout.stream(); - blocking_write_and_flush(&mut *stream, "x".into()) - .await - .unwrap(); - }); - - let mut buf = [0; 100]; - let n = read.read(&mut buf).await.unwrap(); - assert_eq!(&buf[..n], b"x"); - - task.await.unwrap(); - } - - async fn blocking_write_and_flush(s: &mut dyn OutputStream, mut bytes: Bytes) -> Result<()> { - while !bytes.is_empty() { - let permit = s.write_ready().await?; - let len = bytes.len().min(permit); - let chunk = bytes.split_to(len); - s.write(chunk)?; - } - - s.flush()?; - s.write_ready().await?; - Ok(()) - } -} diff --git a/crates/wasi/src/p2/tcp.rs b/crates/wasi/src/p2/tcp.rs index 188b2148d8..019c3c6b1f 100644 --- a/crates/wasi/src/p2/tcp.rs +++ b/crates/wasi/src/p2/tcp.rs @@ -1,663 +1,54 @@ -use crate::p2::bindings::sockets::tcp::ErrorCode; use crate::p2::{ DynInputStream, DynOutputStream, InputStream, OutputStream, Pollable, SocketError, SocketResult, StreamError, }; -use crate::runtime::{AbortOnDropJoinHandle, with_ambient_tokio_runtime}; -use crate::sockets::util::{ - get_unicast_hop_limit, is_valid_address_family, is_valid_remote_address, - is_valid_unicast_address, receive_buffer_size, send_buffer_size, set_keep_alive_count, - set_keep_alive_idle_time, set_keep_alive_interval, set_receive_buffer_size, - set_send_buffer_size, set_unicast_hop_limit, tcp_bind, -}; -use crate::sockets::{DEFAULT_TCP_BACKLOG, SocketAddressFamily}; +use crate::runtime::AbortOnDropJoinHandle; +use crate::sockets::TcpSocket; use anyhow::Result; -use cap_net_ext::AddressFamily; -use futures::Future; use io_lifetimes::AsSocketlike; -use io_lifetimes::views::SocketlikeView; use rustix::io::Errno; -use rustix::net::sockopt; use std::io; use std::mem; -use std::net::{Shutdown, SocketAddr}; -use std::pin::Pin; +use std::net::Shutdown; use std::sync::Arc; -use std::task::{Poll, Waker}; use tokio::sync::Mutex; -/// The state of a TCP socket. -/// -/// This represents the various states a socket can be in during the -/// activities of binding, listening, accepting, and connecting. -enum TcpState { - /// The initial state for a newly-created socket. - Default(tokio::net::TcpSocket), - - /// Binding started via `start_bind`. - BindStarted(tokio::net::TcpSocket), - - /// Binding finished via `finish_bind`. The socket has an address but - /// is not yet listening for connections. - Bound(tokio::net::TcpSocket), - - /// Listening started via `listen_start`. - ListenStarted(tokio::net::TcpSocket), - - /// The socket is now listening and waiting for an incoming connection. - Listening { - listener: tokio::net::TcpListener, - pending_accept: Option>, - }, - - /// An outgoing connection is started via `start_connect`. - Connecting(Pin> + Send>>), - - /// An outgoing connection is ready to be established. - ConnectReady(io::Result), - - /// An outgoing connection has been established. - Connected { - stream: Arc, - - // WASI is single threaded, so in practice these Mutexes should never be contended: - reader: Arc>, - writer: Arc>, - }, - - Closed, -} - -impl std::fmt::Debug for TcpState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Default(_) => f.debug_tuple("Default").finish(), - Self::BindStarted(_) => f.debug_tuple("BindStarted").finish(), - Self::Bound(_) => f.debug_tuple("Bound").finish(), - Self::ListenStarted(_) => f.debug_tuple("ListenStarted").finish(), - Self::Listening { pending_accept, .. } => f - .debug_struct("Listening") - .field("pending_accept", pending_accept) - .finish(), - Self::Connecting(_) => f.debug_tuple("Connecting").finish(), - Self::ConnectReady(_) => f.debug_tuple("ConnectReady").finish(), - Self::Connected { .. } => f.debug_tuple("Connected").finish(), - Self::Closed => write!(f, "Closed"), - } - } -} - -/// A host TCP socket, plus associated bookkeeping. -pub struct TcpSocket { - /// The current state in the bind/listen/accept/connect progression. - tcp_state: TcpState, - - /// The desired listen queue size. - listen_backlog_size: u32, - - family: SocketAddressFamily, - - // The socket options below are not automatically inherited from the listener - // on all platforms. So we keep track of which options have been explicitly - // set and manually apply those values to newly accepted clients. - #[cfg(target_os = "macos")] - receive_buffer_size: Option, - #[cfg(target_os = "macos")] - send_buffer_size: Option, - #[cfg(target_os = "macos")] - hop_limit: Option, - #[cfg(target_os = "macos")] - keep_alive_idle_time: Option, -} - impl TcpSocket { - /// Create a new socket in the given family. - pub fn new(family: AddressFamily) -> io::Result { - with_ambient_tokio_runtime(|| { - let (socket, family) = match family { - AddressFamily::Ipv4 => { - let socket = tokio::net::TcpSocket::new_v4()?; - (socket, SocketAddressFamily::Ipv4) - } - AddressFamily::Ipv6 => { - let socket = tokio::net::TcpSocket::new_v6()?; - sockopt::set_ipv6_v6only(&socket, true)?; - (socket, SocketAddressFamily::Ipv6) - } - }; - - Self::from_state(TcpState::Default(socket), family) - }) - } - - /// Create a `TcpSocket` from an existing socket. - fn from_state(state: TcpState, family: SocketAddressFamily) -> io::Result { - Ok(Self { - tcp_state: state, - listen_backlog_size: DEFAULT_TCP_BACKLOG, - family, - #[cfg(target_os = "macos")] - receive_buffer_size: None, - #[cfg(target_os = "macos")] - send_buffer_size: None, - #[cfg(target_os = "macos")] - hop_limit: None, - #[cfg(target_os = "macos")] - keep_alive_idle_time: None, - }) - } - - fn as_std_view(&self) -> SocketResult> { - use crate::p2::bindings::sockets::network::ErrorCode; - - match &self.tcp_state { - TcpState::Default(socket) | TcpState::Bound(socket) => { - Ok(socket.as_socketlike_view::()) - } - TcpState::Connected { stream, .. } => { - Ok(stream.as_socketlike_view::()) - } - TcpState::Listening { listener, .. } => { - Ok(listener.as_socketlike_view::()) - } - - TcpState::BindStarted(..) - | TcpState::ListenStarted(..) - | TcpState::Connecting(..) - | TcpState::ConnectReady(..) - | TcpState::Closed => Err(ErrorCode::InvalidState.into()), - } - } -} - -impl TcpSocket { - pub fn start_bind(&mut self, local_address: SocketAddr) -> Result<(), ErrorCode> { - let tokio_socket = match &self.tcp_state { - TcpState::Default(socket) => socket, - TcpState::BindStarted(..) => return Err(Errno::ALREADY.into()), - _ => return Err(ErrorCode::InvalidState), - }; - - if !is_valid_unicast_address(local_address.ip()) - || !is_valid_address_family(local_address.ip(), self.family) - { - return Err(ErrorCode::InvalidArgument); - }; - - { - tcp_bind(&tokio_socket, local_address)?; - - self.tcp_state = match std::mem::replace(&mut self.tcp_state, TcpState::Closed) { - TcpState::Default(socket) => TcpState::BindStarted(socket), - _ => unreachable!(), - }; - - Ok(()) - } - } - - pub fn finish_bind(&mut self) -> SocketResult<()> { - match std::mem::replace(&mut self.tcp_state, TcpState::Closed) { - TcpState::BindStarted(socket) => { - self.tcp_state = TcpState::Bound(socket); - Ok(()) - } - current_state => { - // Reset the state so that the outside world doesn't see this socket as closed - self.tcp_state = current_state; - Err(ErrorCode::NotInProgress.into()) - } - } - } - - pub fn start_connect(&mut self, remote_address: SocketAddr) -> SocketResult<()> { - match self.tcp_state { - TcpState::Default(..) | TcpState::Bound(..) => {} - - TcpState::Connecting(..) | TcpState::ConnectReady(..) => { - return Err(ErrorCode::ConcurrencyConflict.into()); - } - - _ => return Err(ErrorCode::InvalidState.into()), - }; - - if !is_valid_unicast_address(remote_address.ip()) - || !is_valid_remote_address(remote_address) - || !is_valid_address_family(remote_address.ip(), self.family) - { - return Err(ErrorCode::InvalidArgument.into()); - }; - - let (TcpState::Default(tokio_socket) | TcpState::Bound(tokio_socket)) = - std::mem::replace(&mut self.tcp_state, TcpState::Closed) - else { - unreachable!(); - }; - - let future = tokio_socket.connect(remote_address); - - self.tcp_state = TcpState::Connecting(Box::pin(future)); - Ok(()) - } - - pub fn finish_connect(&mut self) -> SocketResult<(DynInputStream, DynOutputStream)> { - let previous_state = std::mem::replace(&mut self.tcp_state, TcpState::Closed); - let result = match previous_state { - TcpState::ConnectReady(result) => result, - TcpState::Connecting(mut future) => { - let mut cx = std::task::Context::from_waker(Waker::noop()); - match with_ambient_tokio_runtime(|| future.as_mut().poll(&mut cx)) { - Poll::Ready(result) => result, - Poll::Pending => { - self.tcp_state = TcpState::Connecting(future); - return Err(ErrorCode::WouldBlock.into()); - } - } - } - previous_state => { - self.tcp_state = previous_state; - return Err(ErrorCode::NotInProgress.into()); - } - }; - - match result { - Ok(stream) => { - let stream = Arc::new(stream); - let reader = Arc::new(Mutex::new(TcpReader::new(stream.clone()))); - let writer = Arc::new(Mutex::new(TcpWriter::new(stream.clone()))); - self.tcp_state = TcpState::Connected { - stream, - reader: reader.clone(), - writer: writer.clone(), - }; - let input: DynInputStream = Box::new(TcpReadStream(reader)); - let output: DynOutputStream = Box::new(TcpWriteStream(writer)); - Ok((input, output)) - } - Err(err) => { - self.tcp_state = TcpState::Closed; - Err(err.into()) - } - } - } - - pub fn start_listen(&mut self) -> SocketResult<()> { - match std::mem::replace(&mut self.tcp_state, TcpState::Closed) { - TcpState::Bound(tokio_socket) => { - self.tcp_state = TcpState::ListenStarted(tokio_socket); - Ok(()) - } - TcpState::ListenStarted(tokio_socket) => { - self.tcp_state = TcpState::ListenStarted(tokio_socket); - Err(ErrorCode::ConcurrencyConflict.into()) - } - previous_state => { - self.tcp_state = previous_state; - Err(ErrorCode::InvalidState.into()) - } - } - } - - pub fn finish_listen(&mut self) -> SocketResult<()> { - let tokio_socket = match std::mem::replace(&mut self.tcp_state, TcpState::Closed) { - TcpState::ListenStarted(tokio_socket) => tokio_socket, - previous_state => { - self.tcp_state = previous_state; - return Err(ErrorCode::NotInProgress.into()); - } - }; - - match with_ambient_tokio_runtime(|| tokio_socket.listen(self.listen_backlog_size)) { - Ok(listener) => { - self.tcp_state = TcpState::Listening { - listener, - pending_accept: None, - }; - Ok(()) - } - Err(err) => { - self.tcp_state = TcpState::Closed; - - Err(match Errno::from_io_error(&err) { - // See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen#:~:text=WSAEMFILE - // According to the docs, `listen` can return EMFILE on Windows. - // This is odd, because we're not trying to create a new socket - // or file descriptor of any kind. So we rewrite it to less - // surprising error code. - // - // At the time of writing, this behavior has never been experimentally - // observed by any of the wasmtime authors, so we're relying fully - // on Microsoft's documentation here. - #[cfg(windows)] - Some(Errno::MFILE) => Errno::NOBUFS.into(), - - _ => err.into(), - }) - } - } - } - - pub fn accept(&mut self) -> SocketResult<(Self, DynInputStream, DynOutputStream)> { - let TcpState::Listening { - listener, - pending_accept, - } = &mut self.tcp_state - else { - return Err(ErrorCode::InvalidState.into()); - }; - - let result = match pending_accept.take() { - Some(result) => result, - None => { - let mut cx = std::task::Context::from_waker(Waker::noop()); - match with_ambient_tokio_runtime(|| listener.poll_accept(&mut cx)) - .map_ok(|(stream, _)| stream) - { - Poll::Ready(result) => result, - Poll::Pending => Err(Errno::WOULDBLOCK.into()), - } - } - }; - - let client = result.map_err(|err| match Errno::from_io_error(&err) { - // From: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept#:~:text=WSAEINPROGRESS - // > WSAEINPROGRESS: A blocking Windows Sockets 1.1 call is in progress, - // > or the service provider is still processing a callback function. - // - // wasi-sockets doesn't have an equivalent to the EINPROGRESS error, - // because in POSIX this error is only returned by a non-blocking - // `connect` and wasi-sockets has a different solution for that. - #[cfg(windows)] - Some(Errno::INPROGRESS) => Errno::INTR.into(), - - // Normalize Linux' non-standard behavior. - // - // From https://man7.org/linux/man-pages/man2/accept.2.html: - // > Linux accept() passes already-pending network errors on the - // > new socket as an error code from accept(). This behavior - // > differs from other BSD socket implementations. (...) - #[cfg(target_os = "linux")] - Some( - Errno::CONNRESET - | Errno::NETRESET - | Errno::HOSTUNREACH - | Errno::HOSTDOWN - | Errno::NETDOWN - | Errno::NETUNREACH - | Errno::PROTO - | Errno::NOPROTOOPT - | Errno::NONET - | Errno::OPNOTSUPP, - ) => Errno::CONNABORTED.into(), - - _ => err, - })?; - - #[cfg(target_os = "macos")] - { - // Manually inherit socket options from listener. We only have to - // do this on platforms that don't already do this automatically - // and only if a specific value was explicitly set on the listener. - - if let Some(size) = self.receive_buffer_size { - _ = set_receive_buffer_size(&client, size); // Ignore potential error. - } - - if let Some(size) = self.send_buffer_size { - _ = set_send_buffer_size(&client, size); // Ignore potential error. - } - - // For some reason, IP_TTL is inherited, but IPV6_UNICAST_HOPS isn't. - if let (SocketAddressFamily::Ipv6, Some(ttl)) = (self.family, self.hop_limit) { - _ = rustix::net::sockopt::set_ipv6_unicast_hops(&client, Some(ttl)); // Ignore potential error. - } - - if let Some(value) = self.keep_alive_idle_time { - _ = set_keep_alive_idle_time(&client, value); // Ignore potential error. - } - } - - let client = Arc::new(client); - + pub(crate) fn p2_streams(&mut self) -> SocketResult<(DynInputStream, DynOutputStream)> { + let client = self.tcp_stream_arc()?; let reader = Arc::new(Mutex::new(TcpReader::new(client.clone()))); let writer = Arc::new(Mutex::new(TcpWriter::new(client.clone()))); - - let input: DynInputStream = Box::new(TcpReadStream(reader.clone())); - let output: DynOutputStream = Box::new(TcpWriteStream(writer.clone())); - let tcp_socket = TcpSocket::from_state( - TcpState::Connected { - stream: client, - reader, - writer, - }, - self.family, - )?; - - Ok((tcp_socket, input, output)) - } - - pub fn local_address(&self) -> SocketResult { - let view = match self.tcp_state { - TcpState::Default(..) => return Err(ErrorCode::InvalidState.into()), - TcpState::BindStarted(..) => return Err(ErrorCode::ConcurrencyConflict.into()), - _ => self.as_std_view()?, - }; - - Ok(view.local_addr()?) - } - - pub fn remote_address(&self) -> SocketResult { - let view = match self.tcp_state { - TcpState::Connected { .. } => self.as_std_view()?, - TcpState::Connecting(..) | TcpState::ConnectReady(..) => { - return Err(ErrorCode::ConcurrencyConflict.into()); - } - _ => return Err(ErrorCode::InvalidState.into()), - }; - - Ok(view.peer_addr()?) - } - - pub fn is_listening(&self) -> bool { - matches!(self.tcp_state, TcpState::Listening { .. }) - } - - pub fn address_family(&self) -> SocketAddressFamily { - self.family - } - - pub fn set_listen_backlog_size(&mut self, value: u32) -> SocketResult<()> { - const MIN_BACKLOG: u32 = 1; - const MAX_BACKLOG: u32 = i32::MAX as u32; // OS'es will most likely limit it down even further. - - if value == 0 { - return Err(ErrorCode::InvalidArgument.into()); - } - - // Silently clamp backlog size. This is OK for us to do, because operating systems do this too. - let value = value.clamp(MIN_BACKLOG, MAX_BACKLOG); - - match &self.tcp_state { - TcpState::Default(..) | TcpState::Bound(..) => { - // Socket not listening yet. Stash value for first invocation to `listen`. - } - TcpState::Listening { listener, .. } => { - // Try to update the backlog by calling `listen` again. - // Not all platforms support this. We'll only update our own value if the OS supports changing the backlog size after the fact. - - rustix::net::listen(&listener, value.try_into().unwrap()) - .map_err(|_| ErrorCode::NotSupported)?; - } - _ => return Err(ErrorCode::InvalidState.into()), - } - self.listen_backlog_size = value; - - Ok(()) - } - - pub fn keep_alive_enabled(&self) -> SocketResult { - let view = &*self.as_std_view()?; - Ok(sockopt::socket_keepalive(view)?) - } - - pub fn set_keep_alive_enabled(&self, value: bool) -> SocketResult<()> { - let view = &*self.as_std_view()?; - Ok(sockopt::set_socket_keepalive(view, value)?) - } - - pub fn keep_alive_idle_time(&self) -> SocketResult { - let view = &*self.as_std_view()?; - Ok(sockopt::tcp_keepidle(view)?) - } - - pub fn set_keep_alive_idle_time(&mut self, value: u64) -> SocketResult<()> { - { - let view = &*self.as_std_view()?; - set_keep_alive_idle_time(view, value)?; - } - - #[cfg(target_os = "macos")] - { - self.keep_alive_idle_time = Some(value); - } - - Ok(()) - } - - pub fn keep_alive_interval(&self) -> SocketResult { - let view = &*self.as_std_view()?; - Ok(sockopt::tcp_keepintvl(view)?) - } - - pub fn set_keep_alive_interval(&self, duration: std::time::Duration) -> SocketResult<()> { - let view = &*self.as_std_view()?; - Ok(set_keep_alive_interval(view, duration)?) - } - - pub fn keep_alive_count(&self) -> SocketResult { - let view = &*self.as_std_view()?; - Ok(sockopt::tcp_keepcnt(view)?) - } - - pub fn set_keep_alive_count(&self, value: u32) -> SocketResult<()> { - let view = &*self.as_std_view()?; - Ok(set_keep_alive_count(view, value)?) - } - - pub fn hop_limit(&self) -> SocketResult { - let view = &*self.as_std_view()?; - - let ttl = get_unicast_hop_limit(view, self.family)?; - Ok(ttl) - } - - pub fn set_hop_limit(&mut self, value: u8) -> SocketResult<()> { - { - let view = &*self.as_std_view()?; - - set_unicast_hop_limit(view, self.family, value)?; - } - - #[cfg(target_os = "macos")] - { - self.hop_limit = Some(value); - } - - Ok(()) - } - - pub fn receive_buffer_size(&self) -> SocketResult { - let view = &*self.as_std_view()?; - - Ok(receive_buffer_size(view)?) - } - - pub fn set_receive_buffer_size(&mut self, value: u64) -> SocketResult<()> { - { - let view = &*self.as_std_view()?; - - set_receive_buffer_size(view, value)?; - } - - #[cfg(target_os = "macos")] - { - self.receive_buffer_size = Some(value); - } - - Ok(()) - } - - pub fn send_buffer_size(&self) -> SocketResult { - let view = &*self.as_std_view()?; - - Ok(send_buffer_size(view)?) - } - - pub fn set_send_buffer_size(&mut self, value: u64) -> SocketResult<()> { - { - let view = &*self.as_std_view()?; - - set_send_buffer_size(view, value)?; - } - - #[cfg(target_os = "macos")] - { - self.send_buffer_size = Some(value); - } - - Ok(()) + self.set_p2_streaming_state(P2TcpStreamingState { + stream: client.clone(), + reader: reader.clone(), + writer: writer.clone(), + })?; + let input: DynInputStream = Box::new(TcpReadStream(reader)); + let output: DynOutputStream = Box::new(TcpWriteStream(writer)); + Ok((input, output)) } +} - pub fn shutdown(&self, how: Shutdown) -> SocketResult<()> { - let TcpState::Connected { reader, writer, .. } = &self.tcp_state else { - return Err(ErrorCode::InvalidState.into()); - }; +pub(crate) struct P2TcpStreamingState { + pub(crate) stream: Arc, + reader: Arc>, + writer: Arc>, +} +impl P2TcpStreamingState { + pub(crate) fn shutdown(&self, how: Shutdown) -> SocketResult<()> { if let Shutdown::Both | Shutdown::Read = how { - try_lock_for_socket(reader)?.shutdown(); + try_lock_for_socket(&self.reader)?.shutdown(); } if let Shutdown::Both | Shutdown::Write = how { - try_lock_for_socket(writer)?.shutdown(); + try_lock_for_socket(&self.writer)?.shutdown(); } Ok(()) } } -#[async_trait::async_trait] -impl Pollable for TcpSocket { - async fn ready(&mut self) { - match &mut self.tcp_state { - TcpState::Default(..) - | TcpState::BindStarted(..) - | TcpState::Bound(..) - | TcpState::ListenStarted(..) - | TcpState::ConnectReady(..) - | TcpState::Closed - | TcpState::Connected { .. } => { - // No async operation in progress. - } - TcpState::Connecting(future) => { - self.tcp_state = TcpState::ConnectReady(future.as_mut().await); - } - TcpState::Listening { - listener, - pending_accept, - } => match pending_accept { - Some(_) => {} - None => { - let result = futures::future::poll_fn(|cx| { - listener.poll_accept(cx).map_ok(|(stream, _)| stream) - }) - .await; - *pending_accept = Some(result); - } - }, - } - } -} - struct TcpReader { stream: Arc, closed: bool, @@ -961,7 +352,7 @@ fn try_lock_for_stream(mutex: &Mutex) -> Result(mutex: &Mutex) -> Result, SocketError> { +fn try_lock_for_socket(mutex: &Mutex) -> SocketResult> { mutex.try_lock().map_err(|_| { SocketError::trap(anyhow::anyhow!( "concurrent access to resource not supported" diff --git a/crates/wasi/src/p2/udp.rs b/crates/wasi/src/p2/udp.rs index a066fb97eb..0165a95504 100644 --- a/crates/wasi/src/p2/udp.rs +++ b/crates/wasi/src/p2/udp.rs @@ -1,94 +1,6 @@ -use crate::runtime::with_ambient_tokio_runtime; -use crate::sockets::util::udp_socket; use crate::sockets::{SocketAddrCheck, SocketAddressFamily}; -use async_trait::async_trait; -use cap_net_ext::AddressFamily; -use io_lifetimes::raw::{FromRawSocketlike, IntoRawSocketlike}; -use std::io; use std::net::SocketAddr; use std::sync::Arc; -use wasmtime_wasi_io::poll::Pollable; - -/// The state of a UDP socket. -/// -/// This represents the various states a socket can be in during the -/// activities of binding, and connecting. -pub(crate) enum UdpState { - /// The initial state for a newly-created socket. - Default, - - /// Binding started via `start_bind`. - BindStarted, - - /// Binding finished via `finish_bind`. The socket has an address but - /// is not yet listening for connections. - Bound, - - /// The socket is "connected" to a peer address. - Connected, -} - -/// A host UDP socket, plus associated bookkeeping. -/// -/// The inner state is wrapped in an Arc because the same underlying socket is -/// used for implementing the stream types. -pub struct UdpSocket { - /// The part of a `UdpSocket` which is reference-counted so that we - /// can pass it to async tasks. - pub(crate) inner: Arc, - - /// The current state in the bind/connect progression. - pub(crate) udp_state: UdpState, - - /// Socket address family. - pub(crate) family: SocketAddressFamily, - - /// The check of allowed addresses - pub(crate) socket_addr_check: Option, -} - -#[async_trait] -impl Pollable for UdpSocket { - async fn ready(&mut self) { - // None of the socket-level operations block natively - } -} - -impl UdpSocket { - /// Create a new socket in the given family. - pub fn new(family: AddressFamily) -> io::Result { - // Create a new host socket and set it to non-blocking, which is needed - // by our async implementation. - let fd = udp_socket(family)?; - - let socket_address_family = match family { - AddressFamily::Ipv4 => SocketAddressFamily::Ipv4, - AddressFamily::Ipv6 => { - rustix::net::sockopt::set_ipv6_v6only(&fd, true)?; - SocketAddressFamily::Ipv6 - } - }; - - let socket = Self::setup_tokio_udp_socket(fd.into())?; - - Ok(UdpSocket { - inner: Arc::new(socket), - udp_state: UdpState::Default, - family: socket_address_family, - socket_addr_check: None, - }) - } - - fn setup_tokio_udp_socket(fd: rustix::fd::OwnedFd) -> io::Result { - let std_socket = - unsafe { std::net::UdpSocket::from_raw_socketlike(fd.into_raw_socketlike()) }; - with_ambient_tokio_runtime(|| tokio::net::UdpSocket::try_from(std_socket)) - } - - pub fn udp_socket(&self) -> &tokio::net::UdpSocket { - &self.inner - } -} pub struct IncomingDatagramStream { pub(crate) inner: Arc, diff --git a/crates/wasi/src/p2/view.rs b/crates/wasi/src/p2/view.rs deleted file mode 100644 index ade11ef372..0000000000 --- a/crates/wasi/src/p2/view.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::p2::ctx::WasiCtx; -use wasmtime::component::ResourceTable; -pub use wasmtime_wasi_io::{IoImpl, IoView}; - -/// A trait which provides access to the [`WasiCtx`] inside the embedder's `T` -/// of [`Store`][`Store`]. -/// -/// This crate's WASI Host implementations depend on the contents of -/// [`WasiCtx`]. The `T` type [`Store`][`Store`] is defined in each -/// embedding of Wasmtime. These implementations are connected to the -/// [`Linker`][`Linker`] by the -/// [`add_to_linker_sync`](crate::p2::add_to_linker_sync) and -/// [`add_to_linker_async`](crate::p2::add_to_linker_async) functions. -/// -/// The [`WasiView`] trait implies the [`IoView`] trait, so each `T` must -/// also contain a [`ResourceTable`] and impl `IoView`. -/// -/// # Example -/// -/// ``` -/// use wasmtime_wasi::ResourceTable; -/// use wasmtime_wasi::p2::{WasiCtx, WasiView, IoView, WasiCtxBuilder}; -/// -/// struct MyState { -/// ctx: WasiCtx, -/// table: ResourceTable, -/// } -/// -/// impl IoView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } -/// impl WasiView for MyState { -/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx } -/// } -/// ``` -/// [`Store`]: wasmtime::Store -/// [`Linker`]: wasmtime::component::Linker -/// [`ResourceTable`]: wasmtime::component::ResourceTable -/// -pub trait WasiView: IoView { - /// Yields mutable access to the [`WasiCtx`] configuration used for this - /// context. - fn ctx(&mut self) -> &mut WasiCtx; -} - -impl WasiView for &mut T { - fn ctx(&mut self) -> &mut WasiCtx { - T::ctx(self) - } -} - -impl WasiView for Box { - fn ctx(&mut self) -> &mut WasiCtx { - T::ctx(self) - } -} - -/// A small newtype wrapper which serves as the basis for implementations of -/// `Host` WASI traits in this crate. -/// -/// This type is used as the basis for the implementation of all `Host` traits -/// generated by `bindgen!` for WASI interfaces. This is used automatically with -/// [`add_to_linker_sync`](crate::p2::add_to_linker_sync) and -/// [`add_to_linker_async`](crate::p2::add_to_linker_async). -/// -/// This type is otherwise provided if you're calling the `add_to_linker` -/// functions generated by `bindgen!` from the [`bindings` -/// module](crate::p2::bindings). In this situation you'll want to create a value of -/// this type in the closures added to a `Linker`. -#[repr(transparent)] -pub struct WasiImpl(pub IoImpl); - -impl IoView for WasiImpl { - fn table(&mut self) -> &mut ResourceTable { - T::table(&mut self.0.0) - } -} -impl WasiView for WasiImpl { - fn ctx(&mut self) -> &mut WasiCtx { - T::ctx(&mut self.0.0) - } -} diff --git a/crates/wasi/src/p2/write_stream.rs b/crates/wasi/src/p2/write_stream.rs index c7c80e50a4..e29d343520 100644 --- a/crates/wasi/src/p2/write_stream.rs +++ b/crates/wasi/src/p2/write_stream.rs @@ -1,7 +1,9 @@ use crate::p2::{OutputStream, Pollable, StreamError}; use anyhow::anyhow; use bytes::Bytes; +use std::pin::pin; use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll, Waker}; #[derive(Debug)] struct WorkerState { @@ -10,6 +12,7 @@ struct WorkerState { write_budget: usize, flush_pending: bool, error: Option, + write_ready_changed: Option, } impl WorkerState { @@ -27,7 +30,6 @@ impl WorkerState { struct Worker { state: Mutex, new_work: tokio::sync::Notify, - write_ready_changed: tokio::sync::Notify, } enum Job { @@ -44,23 +46,9 @@ impl Worker { write_budget, flush_pending: false, error: None, + write_ready_changed: None, }), new_work: tokio::sync::Notify::new(), - write_ready_changed: tokio::sync::Notify::new(), - } - } - async fn ready(&self) { - loop { - { - let state = self.state(); - if state.error.is_some() - || !state.alive - || (!state.flush_pending && state.write_budget > 0) - { - return; - } - } - self.write_ready_changed.notified().await; } } fn check_write(&self) -> Result { @@ -91,16 +79,20 @@ impl Worker { None } fn report_error(&self, e: std::io::Error) { - { + let waker = { let mut state = self.state(); state.alive = false; state.error = Some(e.into()); state.flush_pending = false; + state.write_ready_changed.take() + }; + if let Some(waker) = waker { + waker.wake(); } - self.write_ready_changed.notify_one(); } - async fn work(&self, mut writer: T) { + async fn work(&self, writer: T) { use tokio::io::AsyncWriteExt; + let mut writer = pin!(writer); loop { while let Some(job) = self.pop() { match job { @@ -129,7 +121,10 @@ impl Worker { } } - self.write_ready_changed.notify_one(); + let waker = self.state().write_ready_changed.take(); + if let Some(waker) = waker { + waker.wake(); + } } self.new_work.notified().await; } @@ -145,10 +140,7 @@ pub struct AsyncWriteStream { impl AsyncWriteStream { /// Create a [`AsyncWriteStream`]. In order to use the [`OutputStream`] impl /// provided by this struct, the argument must impl [`tokio::io::AsyncWrite`]. - pub fn new( - write_budget: usize, - writer: T, - ) -> Self { + pub fn new(write_budget: usize, writer: T) -> Self { let worker = Arc::new(Worker::new(write_budget)); let w = Arc::clone(&worker); @@ -159,6 +151,16 @@ impl AsyncWriteStream { join_handle: Some(join_handle), } } + + pub(crate) fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<()> { + let mut state = self.worker.state(); + if state.error.is_some() || !state.alive || (!state.flush_pending && state.write_budget > 0) + { + return Poll::Ready(()); + } + state.write_ready_changed = Some(cx.waker().clone()); + Poll::Pending + } } #[async_trait::async_trait] @@ -206,6 +208,6 @@ impl OutputStream for AsyncWriteStream { #[async_trait::async_trait] impl Pollable for AsyncWriteStream { async fn ready(&mut self) { - self.worker.ready().await; + std::future::poll_fn(|cx| self.poll_ready(cx)).await } } diff --git a/crates/wasi/src/p3/bindings.rs b/crates/wasi/src/p3/bindings.rs index 10c78d7545..d84f26b027 100644 --- a/crates/wasi/src/p3/bindings.rs +++ b/crates/wasi/src/p3/bindings.rs @@ -11,8 +11,8 @@ //! use this modules's bindings rather than generate fresh bindings. That can be //! done using the `with` option to [`bindgen!`]: //! -//! ```rust,ignore(TODO fix when wasi finishes merging upstream) -//! use wasmtime_wasi::p3::{WasiCtx, WasiCtxView, WasiView}; +//! ```rust +//! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; //! use wasmtime::{Result, Engine, Config}; //! use wasmtime::component::{Linker, HasSelf, ResourceTable}; //! @@ -94,10 +94,14 @@ mod generated { with: { "wasi:cli/terminal-input/terminal-input": crate::p3::cli::TerminalInput, "wasi:cli/terminal-output/terminal-output": crate::p3::cli::TerminalOutput, - "wasi:sockets/types/tcp-socket": crate::p3::sockets::tcp::TcpSocket, - "wasi:sockets/types/udp-socket": crate::p3::sockets::udp::UdpSocket, - "wasi:filesystem/types/descriptor": crate::p3::filesystem::Descriptor, - } + "wasi:filesystem/types/descriptor": crate::filesystem::Descriptor, + "wasi:sockets/types/tcp-socket": crate::sockets::TcpSocket, + "wasi:sockets/types/udp-socket": crate::sockets::UdpSocket, + }, + trappable_error_type: { + "wasi:filesystem/types/error-code" => crate::p3::filesystem::FilesystemError, + "wasi:sockets/types/error-code" => crate::p3::sockets::SocketError, + }, }); } pub use self::generated::LinkOptions; @@ -114,10 +118,10 @@ pub use self::generated::wasi::*; /// /// # Examples /// -/// ```no_run,ignore(TODO fix after upstreaming is complete) +/// ```no_run /// use wasmtime::{Engine, Result, Store, Config}; /// use wasmtime::component::{Component, Linker, ResourceTable}; -/// use wasmtime_wasi::p3::{WasiCtx, WasiCtxView, WasiCtxBuilder, WasiView}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime_wasi::p3::bindings::Command; /// /// // This example is an example shim of executing a component based on the @@ -141,7 +145,7 @@ pub use self::generated::wasi::*; /// /// // Configure a `WasiCtx` based on this program's environment. Then /// // build a `Store` to instantiate into. -/// let mut builder = WasiCtxBuilder::new(); +/// let mut builder = WasiCtx::builder(); /// builder.inherit_stdio().inherit_env().args(&args); /// let mut store = Store::new( /// &engine, @@ -188,10 +192,10 @@ pub use self::generated::Command; /// /// # Examples /// -/// ```no_run,ignore(TODO fix after upstreaming is complete) +/// ```no_run /// use wasmtime::{Engine, Result, Store, Config}; /// use wasmtime::component::{Linker, Component, ResourceTable}; -/// use wasmtime_wasi::p3::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime_wasi::p3::bindings::CommandPre; /// /// // This example is an example shim of executing a component based on the @@ -216,7 +220,7 @@ pub use self::generated::Command; /// /// // Configure a `WasiCtx` based on this program's environment. Then /// // build a `Store` to instantiate into. -/// let mut builder = WasiCtxBuilder::new(); +/// let mut builder = WasiCtx::builder(); /// builder.inherit_stdio().inherit_env().args(&args); /// let mut store = Store::new( /// &engine, diff --git a/crates/wasi/src/p3/cli/host.rs b/crates/wasi/src/p3/cli/host.rs index b5d25fb4e1..cc0da1639a 100644 --- a/crates/wasi/src/p3/cli/host.rs +++ b/crates/wasi/src/p3/cli/host.rs @@ -1,11 +1,11 @@ use crate::I32Exit; -use crate::cli::IsTerminal; +use crate::cli::{IsTerminal, WasiCli, WasiCliCtxView}; use crate::p3::DEFAULT_BUFFER_CAPACITY; use crate::p3::bindings::cli::{ environment, exit, stderr, stdin, stdout, terminal_input, terminal_output, terminal_stderr, terminal_stdin, terminal_stdout, }; -use crate::p3::cli::{TerminalInput, TerminalOutput, WasiCli, WasiCliCtxView}; +use crate::p3::cli::{TerminalInput, TerminalOutput}; use anyhow::{Context as _, anyhow}; use bytes::BytesMut; use std::io::Cursor; @@ -145,7 +145,7 @@ impl stdin::HostWithStore for WasiCli { let (tx, rx) = instance .stream(&mut view) .context("failed to create stream")?; - let stdin = view.get().ctx.stdin.reader(); + let stdin = view.get().ctx.stdin.async_stream(); view.spawn(InputTask { rx: Box::into_pin(stdin), tx, @@ -163,7 +163,7 @@ impl stdout::HostWithStore for WasiCli { data: StreamReader, ) -> wasmtime::Result<()> { store.with(|mut view| { - let tx = view.get().ctx.stdout.writer(); + let tx = view.get().ctx.stdout.async_stream(); view.spawn(OutputTask { rx: data, tx: Box::into_pin(tx), @@ -181,7 +181,7 @@ impl stderr::HostWithStore for WasiCli { data: StreamReader, ) -> wasmtime::Result<()> { store.with(|mut view| { - let tx = view.get().ctx.stderr.writer(); + let tx = view.get().ctx.stderr.async_stream(); view.spawn(OutputTask { rx: data, tx: Box::into_pin(tx), diff --git a/crates/wasi/src/p3/cli/mod.rs b/crates/wasi/src/p3/cli/mod.rs index 4d4f3041f3..5989e9c5a6 100644 --- a/crates/wasi/src/p3/cli/mod.rs +++ b/crates/wasi/src/p3/cli/mod.rs @@ -1,34 +1,11 @@ mod host; -use crate::cli::{IsTerminal, WasiCliCtx}; -use crate::p3::bindings::cli; -use std::sync::Arc; -use tokio::io::{ - AsyncRead, AsyncWrite, Empty, Stderr, Stdin, Stdout, empty, stderr, stdin, stdout, +use crate::cli::{WasiCli, WasiCliView}; +use crate::p3::bindings::cli::{ + environment, exit, stderr, stdin, stdout, terminal_input, terminal_output, terminal_stderr, + terminal_stdin, terminal_stdout, }; -use wasmtime::component::{HasData, Linker, ResourceTable}; - -pub struct WasiCliCtxView<'a> { - pub ctx: &'a mut WasiCliCtx, Box>, - pub table: &'a mut ResourceTable, -} - -pub trait WasiCliView: Send { - fn cli(&mut self) -> WasiCliCtxView<'_>; -} - -impl Default for WasiCliCtx, Box> { - fn default() -> Self { - Self { - environment: Vec::default(), - arguments: Vec::default(), - initial_cwd: None, - stdin: Box::new(empty()), - stdout: Box::new(empty()), - stderr: Box::new(empty()), - } - } -} +use wasmtime::component::Linker; /// Add all WASI interfaces from this module into the `linker` provided. /// @@ -45,8 +22,7 @@ impl Default for WasiCliCtx, Box> { /// ``` /// use wasmtime::{Engine, Result, Store, Config}; /// use wasmtime::component::{Linker, ResourceTable}; -/// use wasmtime_wasi::cli::WasiCliCtx; -/// use wasmtime_wasi::p3::cli::{InputStream, OutputStream, WasiCliView, WasiCliCtxView}; +/// use wasmtime_wasi::cli::{WasiCliCtx, WasiCliView, WasiCliCtxView}; /// /// fn main() -> Result<()> { /// let mut config = Config::new(); @@ -70,7 +46,7 @@ impl Default for WasiCliCtx, Box> { /// /// #[derive(Default)] /// struct MyState { -/// cli: WasiCliCtx, Box>, +/// cli: WasiCliCtx, /// table: ResourceTable, /// } /// @@ -87,152 +63,30 @@ pub fn add_to_linker(linker: &mut Linker) -> wasmtime::Result<()> where T: WasiCliView + 'static, { - let exit_options = cli::exit::LinkOptions::default(); + let exit_options = exit::LinkOptions::default(); add_to_linker_with_options(linker, &exit_options) } /// Similar to [`add_to_linker`], but with the ability to enable unstable features. pub fn add_to_linker_with_options( linker: &mut Linker, - exit_options: &cli::exit::LinkOptions, + exit_options: &exit::LinkOptions, ) -> anyhow::Result<()> where T: WasiCliView + 'static, { - cli::exit::add_to_linker::<_, WasiCli>(linker, exit_options, T::cli)?; - cli::environment::add_to_linker::<_, WasiCli>(linker, T::cli)?; - cli::stdin::add_to_linker::<_, WasiCli>(linker, T::cli)?; - cli::stdout::add_to_linker::<_, WasiCli>(linker, T::cli)?; - cli::stderr::add_to_linker::<_, WasiCli>(linker, T::cli)?; - cli::terminal_input::add_to_linker::<_, WasiCli>(linker, T::cli)?; - cli::terminal_output::add_to_linker::<_, WasiCli>(linker, T::cli)?; - cli::terminal_stdin::add_to_linker::<_, WasiCli>(linker, T::cli)?; - cli::terminal_stdout::add_to_linker::<_, WasiCli>(linker, T::cli)?; - cli::terminal_stderr::add_to_linker::<_, WasiCli>(linker, T::cli)?; + exit::add_to_linker::<_, WasiCli>(linker, exit_options, T::cli)?; + environment::add_to_linker::<_, WasiCli>(linker, T::cli)?; + stdin::add_to_linker::<_, WasiCli>(linker, T::cli)?; + stdout::add_to_linker::<_, WasiCli>(linker, T::cli)?; + stderr::add_to_linker::<_, WasiCli>(linker, T::cli)?; + terminal_input::add_to_linker::<_, WasiCli>(linker, T::cli)?; + terminal_output::add_to_linker::<_, WasiCli>(linker, T::cli)?; + terminal_stdin::add_to_linker::<_, WasiCli>(linker, T::cli)?; + terminal_stdout::add_to_linker::<_, WasiCli>(linker, T::cli)?; + terminal_stderr::add_to_linker::<_, WasiCli>(linker, T::cli)?; Ok(()) } -struct WasiCli; - -impl HasData for WasiCli { - type Data<'a> = WasiCliCtxView<'a>; -} - pub struct TerminalInput; pub struct TerminalOutput; - -pub trait InputStream: IsTerminal + Send { - fn reader(&self) -> Box; -} - -impl InputStream for &T { - fn reader(&self) -> Box { - T::reader(self) - } -} - -impl InputStream for &mut T { - fn reader(&self) -> Box { - T::reader(self) - } -} - -impl InputStream for Box { - fn reader(&self) -> Box { - T::reader(self) - } -} - -impl InputStream for Arc { - fn reader(&self) -> Box { - T::reader(self) - } -} - -impl InputStream for Empty { - fn reader(&self) -> Box { - Box::new(empty()) - } -} - -impl InputStream for std::io::Empty { - fn reader(&self) -> Box { - Box::new(empty()) - } -} - -impl InputStream for Stdin { - fn reader(&self) -> Box { - Box::new(stdin()) - } -} - -impl InputStream for std::io::Stdin { - fn reader(&self) -> Box { - Box::new(stdin()) - } -} - -pub trait OutputStream: IsTerminal + Send { - fn writer(&self) -> Box; -} - -impl OutputStream for &T { - fn writer(&self) -> Box { - T::writer(self) - } -} - -impl OutputStream for &mut T { - fn writer(&self) -> Box { - T::writer(self) - } -} - -impl OutputStream for Box { - fn writer(&self) -> Box { - T::writer(self) - } -} - -impl OutputStream for Arc { - fn writer(&self) -> Box { - T::writer(self) - } -} - -impl OutputStream for Empty { - fn writer(&self) -> Box { - Box::new(empty()) - } -} - -impl OutputStream for std::io::Empty { - fn writer(&self) -> Box { - Box::new(empty()) - } -} - -impl OutputStream for Stdout { - fn writer(&self) -> Box { - Box::new(stdout()) - } -} - -impl OutputStream for std::io::Stdout { - fn writer(&self) -> Box { - Box::new(stdout()) - } -} - -impl OutputStream for Stderr { - fn writer(&self) -> Box { - Box::new(stderr()) - } -} - -impl OutputStream for std::io::Stderr { - fn writer(&self) -> Box { - Box::new(stderr()) - } -} diff --git a/crates/wasi/src/p3/clocks/host.rs b/crates/wasi/src/p3/clocks/host.rs index 52b3f317f6..5af5cc5b2c 100644 --- a/crates/wasi/src/p3/clocks/host.rs +++ b/crates/wasi/src/p3/clocks/host.rs @@ -1,30 +1,13 @@ +use crate::clocks::WasiClocksCtxView; +use crate::p3::bindings::clocks::{monotonic_clock, wall_clock}; +use crate::p3::clocks::WasiClocks; use core::time::Duration; - -use cap_std::time::SystemTime; use tokio::time::sleep; use wasmtime::component::Accessor; -use crate::clocks::WasiClocksCtx; -use crate::p3::bindings::clocks::{monotonic_clock, wall_clock}; -use crate::p3::clocks::WasiClocks; - -impl TryFrom for wall_clock::Datetime { - type Error = wasmtime::Error; - - fn try_from(time: SystemTime) -> Result { - let duration = - time.duration_since(SystemTime::from_std(std::time::SystemTime::UNIX_EPOCH))?; - - Ok(Self { - seconds: duration.as_secs(), - nanoseconds: duration.subsec_nanos(), - }) - } -} - -impl wall_clock::Host for WasiClocksCtx { +impl wall_clock::Host for WasiClocksCtxView<'_> { fn now(&mut self) -> wasmtime::Result { - let now = self.wall_clock.now(); + let now = self.ctx.wall_clock.now(); Ok(wall_clock::Datetime { seconds: now.as_secs(), nanoseconds: now.subsec_nanos(), @@ -32,7 +15,7 @@ impl wall_clock::Host for WasiClocksCtx { } fn resolution(&mut self) -> wasmtime::Result { - let res = self.wall_clock.resolution(); + let res = self.ctx.wall_clock.resolution(); Ok(wall_clock::Datetime { seconds: res.as_secs(), nanoseconds: res.subsec_nanos(), @@ -45,7 +28,7 @@ impl monotonic_clock::HostWithStore for WasiClocks { store: &Accessor, when: monotonic_clock::Instant, ) -> wasmtime::Result<()> { - let clock_now = store.with(|mut view| view.get().monotonic_clock.now()); + let clock_now = store.with(|mut view| view.get().ctx.monotonic_clock.now()); if when > clock_now { sleep(Duration::from_nanos(when - clock_now)).await; }; @@ -63,12 +46,12 @@ impl monotonic_clock::HostWithStore for WasiClocks { } } -impl monotonic_clock::Host for WasiClocksCtx { +impl monotonic_clock::Host for WasiClocksCtxView<'_> { fn now(&mut self) -> wasmtime::Result { - Ok(self.monotonic_clock.now()) + Ok(self.ctx.monotonic_clock.now()) } fn resolution(&mut self) -> wasmtime::Result { - Ok(self.monotonic_clock.resolution()) + Ok(self.ctx.monotonic_clock.resolution()) } } diff --git a/crates/wasi/src/p3/clocks/mod.rs b/crates/wasi/src/p3/clocks/mod.rs index d8e8f3bdcb..3c7ecdb58d 100644 --- a/crates/wasi/src/p3/clocks/mod.rs +++ b/crates/wasi/src/p3/clocks/mod.rs @@ -1,8 +1,9 @@ mod host; -use crate::clocks::{WasiClocksCtx, WasiClocksView}; -use crate::p3::bindings::clocks; -use wasmtime::component::{HasData, Linker}; +use crate::clocks::{WasiClocks, WasiClocksView}; +use crate::p3::bindings::clocks::{monotonic_clock, wall_clock}; +use cap_std::time::SystemTime; +use wasmtime::component::Linker; /// Add all WASI interfaces from this module into the `linker` provided. /// @@ -18,8 +19,8 @@ use wasmtime::component::{HasData, Linker}; /// /// ``` /// use wasmtime::{Engine, Result, Store, Config}; -/// use wasmtime::component::Linker; -/// use wasmtime_wasi::clocks::{WasiClocksView, WasiClocksCtx}; +/// use wasmtime::component::{Linker, ResourceTable}; +/// use wasmtime_wasi::clocks::{WasiClocksView, WasiClocksCtxView, WasiClocksCtx}; /// /// fn main() -> Result<()> { /// let mut config = Config::new(); @@ -33,9 +34,7 @@ use wasmtime::component::{HasData, Linker}; /// /// let mut store = Store::new( /// &engine, -/// MyState { -/// clocks: WasiClocksCtx::default(), -/// }, +/// MyState::default(), /// ); /// /// // ... use `linker` to instantiate within `store` ... @@ -43,25 +42,60 @@ use wasmtime::component::{HasData, Linker}; /// Ok(()) /// } /// +/// #[derive(Default)] /// struct MyState { /// clocks: WasiClocksCtx, +/// table: ResourceTable, /// } /// /// impl WasiClocksView for MyState { -/// fn clocks(&mut self) -> &mut WasiClocksCtx { &mut self.clocks } +/// fn clocks(&mut self) -> WasiClocksCtxView { +/// WasiClocksCtxView { ctx: &mut self.clocks, table: &mut self.table } +/// } /// } /// ``` pub fn add_to_linker(linker: &mut Linker) -> wasmtime::Result<()> where T: WasiClocksView + 'static, { - clocks::monotonic_clock::add_to_linker::<_, WasiClocks>(linker, T::clocks)?; - clocks::wall_clock::add_to_linker::<_, WasiClocks>(linker, T::clocks)?; + monotonic_clock::add_to_linker::<_, WasiClocks>(linker, T::clocks)?; + wall_clock::add_to_linker::<_, WasiClocks>(linker, T::clocks)?; Ok(()) } -struct WasiClocks; +impl From for wall_clock::Datetime { + fn from( + crate::clocks::Datetime { + seconds, + nanoseconds, + }: crate::clocks::Datetime, + ) -> Self { + Self { + seconds, + nanoseconds, + } + } +} + +impl From for crate::clocks::Datetime { + fn from( + wall_clock::Datetime { + seconds, + nanoseconds, + }: wall_clock::Datetime, + ) -> Self { + Self { + seconds, + nanoseconds, + } + } +} + +impl TryFrom for wall_clock::Datetime { + type Error = wasmtime::Error; -impl HasData for WasiClocks { - type Data<'a> = &'a mut WasiClocksCtx; + fn try_from(time: SystemTime) -> Result { + let time = crate::clocks::Datetime::try_from(time)?; + Ok(time.into()) + } } diff --git a/crates/wasi/src/p3/ctx.rs b/crates/wasi/src/p3/ctx.rs deleted file mode 100644 index 6876038f25..0000000000 --- a/crates/wasi/src/p3/ctx.rs +++ /dev/null @@ -1,512 +0,0 @@ -use crate::cli::WasiCliCtx; -use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx}; -use crate::p3::cli::{InputStream, OutputStream}; -use crate::p3::filesystem::Dir; -use crate::random::WasiRandomCtx; -use crate::sockets::{SocketAddrUse, WasiSocketsCtx}; -use crate::{DirPerms, FilePerms, OpenMode}; -use anyhow::Result; -use cap_rand::RngCore; -use cap_std::ambient_authority; -use std::future::Future; -use std::mem; -use std::net::SocketAddr; -use std::path::Path; -use std::pin::Pin; -use tokio::io::{empty, stderr, stdin, stdout}; - -/// Builder-style structure used to create a [`WasiCtx`]. -/// -/// This type is used to create a [`WasiCtx`] that is considered per-[`Store`] -/// state. The [`build`][WasiCtxBuilder::build] method is used to finish the -/// building process and produce a finalized [`WasiCtx`]. -/// -/// # Examples -/// -/// ``` -/// use wasmtime_wasi::p3::{WasiCtxBuilder, WasiCtx}; -/// -/// let mut wasi = WasiCtxBuilder::new(); -/// wasi.arg("./foo.wasm"); -/// wasi.arg("--help"); -/// wasi.env("FOO", "bar"); -/// -/// let wasi: WasiCtx = wasi.build(); -/// ``` -/// -/// [`Store`]: wasmtime::Store -pub struct WasiCtxBuilder { - common: crate::WasiCtxBuilder, Box>, - // TODO: implement filesystem - preopens: Vec<(Dir, String)>, - built: bool, -} - -impl Default for crate::WasiCtxBuilder, Box> { - fn default() -> Self { - crate::WasiCtxBuilder::new(Box::new(empty()), Box::new(empty()), Box::new(empty())) - } -} - -impl WasiCtxBuilder { - /// Creates a builder for a new context with default parameters set. - /// - /// The current defaults are: - /// - /// * stdin is closed - /// * stdout and stderr eat all input and it doesn't go anywhere - /// * no env vars - /// * no arguments - /// * no preopens - /// * clocks use the host implementation of wall/monotonic clocks - /// * RNGs are all initialized with random state and suitable generator - /// quality to satisfy the requirements of WASI APIs. - /// * TCP/UDP are allowed but all addresses are denied by default. - /// * `wasi:sockets/ip-name-lookup` is denied by default. - /// - /// These defaults can all be updated via the various builder configuration - /// methods below. - pub fn new() -> Self { - Self { - common: crate::WasiCtxBuilder::default(), - preopens: Vec::default(), - built: false, - } - } - - /// Provides a custom implementation of stdin to use. - /// - /// By default stdin is closed. - /// - /// Note that inheriting the process's stdin can also be done through - /// [`inherit_stdin`](WasiCtxBuilder::inherit_stdin). - pub fn stdin(&mut self, stdin: impl InputStream + 'static) -> &mut Self { - self.common.stdin(Box::new(stdin)); - self - } - - /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout. - pub fn stdout(&mut self, stdout: impl OutputStream + 'static) -> &mut Self { - self.common.stdout(Box::new(stdout)); - self - } - - /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr. - pub fn stderr(&mut self, stderr: impl OutputStream + 'static) -> &mut Self { - self.common.stderr(Box::new(stderr)); - self - } - - /// Configures this context's stdin stream to read the host process's - /// stdin. - /// - /// Note that concurrent reads of stdin can produce surprising results so - /// when using this it's typically best to have a single wasm instance in - /// the process using this. - pub fn inherit_stdin(&mut self) -> &mut Self { - self.stdin(stdin()) - } - - /// Configures this context's stdout stream to write to the host process's - /// stdout. - /// - /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) - /// multiple instances printing to stdout works well. - pub fn inherit_stdout(&mut self) -> &mut Self { - self.stdout(stdout()) - } - - /// Configures this context's stderr stream to write to the host process's - /// stderr. - /// - /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) - /// multiple instances printing to stderr works well. - pub fn inherit_stderr(&mut self) -> &mut Self { - self.stderr(stderr()) - } - - /// Configures all of stdin, stdout, and stderr to be inherited from the - /// host process. - /// - /// See [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) for some rationale - /// on why this should only be done in situations of - /// one-instance-per-process. - pub fn inherit_stdio(&mut self) -> &mut Self { - self.inherit_stdin().inherit_stdout().inherit_stderr() - } - - /// Configures whether or not blocking operations made through this - /// `WasiCtx` are allowed to block the current thread. - /// - /// WASI is currently implemented on top of the Rust - /// [Tokio](https://tokio.rs/) library. While most WASI APIs are - /// non-blocking some are instead blocking from the perspective of - /// WebAssembly. For example opening a file is a blocking operation with - /// respect to WebAssembly but it's implemented as an asynchronous operation - /// on the host. This is currently done with Tokio's - /// [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html). - /// - /// When WebAssembly is used in a synchronous context, for example when - /// [`Config::async_support`] is disabled, then this asynchronous operation - /// is quickly turned back into a synchronous operation with a `block_on` in - /// Rust. This switching back-and-forth between a blocking a non-blocking - /// context can have overhead, and this option exists to help alleviate this - /// overhead. - /// - /// This option indicates that for WASI functions that are blocking from the - /// perspective of WebAssembly it's ok to block the native thread as well. - /// This means that this back-and-forth between async and sync won't happen - /// and instead blocking operations are performed on-thread (such as opening - /// a file). This can improve the performance of WASI operations when async - /// support is disabled. - /// - /// [`Config::async_support`]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support - pub fn allow_blocking_current_thread(&mut self, enable: bool) -> &mut Self { - self.common.allow_blocking_current_thread(enable); - self - } - - /// Appends multiple environment variables at once for this builder. - /// - /// All environment variables are appended to the list of environment - /// variables that this builder will configure. - /// - /// At this time environment variables are not deduplicated and if the same - /// key is set twice then the guest will see two entries for the same key. - /// - /// # Examples - /// - /// ``` - /// use wasmtime_wasi::p3::WasiCtxBuilder; - /// - /// let mut wasi = WasiCtxBuilder::new(); - /// wasi.envs(&[ - /// ("FOO", "bar"), - /// ("HOME", "/somewhere"), - /// ]); - /// ``` - pub fn envs(&mut self, env: &[(impl AsRef, impl AsRef)]) -> &mut Self { - self.common.envs(env); - self - } - - /// Appends a single environment variable for this builder. - /// - /// At this time environment variables are not deduplicated and if the same - /// key is set twice then the guest will see two entries for the same key. - /// - /// # Examples - /// - /// ``` - /// use wasmtime_wasi::p3::WasiCtxBuilder; - /// - /// let mut wasi = WasiCtxBuilder::new(); - /// wasi.env("FOO", "bar"); - /// ``` - pub fn env(&mut self, k: impl AsRef, v: impl AsRef) -> &mut Self { - self.common.env(k, v); - self - } - - /// Configures all environment variables to be inherited from the calling - /// process into this configuration. - /// - /// This will use [`envs`](WasiCtxBuilder::envs) to append all host-defined - /// environment variables. - pub fn inherit_env(&mut self) -> &mut Self { - self.common.inherit_env(); - self - } - - /// Appends a list of arguments to the argument array to pass to wasm. - pub fn args(&mut self, args: &[impl AsRef]) -> &mut Self { - self.common.args(args); - self - } - - /// Appends a single argument to get passed to wasm. - pub fn arg(&mut self, arg: impl AsRef) -> &mut Self { - self.common.arg(arg); - self - } - - /// Appends all host process arguments to the list of arguments to get - /// passed to wasm. - pub fn inherit_args(&mut self) -> &mut Self { - self.common.inherit_args(); - self - } - - /// Configures a "preopened directory" to be available to WebAssembly. - /// - /// By default WebAssembly does not have access to the filesystem because - /// there are no preopened directories. All filesystem operations, such as - /// opening a file, are done through a preexisting handle. This means that - /// to provide WebAssembly access to a directory it must be configured - /// through this API. - /// - /// WASI will also prevent access outside of files provided here. For - /// example `..` can't be used to traverse up from the `host_path` provided here - /// to the containing directory. - /// - /// * `host_path` - a path to a directory on the host to open and make - /// accessible to WebAssembly. Note that the name of this directory in the - /// guest is configured with `guest_path` below. - /// * `guest_path` - the name of the preopened directory from WebAssembly's - /// perspective. Note that this does not need to match the host's name for - /// the directory. - /// * `dir_perms` - this is the permissions that wasm will have to operate on - /// `guest_path`. This can be used, for example, to provide readonly access to a - /// directory. - /// * `file_perms` - similar to `dir_perms` but corresponds to the maximum set - /// of permissions that can be used for any file in this directory. - /// - /// # Errors - /// - /// This method will return an error if `host_path` cannot be opened. - /// - /// # Examples - /// - /// ``` - /// use wasmtime_wasi::p3::WasiCtxBuilder; - /// use wasmtime_wasi::{DirPerms, FilePerms}; - /// - /// # fn main() {} - /// # fn foo() -> wasmtime::Result<()> { - /// let mut wasi = WasiCtxBuilder::new(); - /// - /// // Make `./host-directory` available in the guest as `.` - /// wasi.preopened_dir("./host-directory", ".", DirPerms::all(), FilePerms::all()); - /// - /// // Make `./readonly` available in the guest as `./ro` - /// wasi.preopened_dir("./readonly", "./ro", DirPerms::READ, FilePerms::READ); - /// # Ok(()) - /// # } - /// ``` - pub fn preopened_dir( - &mut self, - host_path: impl AsRef, - guest_path: impl AsRef, - dir_perms: DirPerms, - file_perms: FilePerms, - ) -> Result<&mut Self> { - let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?; - let mut open_mode = OpenMode::empty(); - if dir_perms.contains(DirPerms::READ) { - open_mode |= OpenMode::READ; - } - if dir_perms.contains(DirPerms::MUTATE) { - open_mode |= OpenMode::WRITE; - } - self.preopens.push(( - Dir::new( - dir, - dir_perms, - file_perms, - open_mode, - self.common.allow_blocking_current_thread, - ), - guest_path.as_ref().to_owned(), - )); - Ok(self) - } - - /// Set the generator for the `wasi:random/random` number generator to the - /// custom generator specified. - /// - /// Note that contexts have a default RNG configured which is a suitable - /// generator for WASI and is configured with a random seed per-context. - /// - /// Guest code may rely on this random number generator to produce fresh - /// unpredictable random data in order to maintain its security invariants, - /// and ideally should use the insecure random API otherwise, so using any - /// prerecorded or otherwise predictable data may compromise security. - pub fn secure_random(&mut self, random: impl RngCore + Send + 'static) -> &mut Self { - self.common.secure_random(random); - self - } - - /// Configures the generator for `wasi:random/insecure`. - /// - /// The `insecure_random` generator provided will be used for all randomness - /// requested by the `wasi:random/insecure` interface. - pub fn insecure_random(&mut self, insecure_random: impl RngCore + Send + 'static) -> &mut Self { - self.common.insecure_random(insecure_random); - self - } - - /// Configures the seed to be returned from `wasi:random/insecure-seed` to - /// the specified custom value. - /// - /// By default this number is randomly generated when a builder is created. - pub fn insecure_random_seed(&mut self, insecure_random_seed: u128) -> &mut Self { - self.common.insecure_random_seed(insecure_random_seed); - self - } - - /// Configures `wasi:clocks/wall-clock` to use the `clock` specified. - /// - /// By default the host's wall clock is used. - pub fn wall_clock(&mut self, clock: impl HostWallClock + 'static) -> &mut Self { - self.common.wall_clock(clock); - self - } - - /// Configures `wasi:clocks/monotonic-clock` to use the `clock` specified. - /// - /// By default the host's monotonic clock is used. - pub fn monotonic_clock(&mut self, clock: impl HostMonotonicClock + 'static) -> &mut Self { - self.common.monotonic_clock(clock); - self - } - - /// Allow all network addresses accessible to the host. - /// - /// This method will inherit all network addresses meaning that any address - /// can be bound by the guest or connected to by the guest using any - /// protocol. - /// - /// See also [`WasiCtxBuilder::socket_addr_check`]. - pub fn inherit_network(&mut self) -> &mut Self { - self.common.inherit_network(); - self - } - - /// A check that will be called for each socket address that is used. - /// - /// Returning `true` will permit socket connections to the `SocketAddr`, - /// while returning `false` will reject the connection. - pub fn socket_addr_check(&mut self, check: F) -> &mut Self - where - F: Fn(SocketAddr, SocketAddrUse) -> Pin + Send + Sync>> - + Send - + Sync - + 'static, - { - self.common.socket_addr_check(check); - self - } - - /// Allow usage of `wasi:sockets/ip-name-lookup` - /// - /// By default this is disabled. - pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self { - self.common.allow_ip_name_lookup(enable); - self - } - - /// Allow usage of UDP. - /// - /// This is enabled by default, but can be disabled if UDP should be blanket - /// disabled. - pub fn allow_udp(&mut self, enable: bool) -> &mut Self { - self.common.allow_udp(enable); - self - } - - /// Allow usage of TCP - /// - /// This is enabled by default, but can be disabled if TCP should be blanket - /// disabled. - pub fn allow_tcp(&mut self, enable: bool) -> &mut Self { - self.common.allow_tcp(enable); - self - } - - /// Uses the configured context so far to construct the final [`WasiCtx`]. - /// - /// Note that each `WasiCtxBuilder` can only be used to "build" once, and - /// calling this method twice will panic. - /// - /// # Panics - /// - /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be - /// used to create only a single [`WasiCtx`]. Repeated usage of this method - /// is not allowed and should use a second builder instead. - pub fn build(&mut self) -> WasiCtx { - assert!(!self.built); - - let Self { - common: - crate::WasiCtxBuilder { - random, - clocks, - cli, - sockets, - .. - }, - built: _, - .. - } = mem::replace(self, Self::new()); - self.built = true; - - WasiCtx { - cli, - clocks, - random, - sockets, - } - } -} - -/// Per-[`Store`] state which holds state necessary to implement WASI from this -/// crate. -/// -/// This structure is created through [`WasiCtxBuilder`] and is stored within -/// the `T` of [`Store`][`Store`]. Access to the structure is provided -/// through the [`WasiView`](crate::p3::WasiView) trait as an implementation on `T`. -/// -/// Note that this structure itself does not have any accessors, it's here for -/// internal use within the `wasmtime-wasi` crate's implementation of -/// bindgen-generated traits. -/// -/// [`Store`]: wasmtime::Store -/// -/// # Example -/// -/// ``` -/// use wasmtime::component::ResourceTable; -/// use wasmtime_wasi::p3::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; -/// -/// struct MyState { -/// ctx: WasiCtx, -/// table: ResourceTable, -/// } -/// -/// impl WasiView for MyState { -/// fn ctx(&mut self) -> WasiCtxView<'_> { -/// WasiCtxView{ -/// ctx: &mut self.ctx, -/// table: &mut self.table, -/// } -/// } -/// } -/// -/// impl MyState { -/// fn new() -> MyState { -/// let mut wasi = WasiCtxBuilder::new(); -/// wasi.arg("./foo.wasm"); -/// wasi.arg("--help"); -/// wasi.env("FOO", "bar"); -/// -/// MyState { -/// ctx: wasi.build(), -/// table: ResourceTable::default(), -/// } -/// } -/// } -/// ``` -#[derive(Default)] -pub struct WasiCtx { - pub cli: WasiCliCtx, Box>, - pub clocks: WasiClocksCtx, - pub random: WasiRandomCtx, - pub sockets: WasiSocketsCtx, -} - -impl WasiCtx { - /// Convenience function for calling [`WasiCtxBuilder::new`]. - pub fn builder() -> WasiCtxBuilder { - WasiCtxBuilder::new() - } -} diff --git a/crates/wasi/src/p3/filesystem/host.rs b/crates/wasi/src/p3/filesystem/host.rs index bd8b35066c..34e8304ec0 100644 --- a/crates/wasi/src/p3/filesystem/host.rs +++ b/crates/wasi/src/p3/filesystem/host.rs @@ -1,245 +1,329 @@ -use std::sync::Arc; - -use anyhow::{Context as _, anyhow}; -use system_interface::fs::FileIoExt as _; -use tokio::sync::mpsc; -use wasmtime::component::{ - Accessor, AccessorTask, FutureReader, FutureWriter, GuardedFutureReader, GuardedFutureWriter, - GuardedStreamReader, GuardedStreamWriter, Lower, Resource, ResourceTable, StreamReader, -}; - +use crate::DirPerms; +use crate::filesystem::{Descriptor, Dir, File, WasiFilesystem, WasiFilesystemCtxView}; +use crate::p3::DEFAULT_BUFFER_CAPACITY; +use crate::p3::bindings::clocks::wall_clock; use crate::p3::bindings::filesystem::types::{ - Advice, DescriptorFlags, DescriptorStat, DescriptorType, DirectoryEntry, ErrorCode, Filesize, - MetadataHashValue, NewTimestamp, OpenFlags, PathFlags, + self, Advice, DescriptorFlags, DescriptorStat, DescriptorType, DirectoryEntry, ErrorCode, + Filesize, MetadataHashValue, NewTimestamp, OpenFlags, PathFlags, }; -use crate::p3::bindings::filesystem::{preopens, types}; -use crate::p3::filesystem::{ - Descriptor, DirPerms, FilePerms, WasiFilesystem, WasiFilesystemImpl, WasiFilesystemView, +use crate::p3::filesystem::{FilesystemError, FilesystemResult, preopens}; +use crate::{FilePerms, TrappableError}; +use anyhow::Context as _; +use bytes::BytesMut; +use std::io::Cursor; +use system_interface::fs::FileIoExt as _; +use wasmtime::component::{ + Accessor, AccessorTask, FutureReader, FutureWriter, GuardedFutureWriter, GuardedStreamWriter, + Resource, ResourceTable, StreamReader, StreamWriter, }; -use crate::p3::{AbortOnDropHandle, IoTask, ResourceView as _, SpawnExt, TaskTable}; fn get_descriptor<'a>( table: &'a ResourceTable, fd: &'a Resource, -) -> wasmtime::Result<&'a Descriptor> { +) -> FilesystemResult<&'a Descriptor> { table .get(fd) .context("failed to get descriptor resource from table") + .map_err(TrappableError::trap) } -pub struct ReadTask { - io: IoTask, - id: u32, - tasks: Arc>, +fn get_file<'a>( + table: &'a ResourceTable, + fd: &'a Resource, +) -> FilesystemResult<&'a File> { + let file = get_descriptor(table, fd).map(Descriptor::file)??; + Ok(file) } -impl AccessorTask> for ReadTask -where - U: wasmtime::component::HasData, - V: Lower + Send + Sync + 'static, -{ - async fn run(self, store: &Accessor) -> wasmtime::Result<()> { - let res = self.io.run(store).await; - let mut tasks = self.tasks.lock().map_err(|_| anyhow!("lock poisoned"))?; - tasks.remove(self.id); - res +fn get_dir<'a>( + table: &'a ResourceTable, + fd: &'a Resource, +) -> FilesystemResult<&'a Dir> { + let dir = get_descriptor(table, fd).map(Descriptor::dir)??; + Ok(dir) +} + +trait AccessorExt { + fn get_descriptor(&self, fd: &Resource) -> FilesystemResult; + fn get_file(&self, fd: &Resource) -> FilesystemResult; + fn get_dir(&self, fd: &Resource) -> FilesystemResult; + fn get_dir_pair( + &self, + a: &Resource, + b: &Resource, + ) -> FilesystemResult<(Dir, Dir)>; +} + +impl AccessorExt for Accessor { + fn get_descriptor(&self, fd: &Resource) -> FilesystemResult { + self.with(|mut store| { + let fd = get_descriptor(store.get().table, fd)?; + Ok(fd.clone()) + }) + } + + fn get_file(&self, fd: &Resource) -> FilesystemResult { + self.with(|mut store| { + let file = get_file(store.get().table, fd)?; + Ok(file.clone()) + }) + } + + fn get_dir(&self, fd: &Resource) -> FilesystemResult { + self.with(|mut store| { + let dir = get_dir(store.get().table, fd)?; + Ok(dir.clone()) + }) + } + + fn get_dir_pair( + &self, + a: &Resource, + b: &Resource, + ) -> FilesystemResult<(Dir, Dir)> { + self.with(|mut store| { + let table = store.get().table; + let a = get_dir(table, a)?; + let b = get_dir(table, b)?; + Ok((a.clone(), b.clone())) + }) } } -impl types::Host for WasiFilesystemImpl where T: WasiFilesystemView {} +fn systemtime_from(t: wall_clock::Datetime) -> Result { + std::time::SystemTime::UNIX_EPOCH + .checked_add(core::time::Duration::new(t.seconds, t.nanoseconds)) + .ok_or(ErrorCode::Overflow) +} -impl types::HostDescriptorWithStore for WasiFilesystem -where - T: WasiFilesystemView + 'static, -{ - async fn read_via_stream( - store: &Accessor, - fd: Resource, - mut offset: Filesize, - ) -> wasmtime::Result<(StreamReader, FutureReader>)> { - let ((data_tx, data_rx), (res_tx, res_rx)) = store.with(|mut view| { - let instance = view.instance(); - let data = instance - .stream(&mut view) - .context("failed to create stream")?; - let res = instance - .future(&mut view, || Ok(())) - .context("failed to create future")?; - anyhow::Ok((data, res)) - })?; - let data_tx = GuardedStreamWriter::new(store, data_tx); - let data_rx = GuardedStreamReader::new(store, data_rx); - let res_tx = GuardedFutureWriter::new(store, res_tx); - let res_rx = GuardedFutureReader::new(store, res_rx); - - let result = store.with(|mut view| { - let mut binding = view.get(); - let fd = get_descriptor(binding.table(), &fd)?; - anyhow::Ok(match fd.file() { - Ok(f) => { - let (task_tx, task_rx) = mpsc::channel(1); - let f = f.clone(); - let tasks = Arc::clone(&f.tasks); - let task = view.spawn_fn(move |_| async move { - while let Ok(tx) = task_tx.reserve().await { - match f - .spawn_blocking(move |f| { - let mut buf = vec![0; 8096]; - loop { - let res = f.read_at(&mut buf, offset); - if let Err(err) = &res { - if err.kind() == std::io::ErrorKind::Interrupted { - // Try again, continue looping - continue; - } - } - return (res, buf); - } - }) - .await +fn systemtimespec_from(t: NewTimestamp) -> Result, ErrorCode> { + use fs_set_times::SystemTimeSpec; + match t { + NewTimestamp::NoChange => Ok(None), + NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)), + NewTimestamp::Timestamp(st) => { + let st = systemtime_from(st)?; + Ok(Some(SystemTimeSpec::Absolute(st))) + } + } +} + +struct ReadFileTask { + file: File, + offset: u64, + data_tx: StreamWriter, + result_tx: FutureWriter>, +} + +impl AccessorTask> for ReadFileTask { + async fn run(self, store: &Accessor) -> wasmtime::Result<()> { + let mut buf = BytesMut::zeroed(DEFAULT_BUFFER_CAPACITY); + let mut offset = self.offset; + let mut data_tx = GuardedStreamWriter::new(store, self.data_tx); + let result_tx = GuardedFutureWriter::new(store, self.result_tx); + let res = loop { + match self + .file + .run_blocking(move |file| { + let n = file.read_at(&mut buf, offset)?; + buf.truncate(n); + std::io::Result::Ok(buf) + }) + .await + { + Ok(chunk) if chunk.is_empty() => { + break Ok(()); + } + Ok(chunk) => { + let Ok(n) = chunk.len().try_into() else { + break Err(ErrorCode::Overflow); + }; + let Some(n) = offset.checked_add(n) else { + break Err(ErrorCode::Overflow); + }; + offset = n; + buf = data_tx.write_all(Cursor::new(chunk)).await.into_inner(); + if data_tx.is_closed() { + break Ok(()); + } + buf.resize(DEFAULT_BUFFER_CAPACITY, 0); + } + Err(err) => { + break Err(err.into()); + } + } + }; + drop(self.file); + drop(data_tx); + result_tx.write(res).await; + Ok(()) + } +} + +struct ReadDirectoryTask { + dir: Dir, + data_tx: StreamWriter, + result_tx: FutureWriter>, +} + +impl AccessorTask> for ReadDirectoryTask { + async fn run(self, store: &Accessor) -> wasmtime::Result<()> { + let mut data_tx = GuardedStreamWriter::new(store, self.data_tx); + let result_tx = GuardedFutureWriter::new(store, self.result_tx); + let res = loop { + let mut entries = match self.dir.run_blocking(cap_std::fs::Dir::entries).await { + Ok(entries) => entries, + Err(err) => break Err(err.into()), + }; + if let Err(err) = loop { + let Some((res, tail)) = self + .dir + .run_blocking(move |_| entries.next().map(|entry| (entry, entries))) + .await + else { + break Ok(()); + }; + entries = tail; + let entry = match res { + Ok(entry) => entry, + Err(err) => { + // On windows, filter out files like `C:\DumpStack.log.tmp` which we + // can't get full metadata for. + #[cfg(windows)] + { + use windows_sys::Win32::Foundation::{ + ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION, + }; + if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32) + || err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32) { - (Ok(0), ..) => break, - (Ok(n), mut buf) => { - buf.truncate(n); - let Some(n) = - n.try_into().ok().and_then(|n| offset.checked_add(n)) - else { - tx.send(Err(ErrorCode::Overflow)); - break; - }; - offset = n; - tx.send(Ok(buf)); - } - (Err(err), ..) => { - tx.send(Err(err.into())); - break; - } + continue; } } - Ok(()) - }); - let id = { - let mut tasks = tasks.lock().map_err(|_| anyhow!("lock poisoned"))?; - tasks - .push(AbortOnDropHandle(task)) - .context("failed to push task to table")? - }; - Ok((task_rx, id, tasks)) + break Err(err.into()); + } + }; + let meta = match entry.metadata() { + Ok(meta) => meta, + Err(err) => break Err(err.into()), + }; + let Ok(name) = entry.file_name().into_string() else { + break Err(ErrorCode::IllegalByteSequence); + }; + data_tx + .write(Some(DirectoryEntry { + type_: meta.file_type().into(), + name, + })) + .await; + if data_tx.is_closed() { + break Ok(()); } - Err(err) => Err(err), - }) - })?; + } { + break Err(err); + }; + }; + drop(self.dir); + drop(data_tx); + result_tx.write(res).await; + Ok(()) + } +} - match result { - Ok((task_rx, id, tasks)) => { - store.spawn(ReadTask { - io: IoTask { - data: data_tx.into(), - result: res_tx.into(), - rx: task_rx, - }, - id, - tasks, - }); - } - Err(err) => { - let res_tx = FutureWriter::from(res_tx); - store.spawn_fn_box(move |store| { - Box::pin(async move { - res_tx.write(store, Err(err)).await; - Ok(()) - }) - }); +impl types::Host for WasiFilesystemCtxView<'_> { + fn convert_error_code(&mut self, error: FilesystemError) -> wasmtime::Result { + error.downcast() + } +} + +impl types::HostDescriptorWithStore for WasiFilesystem { + async fn read_via_stream( + store: &Accessor, + fd: Resource, + offset: Filesize, + ) -> wasmtime::Result<(StreamReader, FutureReader>)> { + let (file, (data_tx, data_rx), (result_tx, result_rx)) = store.with(|mut store| { + let file = get_file(store.get().table, &fd).cloned()?; + let instance = store.instance(); + let data = instance + .stream(&mut store) + .context("failed to create stream")?; + let result = if !file.perms.contains(FilePerms::READ) { + instance.future(&mut store, || Err(types::ErrorCode::NotPermitted)) + } else { + instance.future(&mut store, || unreachable!()) } + .context("failed to create future")?; + anyhow::Ok((file, data, result)) + })?; + if !file.perms.contains(FilePerms::READ) { + return Ok((data_rx, result_rx)); } - - Ok((data_rx.into(), res_rx.into())) + store.spawn(ReadFileTask { + file, + offset, + data_tx, + result_tx, + }); + Ok((data_rx, result_rx)) } - async fn write_via_stream( + async fn write_via_stream( store: &Accessor, fd: Resource, - data: StreamReader, + mut data: StreamReader, mut offset: Filesize, - ) -> wasmtime::Result> { - let (fd, mut data) = store.with(|mut view| -> wasmtime::Result<_> { - let fd = get_descriptor(view.get().table(), &fd)?.clone(); - Ok((fd, data)) - })?; - let f = match fd.file() { - Ok(f) => f, - Err(err) => return Ok(Err(err)), - }; - if !f.perms.contains(FilePerms::WRITE) { - return Ok(Err(types::ErrorCode::BadDescriptor)); + ) -> FilesystemResult<()> { + let file = store.get_file(&fd)?; + if !file.perms.contains(FilePerms::WRITE) { + return Err(types::ErrorCode::NotPermitted.into()); } - let mut buf = Vec::with_capacity(8096); + let mut buf = Vec::with_capacity(DEFAULT_BUFFER_CAPACITY); while !data.is_closed() { buf = data.read(store, buf).await; - match f - .spawn_blocking(move |f| { - let mut slice = buf.as_slice(); - while !slice.is_empty() { - let n = f.write_at(slice, offset)?; - slice = &slice[n..]; + buf = file + .spawn_blocking(move |file| { + let mut pos = 0; + while pos != buf.len() { + let n = file.write_at(&buf[pos..], offset)?; + pos = pos.saturating_add(n); let n = n.try_into().or(Err(ErrorCode::Overflow))?; offset = offset.checked_add(n).ok_or(ErrorCode::Overflow)?; } - Ok((offset, buf)) + FilesystemResult::Ok(buf) }) - .await - { - Ok((n, b)) => { - offset = n; - buf = b; - buf.clear(); - } - Err(err) => return Ok(Err(err)), - } + .await?; + offset = offset.saturating_add(buf.len() as _); + buf.clear(); } - Ok(Ok(())) + Ok(()) } - async fn append_via_stream( + async fn append_via_stream( store: &Accessor, fd: Resource, - data: StreamReader, - ) -> wasmtime::Result> { - let (fd, mut data) = store.with(|mut view| { - let fd = get_descriptor(view.get().table(), &fd)?.clone(); - anyhow::Ok((fd, data)) - })?; - let f = match fd.file() { - Ok(f) => f, - Err(err) => return Ok(Err(err)), - }; - if !f.perms.contains(FilePerms::WRITE) { - return Ok(Err(types::ErrorCode::BadDescriptor)); + mut data: StreamReader, + ) -> FilesystemResult<()> { + let file = store.get_file(&fd)?; + if !file.perms.contains(FilePerms::WRITE) { + return Err(types::ErrorCode::NotPermitted.into()); } - let mut buf = Vec::with_capacity(8096); + let mut buf = Vec::with_capacity(DEFAULT_BUFFER_CAPACITY); while !data.is_closed() { buf = data.read(store, buf).await; - match f - .spawn_blocking(move |f| { - let mut slice = buf.as_slice(); - loop { - let n = f.append(slice)?; - if slice.len() == n { - return Ok(buf); - } - slice = &slice[n..]; + buf = file + .spawn_blocking(move |file| { + let mut pos = 0; + while pos != buf.len() { + let n = file.append(&buf[pos..])?; + pos = pos.saturating_add(n); } + FilesystemResult::Ok(buf) }) - .await - { - Ok(buf_again) => { - buf = buf_again; - buf.clear(); - } - Err(err) => return Ok(Err(err)), - } + .await?; + buf.clear(); } - Ok(Ok(())) + Ok(()) } async fn advise( @@ -248,53 +332,47 @@ where offset: Filesize, length: Filesize, advice: Advice, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd) - .map(|fd| fd.clone().advise(offset, length, advice)) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let file = store.get_file(&fd)?; + file.advise(offset, length, advice.into()).await?; + Ok(()) } async fn sync_data( store: &Accessor, fd: Resource, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().sync_data()) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let fd = store.get_descriptor(&fd)?; + fd.sync_data().await?; + Ok(()) } async fn get_flags( store: &Accessor, fd: Resource, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().get_flags()) - })?; - Ok(fut.await) + ) -> FilesystemResult { + let fd = store.get_descriptor(&fd)?; + let flags = fd.get_flags().await?; + Ok(flags.into()) } async fn get_type( store: &Accessor, fd: Resource, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().get_type()) - })?; - Ok(fut.await) + ) -> FilesystemResult { + let fd = store.get_descriptor(&fd)?; + let ty = fd.get_type().await?; + Ok(ty.into()) } async fn set_size( store: &Accessor, fd: Resource, size: Filesize, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().set_size(size)) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let file = store.get_file(&fd)?; + file.set_size(size).await?; + Ok(()) } async fn set_times( @@ -302,186 +380,69 @@ where fd: Resource, data_access_timestamp: NewTimestamp, data_modification_timestamp: NewTimestamp, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| { - fd.clone() - .set_times(data_access_timestamp, data_modification_timestamp) - }) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let fd = store.get_descriptor(&fd)?; + let atim = systemtimespec_from(data_access_timestamp)?; + let mtim = systemtimespec_from(data_modification_timestamp)?; + fd.set_times(atim, mtim).await?; + Ok(()) } - async fn read_directory( + async fn read_directory( store: &Accessor, fd: Resource, ) -> wasmtime::Result<( StreamReader, FutureReader>, )> { - let ((data_tx, data_rx), (res_tx, res_rx)) = store.with(|mut view| { - let instance = view.instance(); + let (dir, (data_tx, data_rx), (result_tx, result_rx)) = store.with(|mut store| { + let dir = get_dir(store.get().table, &fd).cloned()?; + let instance = store.instance(); let data = instance - .stream(&mut view) + .stream(&mut store) .context("failed to create stream")?; - let res = instance - .future(&mut view, || Ok(())) - .context("failed to create future")?; - anyhow::Ok((data, res)) - })?; - let data_tx = GuardedStreamWriter::new(store, data_tx); - let data_rx = GuardedStreamReader::new(store, data_rx); - let res_tx = GuardedFutureWriter::new(store, res_tx); - let res_rx = GuardedFutureReader::new(store, res_rx); - - let result = store.with(|mut view| { - let mut binding = view.get(); - let fd = get_descriptor(binding.table(), &fd)?; - anyhow::Ok( - match fd.dir().and_then(|d| { - if !d.perms.contains(DirPerms::READ) { - Err(ErrorCode::NotPermitted) - } else { - Ok(d) - } - }) { - Ok(d) => { - let d = d.clone(); - let tasks = Arc::clone(&d.tasks); - let (task_tx, task_rx) = mpsc::channel(1); - let task = view.spawn_fn(|_| async move { - match d.run_blocking(cap_std::fs::Dir::entries).await { - Ok(mut entries) => { - while let Ok(tx) = task_tx.reserve().await { - match d - .run_blocking(|_| match entries.next()? { - Ok(entry) => { - let meta = match entry.metadata() { - Ok(meta) => meta, - Err(err) => return Some(Err(err.into())), - }; - let Ok(name) = entry.file_name().into_string() - else { - return Some(Err( - ErrorCode::IllegalByteSequence, - )); - }; - Some(Ok(( - Some(DirectoryEntry { - type_: meta.file_type().into(), - name, - }), - entries, - ))) - } - Err(err) => { - // On windows, filter out files like `C:\DumpStack.log.tmp` which we - // can't get full metadata for. - #[cfg(windows)] - { - use windows_sys::Win32::Foundation::{ - ERROR_ACCESS_DENIED, - ERROR_SHARING_VIOLATION, - }; - if err.raw_os_error() - == Some(ERROR_SHARING_VIOLATION as i32) - || err.raw_os_error() - == Some(ERROR_ACCESS_DENIED as i32) - { - return Some(Ok((None, entries))); - } - } - Some(Err(err.into())) - } - }) - .await - { - None => break, - Some(Ok((entry, tail))) => { - if let Some(entry) = entry { - tx.send(Ok(vec![entry])); - } - entries = tail; - } - Some(Err(err)) => { - tx.send(Err(err)); - break; - } - } - } - } - Err(err) => { - _ = task_tx.send(Err(err.into())).await; - } - } - Ok(()) - }); - let id = { - let mut tasks = tasks.lock().map_err(|_| anyhow!("lock poisoned"))?; - tasks - .push(AbortOnDropHandle(task)) - .context("failed to push task to table")? - }; - Ok((task_rx, id, tasks)) - } - Err(err) => Err(err), - }, - ) - })?; - - match result { - Ok((task_rx, id, tasks)) => { - store.spawn(ReadTask { - io: IoTask { - data: data_tx.into(), - result: res_tx.into(), - rx: task_rx, - }, - id, - tasks, - }); - } - Err(err) => { - let res_tx = FutureWriter::from(res_tx); - store.spawn_fn_box(move |store| { - Box::pin(async move { - res_tx.write(store, Err(err)).await; - Ok(()) - }) - }); + let result = if !dir.perms.contains(DirPerms::READ) { + instance.future(&mut store, || Err(types::ErrorCode::NotPermitted)) + } else { + instance.future(&mut store, || unreachable!()) } + .context("failed to create future")?; + anyhow::Ok((dir, data, result)) + })?; + if !dir.perms.contains(DirPerms::READ) { + return Ok((data_rx, result_rx)); } - - Ok((data_rx.into(), res_rx.into())) + store.spawn(ReadDirectoryTask { + dir, + data_tx, + result_tx, + }); + Ok((data_rx, result_rx)) } - async fn sync( - store: &Accessor, - fd: Resource, - ) -> wasmtime::Result> { - let fut = store - .with(|mut view| get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().sync()))?; - Ok(fut.await) + async fn sync(store: &Accessor, fd: Resource) -> FilesystemResult<()> { + let fd = store.get_descriptor(&fd)?; + fd.sync().await?; + Ok(()) } async fn create_directory_at( store: &Accessor, fd: Resource, path: String, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().create_directory_at(path)) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let dir = store.get_dir(&fd)?; + dir.create_directory_at(path).await?; + Ok(()) } async fn stat( store: &Accessor, fd: Resource, - ) -> wasmtime::Result> { - let fut = store - .with(|mut view| get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().stat()))?; - Ok(fut.await) + ) -> FilesystemResult { + let fd = store.get_descriptor(&fd)?; + let stat = fd.stat().await?; + Ok(stat.into()) } async fn stat_at( @@ -489,11 +450,10 @@ where fd: Resource, path_flags: PathFlags, path: String, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().stat_at(path_flags, path)) - })?; - Ok(fut.await) + ) -> FilesystemResult { + let dir = store.get_dir(&fd)?; + let stat = dir.stat_at(path_flags.into(), path).await?; + Ok(stat.into()) } async fn set_times_at( @@ -503,18 +463,13 @@ where path: String, data_access_timestamp: NewTimestamp, data_modification_timestamp: NewTimestamp, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| { - fd.clone().set_times_at( - path_flags, - path, - data_access_timestamp, - data_modification_timestamp, - ) - }) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let dir = store.get_dir(&fd)?; + let atim = systemtimespec_from(data_access_timestamp)?; + let mtim = systemtimespec_from(data_modification_timestamp)?; + dir.set_times_at(path_flags.into(), path, atim, mtim) + .await?; + Ok(()) } async fn link_at( @@ -524,15 +479,12 @@ where old_path: String, new_fd: Resource, new_path: String, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - let new_fd = get_descriptor(view.get().table(), &new_fd).cloned()?; - get_descriptor(view.get().table(), &fd).map(|fd| { - fd.clone() - .link_at(old_path_flags, old_path, new_fd, new_path) - }) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let (old_dir, new_dir) = store.get_dir_pair(&fd, &new_fd)?; + old_dir + .link_at(old_path_flags.into(), old_path, &new_dir, new_path) + .await?; + Ok(()) } async fn open_at( @@ -542,53 +494,43 @@ where path: String, open_flags: OpenFlags, flags: DescriptorFlags, - ) -> wasmtime::Result, ErrorCode>> { - let fut = store.with(|mut view| { - let allow_blocking_current_thread = - view.get().filesystem().allow_blocking_current_thread; - get_descriptor(view.get().table(), &fd).map(|fd| { - fd.clone().open_at( - path_flags, - path, - open_flags, - flags, - allow_blocking_current_thread, - ) - }) + ) -> FilesystemResult> { + let (allow_blocking_current_thread, dir) = store.with(|mut store| { + let store = store.get(); + let dir = get_dir(&store.table, &fd)?; + FilesystemResult::Ok((store.ctx.allow_blocking_current_thread, dir.clone())) })?; - match fut.await { - Ok(fd) => store.with(|mut view| { - let fd = view - .get() - .table() - .push(fd) - .context("failed to push descriptor resource to table")?; - Ok(Ok(fd)) - }), - Err(err) => Ok(Err(err)), - } + let fd = dir + .open_at( + path_flags.into(), + path, + open_flags.into(), + flags.into(), + allow_blocking_current_thread, + ) + .await?; + let fd = store.with(|mut store| store.get().table.push(fd))?; + Ok(fd) } async fn readlink_at( store: &Accessor, fd: Resource, path: String, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().readlink_at(path)) - })?; - Ok(fut.await) + ) -> FilesystemResult { + let dir = store.get_dir(&fd)?; + let path = dir.readlink_at(path).await?; + Ok(path) } async fn remove_directory_at( store: &Accessor, fd: Resource, path: String, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().remove_directory_at(path)) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let dir = store.get_dir(&fd)?; + dir.remove_directory_at(path).await?; + Ok(()) } async fn rename_at( @@ -597,13 +539,10 @@ where old_path: String, new_fd: Resource, new_path: String, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - let new_fd = get_descriptor(view.get().table(), &new_fd).cloned()?; - get_descriptor(view.get().table(), &fd) - .map(|fd| fd.clone().rename_at(old_path, new_fd, new_path)) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let (old_dir, new_dir) = store.get_dir_pair(&fd, &new_fd)?; + old_dir.rename_at(old_path, &new_dir, new_path).await?; + Ok(()) } async fn symlink_at( @@ -611,23 +550,20 @@ where fd: Resource, old_path: String, new_path: String, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd) - .map(|fd| fd.clone().symlink_at(old_path, new_path)) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let dir = store.get_dir(&fd)?; + dir.symlink_at(old_path, new_path).await?; + Ok(()) } async fn unlink_file_at( store: &Accessor, fd: Resource, path: String, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().unlink_file_at(path)) - })?; - Ok(fut.await) + ) -> FilesystemResult<()> { + let dir = store.get_dir(&fd)?; + dir.unlink_file_at(path).await?; + Ok(()) } async fn is_same_object( @@ -635,21 +571,22 @@ where fd: Resource, other: Resource, ) -> wasmtime::Result { - let fut = store.with(|mut view| { - let other = get_descriptor(view.get().table(), &other).cloned()?; - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().is_same_object(other)) + let (fd, other) = store.with(|mut store| { + let table = store.get().table; + let fd = get_descriptor(table, &fd)?.clone(); + let other = get_descriptor(table, &other)?.clone(); + anyhow::Ok((fd, other)) })?; - fut.await + fd.is_same_object(&other).await } async fn metadata_hash( store: &Accessor, fd: Resource, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd).map(|fd| fd.clone().metadata_hash()) - })?; - Ok(fut.await) + ) -> FilesystemResult { + let fd = store.get_descriptor(&fd)?; + let meta = fd.metadata_hash().await?; + Ok(meta.into()) } async fn metadata_hash_at( @@ -657,41 +594,24 @@ where fd: Resource, path_flags: PathFlags, path: String, - ) -> wasmtime::Result> { - let fut = store.with(|mut view| { - get_descriptor(view.get().table(), &fd) - .map(|fd| fd.clone().metadata_hash_at(path_flags, path)) - })?; - Ok(fut.await) + ) -> FilesystemResult { + let dir = store.get_dir(&fd)?; + let meta = dir.metadata_hash_at(path_flags.into(), path).await?; + Ok(meta.into()) } } -impl types::HostDescriptor for WasiFilesystemImpl -where - T: WasiFilesystemView, -{ - fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { - self.table() - .delete(rep) +impl types::HostDescriptor for WasiFilesystemCtxView<'_> { + fn drop(&mut self, fd: Resource) -> wasmtime::Result<()> { + self.table + .delete(fd) .context("failed to delete descriptor resource from table")?; Ok(()) } } -impl preopens::Host for WasiFilesystemImpl -where - T: WasiFilesystemView, -{ +impl preopens::Host for WasiFilesystemCtxView<'_> { fn get_directories(&mut self) -> wasmtime::Result, String)>> { - let preopens = self.filesystem().preopens.clone(); - let mut results = Vec::with_capacity(preopens.len()); - for (dir, name) in preopens { - let fd = self - .table() - .push(Descriptor::Dir(dir)) - .with_context(|| format!("failed to push preopen {name}"))?; - results.push((fd, name)); - } - Ok(results) + self.get_directories() } } diff --git a/crates/wasi/src/p3/filesystem/mod.rs b/crates/wasi/src/p3/filesystem/mod.rs index b106cc0944..864ca56980 100644 --- a/crates/wasi/src/p3/filesystem/mod.rs +++ b/crates/wasi/src/p3/filesystem/mod.rs @@ -1,152 +1,34 @@ -use crate::filesystem::{DirPerms, FilePerms, OpenMode}; -use crate::p3::bindings::clocks::wall_clock; -use crate::p3::bindings::filesystem; -use crate::p3::bindings::filesystem::types::{ - Advice, DescriptorFlags, DescriptorStat, DescriptorType, ErrorCode, MetadataHashValue, - NewTimestamp, OpenFlags, PathFlags, -}; -use crate::p3::{ResourceView, TaskTable}; -use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking}; -use cap_fs_ext::{FileTypeExt as _, MetadataExt as _}; -use cap_std::ambient_authority; -use std::collections::hash_map; -use std::path::Path; -use std::sync::Arc; -use tracing::debug; -use wasmtime::component::{HasData, Linker, ResourceTable}; - mod host; -#[repr(transparent)] -pub struct WasiFilesystemImpl(pub T); - -impl WasiFilesystemView for &mut T { - fn filesystem(&self) -> &WasiFilesystemCtx { - (**self).filesystem() - } -} - -impl WasiFilesystemView for WasiFilesystemImpl { - fn filesystem(&self) -> &WasiFilesystemCtx { - self.0.filesystem() - } -} - -impl ResourceView for WasiFilesystemImpl { - fn table(&mut self) -> &mut ResourceTable { - self.0.table() - } -} - -pub trait WasiFilesystemView: ResourceView + Send { - fn filesystem(&self) -> &WasiFilesystemCtx; -} +use crate::TrappableError; +use crate::filesystem::{WasiFilesystem, WasiFilesystemView}; +use crate::p3::bindings::filesystem::{preopens, types}; +use wasmtime::component::Linker; -#[derive(Clone, Default)] -pub struct WasiFilesystemCtx { - pub preopens: Vec<(Dir, String)>, - pub allow_blocking_current_thread: bool, -} - -impl WasiFilesystemCtx { - /// Configures a "preopened directory" to be available to WebAssembly. - /// - /// By default WebAssembly does not have access to the filesystem because - /// there are no preopened directories. All filesystem operations, such as - /// opening a file, are done through a preexisting handle. This means that - /// to provide WebAssembly access to a directory it must be configured - /// through this API. - /// - /// WASI will also prevent access outside of files provided here. For - /// example `..` can't be used to traverse up from the `host_path` provided here - /// to the containing directory. - /// - /// * `host_path` - a path to a directory on the host to open and make - /// accessible to WebAssembly. Note that the name of this directory in the - /// guest is configured with `guest_path` below. - /// * `guest_path` - the name of the preopened directory from WebAssembly's - /// perspective. Note that this does not need to match the host's name for - /// the directory. - /// * `dir_perms` - this is the permissions that wasm will have to operate on - /// `guest_path`. This can be used, for example, to provide readonly access to a - /// directory. - /// * `file_perms` - similar to `dir_perms` but corresponds to the maximum set - /// of permissions that can be used for any file in this directory. - /// - /// # Errors - /// - /// This method will return an error if `host_path` cannot be opened. - /// - /// # Examples - /// - /// ``` - /// use wasmtime_wasi::{p2::WasiCtxBuilder, DirPerms, FilePerms}; - /// - /// # fn main() {} - /// # fn foo() -> wasmtime::Result<()> { - /// let mut wasi = WasiCtxBuilder::new(); - /// - /// // Make `./host-directory` available in the guest as `.` - /// wasi.preopened_dir("./host-directory", ".", DirPerms::all(), FilePerms::all()); - /// - /// // Make `./readonly` available in the guest as `./ro` - /// wasi.preopened_dir("./readonly", "./ro", DirPerms::READ, FilePerms::READ); - /// # Ok(()) - /// # } - /// ``` - pub fn preopened_dir( - &mut self, - host_path: impl AsRef, - guest_path: impl Into, - dir_perms: DirPerms, - file_perms: FilePerms, - ) -> wasmtime::Result<&mut Self> { - let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?; - let mut open_mode = OpenMode::empty(); - if dir_perms.contains(DirPerms::READ) { - open_mode |= OpenMode::READ; - } - if dir_perms.contains(DirPerms::MUTATE) { - open_mode |= OpenMode::WRITE; - } - self.preopens.push(( - Dir::new( - dir, - dir_perms, - file_perms, - open_mode, - self.allow_blocking_current_thread, - ), - guest_path.into(), - )); - Ok(self) - } -} +pub type FilesystemResult = Result; +pub type FilesystemError = TrappableError; /// Add all WASI interfaces from this module into the `linker` provided. /// -/// This function will add the `async` variant of all interfaces into the -/// [`Linker`] provided. By `async` this means that this function is only -/// compatible with [`Config::async_support(true)`][async]. For embeddings with -/// async support disabled see [`add_to_linker_sync`] instead. -/// -/// This function will add all interfaces implemented by this crate to the -/// [`Linker`], which corresponds to the `wasi:filesystem/imports` world supported by -/// this crate. +/// This function will add all interfaces implemented by this module to the +/// [`Linker`], which corresponds to the `wasi:sockets/imports` world supported by +/// this module. /// -/// [async]: wasmtime::Config::async_support +/// This is low-level API for advanced use cases, +/// [`wasmtime_wasi::p3::add_to_linker`](crate::p3::add_to_linker) can be used instead +/// to add *all* wasip3 interfaces (including the ones from this module) to the `linker`. /// /// # Example /// /// ``` /// use wasmtime::{Engine, Result, Store, Config}; -/// use wasmtime::component::{ResourceTable, Linker}; -/// use wasmtime_wasi::p3::filesystem::{WasiFilesystemView, WasiFilesystemCtx}; -/// use wasmtime_wasi::p3::ResourceView; +/// use wasmtime::component::{Linker, ResourceTable}; +/// use wasmtime_wasi::filesystem::{WasiFilesystemCtx, WasiFilesystemCtxView, WasiFilesystemView}; /// /// fn main() -> Result<()> { /// let mut config = Config::new(); /// config.async_support(true); +/// config.wasm_component_model_async(true); /// let engine = Engine::new(&config)?; /// /// let mut linker = Linker::::new(&engine); @@ -155,10 +37,7 @@ impl WasiFilesystemCtx { /// /// let mut store = Store::new( /// &engine, -/// MyState { -/// filesystem: WasiFilesystemCtx::default(), -/// table: ResourceTable::default(), -/// }, +/// MyState::default(), /// ); /// /// // ... use `linker` to instantiate within `store` ... @@ -166,936 +45,242 @@ impl WasiFilesystemCtx { /// Ok(()) /// } /// +/// #[derive(Default)] /// struct MyState { /// filesystem: WasiFilesystemCtx, /// table: ResourceTable, /// } /// -/// impl ResourceView for MyState { -/// fn table(&mut self) -> &mut ResourceTable { &mut self.table } -/// } -/// /// impl WasiFilesystemView for MyState { -/// fn filesystem(&self) -> &WasiFilesystemCtx { &self.filesystem } +/// fn filesystem(&mut self) -> WasiFilesystemCtxView<'_> { +/// WasiFilesystemCtxView { +/// ctx: &mut self.filesystem, +/// table: &mut self.table, +/// } +/// } /// } /// ``` -pub fn add_to_linker( - linker: &mut Linker, -) -> wasmtime::Result<()> { - filesystem::types::add_to_linker::<_, WasiFilesystem>(linker, |x| WasiFilesystemImpl(x))?; - filesystem::preopens::add_to_linker::<_, WasiFilesystem>(linker, |x| WasiFilesystemImpl(x))?; +pub fn add_to_linker(linker: &mut Linker) -> wasmtime::Result<()> +where + T: WasiFilesystemView + 'static, +{ + types::add_to_linker::<_, WasiFilesystem>(linker, T::filesystem)?; + preopens::add_to_linker::<_, WasiFilesystem>(linker, T::filesystem)?; Ok(()) } -struct WasiFilesystem(T); - -impl HasData for WasiFilesystem { - type Data<'a> = WasiFilesystemImpl<&'a mut T>; -} - -fn datetime_from(t: std::time::SystemTime) -> wall_clock::Datetime { - // FIXME make this infallible or handle errors properly - wall_clock::Datetime::try_from(cap_std::time::SystemTime::from_std(t)).unwrap() -} - -fn systemtime_from(t: wall_clock::Datetime) -> Result { - std::time::SystemTime::UNIX_EPOCH - .checked_add(core::time::Duration::new(t.seconds, t.nanoseconds)) - .ok_or(ErrorCode::Overflow) -} - -fn systemtimespec_from(t: NewTimestamp) -> Result, ErrorCode> { - use fs_set_times::SystemTimeSpec; - match t { - NewTimestamp::NoChange => Ok(None), - NewTimestamp::Now => Ok(Some(SystemTimeSpec::SymbolicNow)), - NewTimestamp::Timestamp(st) => { - let st = systemtime_from(st)?; - Ok(Some(SystemTimeSpec::Absolute(st))) - } +impl<'a> From<&'a std::io::Error> for types::ErrorCode { + fn from(err: &'a std::io::Error) -> Self { + crate::filesystem::ErrorCode::from(err).into() } } -impl From for DescriptorType { - fn from(ft: cap_std::fs::FileType) -> Self { - if ft.is_dir() { - DescriptorType::Directory - } else if ft.is_symlink() { - DescriptorType::SymbolicLink - } else if ft.is_block_device() { - DescriptorType::BlockDevice - } else if ft.is_char_device() { - DescriptorType::CharacterDevice - } else if ft.is_file() { - DescriptorType::RegularFile - } else { - DescriptorType::Unknown - } - } -} - -impl From for DescriptorStat { - fn from(meta: cap_std::fs::Metadata) -> Self { - Self { - type_: meta.file_type().into(), - link_count: meta.nlink(), - size: meta.len(), - data_access_timestamp: meta.accessed().map(|t| datetime_from(t.into_std())).ok(), - data_modification_timestamp: meta.modified().map(|t| datetime_from(t.into_std())).ok(), - status_change_timestamp: meta.created().map(|t| datetime_from(t.into_std())).ok(), - } +impl From for types::ErrorCode { + fn from(err: std::io::Error) -> Self { + Self::from(&err) } } -impl From<&cap_std::fs::Metadata> for MetadataHashValue { - fn from(meta: &cap_std::fs::Metadata) -> Self { - use cap_fs_ext::MetadataExt; - // Without incurring any deps, std provides us with a 64 bit hash - // function: - use std::hash::Hasher; - // Note that this means that the metadata hash (which becomes a preview1 ino) may - // change when a different rustc release is used to build this host implementation: - let mut hasher = hash_map::DefaultHasher::new(); - hasher.write_u64(meta.dev()); - hasher.write_u64(meta.ino()); - let lower = hasher.finish(); - // MetadataHashValue has a pair of 64-bit members for representing a - // single 128-bit number. However, we only have 64 bits of entropy. To - // synthesize the upper 64 bits, lets xor the lower half with an arbitrary - // constant, in this case the 64 bit integer corresponding to the IEEE - // double representation of (a number as close as possible to) pi. - // This seems better than just repeating the same bits in the upper and - // lower parts outright, which could make folks wonder if the struct was - // mangled in the ABI, or worse yet, lead to consumers of this interface - // expecting them to be equal. - let upper = lower ^ 4614256656552045848u64; - Self { lower, upper } +impl From for FilesystemError { + fn from(error: std::io::Error) -> Self { + types::ErrorCode::from(error).into() } } -#[cfg(unix)] -fn from_raw_os_error(err: Option) -> Option { - use rustix::io::Errno as RustixErrno; - if err.is_none() { - return None; +impl From for types::ErrorCode { + fn from(error: crate::filesystem::ErrorCode) -> Self { + match error { + crate::filesystem::ErrorCode::Access => Self::Access, + crate::filesystem::ErrorCode::Already => Self::Already, + crate::filesystem::ErrorCode::BadDescriptor => Self::BadDescriptor, + crate::filesystem::ErrorCode::Busy => Self::Busy, + crate::filesystem::ErrorCode::Exist => Self::Exist, + crate::filesystem::ErrorCode::FileTooLarge => Self::FileTooLarge, + crate::filesystem::ErrorCode::IllegalByteSequence => Self::IllegalByteSequence, + crate::filesystem::ErrorCode::InProgress => Self::InProgress, + crate::filesystem::ErrorCode::Interrupted => Self::Interrupted, + crate::filesystem::ErrorCode::Invalid => Self::Invalid, + crate::filesystem::ErrorCode::Io => Self::Io, + crate::filesystem::ErrorCode::IsDirectory => Self::IsDirectory, + crate::filesystem::ErrorCode::Loop => Self::Loop, + crate::filesystem::ErrorCode::TooManyLinks => Self::TooManyLinks, + crate::filesystem::ErrorCode::NameTooLong => Self::NameTooLong, + crate::filesystem::ErrorCode::NoEntry => Self::NoEntry, + crate::filesystem::ErrorCode::InsufficientMemory => Self::InsufficientMemory, + crate::filesystem::ErrorCode::InsufficientSpace => Self::InsufficientSpace, + crate::filesystem::ErrorCode::NotDirectory => Self::NotDirectory, + crate::filesystem::ErrorCode::NotEmpty => Self::NotEmpty, + crate::filesystem::ErrorCode::Unsupported => Self::Unsupported, + crate::filesystem::ErrorCode::Overflow => Self::Overflow, + crate::filesystem::ErrorCode::NotPermitted => Self::NotPermitted, + crate::filesystem::ErrorCode::Pipe => Self::Pipe, + crate::filesystem::ErrorCode::InvalidSeek => Self::InvalidSeek, + } } - Some(match RustixErrno::from_raw_os_error(err.unwrap()) { - RustixErrno::PIPE => ErrorCode::Pipe, - RustixErrno::PERM => ErrorCode::NotPermitted, - RustixErrno::NOENT => ErrorCode::NoEntry, - RustixErrno::NOMEM => ErrorCode::InsufficientMemory, - RustixErrno::IO => ErrorCode::Io, - RustixErrno::BADF => ErrorCode::BadDescriptor, - RustixErrno::BUSY => ErrorCode::Busy, - RustixErrno::ACCESS => ErrorCode::Access, - RustixErrno::NOTDIR => ErrorCode::NotDirectory, - RustixErrno::ISDIR => ErrorCode::IsDirectory, - RustixErrno::INVAL => ErrorCode::Invalid, - RustixErrno::EXIST => ErrorCode::Exist, - RustixErrno::FBIG => ErrorCode::FileTooLarge, - RustixErrno::NOSPC => ErrorCode::InsufficientSpace, - RustixErrno::SPIPE => ErrorCode::InvalidSeek, - RustixErrno::MLINK => ErrorCode::TooManyLinks, - RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong, - RustixErrno::NOTEMPTY => ErrorCode::NotEmpty, - RustixErrno::LOOP => ErrorCode::Loop, - RustixErrno::OVERFLOW => ErrorCode::Overflow, - RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence, - RustixErrno::NOTSUP => ErrorCode::Unsupported, - RustixErrno::ALREADY => ErrorCode::Already, - RustixErrno::INPROGRESS => ErrorCode::InProgress, - RustixErrno::INTR => ErrorCode::Interrupted, - - // On some platforms, these have the same value as other errno values. - #[allow(unreachable_patterns, reason = "see comment")] - RustixErrno::OPNOTSUPP => ErrorCode::Unsupported, - - _ => return None, - }) } -#[cfg(windows)] -fn from_raw_os_error(raw_os_error: Option) -> Option { - use windows_sys::Win32::Foundation; - Some(match raw_os_error.map(|code| code as u32) { - Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry, - Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry, - Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access, - Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access, - Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted, - Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor, - Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry, - Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory, - Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory, - Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty, - Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy, - Some(Foundation::ERROR_BUSY) => ErrorCode::Busy, - Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported, - Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist, - Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe, - Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong, - Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid, - Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid, - Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory, - Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist, - Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop, - Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory, - _ => return None, - }) -} - -impl<'a> From<&'a std::io::Error> for ErrorCode { - fn from(err: &'a std::io::Error) -> ErrorCode { - match from_raw_os_error(err.raw_os_error()) { - Some(errno) => errno, - None => { - debug!("unknown raw os error: {err}"); - match err.kind() { - std::io::ErrorKind::NotFound => ErrorCode::NoEntry, - std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted, - std::io::ErrorKind::AlreadyExists => ErrorCode::Exist, - std::io::ErrorKind::InvalidInput => ErrorCode::Invalid, - _ => ErrorCode::Io, - } - } - } +impl From for FilesystemError { + fn from(error: crate::filesystem::ErrorCode) -> Self { + types::ErrorCode::from(error).into() } } -impl From for ErrorCode { - fn from(err: std::io::Error) -> ErrorCode { - ErrorCode::from(&err) +impl From for FilesystemError { + fn from(error: wasmtime::component::ResourceTableError) -> Self { + Self::trap(error) } } -impl From for system_interface::fs::Advice { - fn from(advice: Advice) -> Self { +impl From for system_interface::fs::Advice { + fn from(advice: types::Advice) -> Self { match advice { - Advice::Normal => Self::Normal, - Advice::Sequential => Self::Sequential, - Advice::Random => Self::Random, - Advice::WillNeed => Self::WillNeed, - Advice::DontNeed => Self::DontNeed, - Advice::NoReuse => Self::NoReuse, + types::Advice::Normal => Self::Normal, + types::Advice::Sequential => Self::Sequential, + types::Advice::Random => Self::Random, + types::Advice::WillNeed => Self::WillNeed, + types::Advice::DontNeed => Self::DontNeed, + types::Advice::NoReuse => Self::NoReuse, } } } -#[derive(Clone)] -pub enum Descriptor { - File(File), - Dir(Dir), -} - -impl Descriptor { - pub fn file(&self) -> Result<&File, ErrorCode> { - match self { - Self::File(f) => Ok(f), - Self::Dir(_) => Err(ErrorCode::BadDescriptor), - } - } - - pub fn dir(&self) -> Result<&Dir, ErrorCode> { - match self { - Self::Dir(d) => Ok(d), - Self::File(_) => Err(ErrorCode::NotDirectory), - } - } - - pub fn is_file(&self) -> bool { - match self { - Self::File(_) => true, - Self::Dir(_) => false, - } - } - - pub fn is_dir(&self) -> bool { - match self { - Self::File(_) => false, - Self::Dir(_) => true, - } - } - - async fn get_metadata(&self) -> std::io::Result { - match self { - Self::File(f) => { - // No permissions check on metadata: if opened, allowed to stat it - f.run_blocking(|f| f.metadata()).await - } - Self::Dir(d) => { - // No permissions check on metadata: if opened, allowed to stat it - d.run_blocking(|d| d.dir_metadata()).await - } - } - } - - async fn advise(self, offset: u64, len: u64, advice: Advice) -> Result<(), ErrorCode> { - use system_interface::fs::FileIoExt; - - let f = self.file()?; - f.run_blocking(move |f| f.advise(offset, len, advice.into())) - .await?; - Ok(()) - } - - async fn sync_data(self) -> Result<(), ErrorCode> { - match self { - Self::File(f) => { - match f.run_blocking(|f| f.sync_data()).await { - Ok(()) => Ok(()), - // On windows, `sync_data` uses `FileFlushBuffers` which fails with - // `ERROR_ACCESS_DENIED` if the file is not upen for writing. Ignore - // this error, for POSIX compatibility. - #[cfg(windows)] - Err(err) - if err.raw_os_error() - == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) => - { - Ok(()) - } - Err(err) => Err(err.into()), - } - } - Self::Dir(d) => { - d.run_blocking(|d| { - let d = d.open(std::path::Component::CurDir)?; - d.sync_data()?; - Ok(()) - }) - .await - } - } - } - - async fn get_flags(self) -> Result { - use system_interface::fs::{FdFlags, GetSetFdFlags}; - - fn get_from_fdflags(flags: FdFlags) -> DescriptorFlags { - let mut out = DescriptorFlags::empty(); - if flags.contains(FdFlags::DSYNC) { - out |= DescriptorFlags::REQUESTED_WRITE_SYNC; - } - if flags.contains(FdFlags::RSYNC) { - out |= DescriptorFlags::DATA_INTEGRITY_SYNC; - } - if flags.contains(FdFlags::SYNC) { - out |= DescriptorFlags::FILE_INTEGRITY_SYNC; - } - out - } - match self { - Self::File(f) => { - let flags = f.run_blocking(|f| f.get_fd_flags()).await?; - let mut flags = get_from_fdflags(flags); - if f.open_mode.contains(OpenMode::READ) { - flags |= DescriptorFlags::READ; - } - if f.open_mode.contains(OpenMode::WRITE) { - flags |= DescriptorFlags::WRITE; - } - Ok(flags) - } - Self::Dir(d) => { - let flags = d.run_blocking(|d| d.get_fd_flags()).await?; - let mut flags = get_from_fdflags(flags); - if d.open_mode.contains(OpenMode::READ) { - flags |= DescriptorFlags::READ; - } - if d.open_mode.contains(OpenMode::WRITE) { - flags |= DescriptorFlags::MUTATE_DIRECTORY; - } - Ok(flags) - } - } - } - - async fn get_type(self) -> Result { - match self { - Self::File(f) => { - let meta = f.run_blocking(|f| f.metadata()).await?; - Ok(meta.file_type().into()) - } - Self::Dir(_) => Ok(DescriptorType::Directory), - } - } - - async fn set_size(self, size: u64) -> Result<(), ErrorCode> { - let f = self.file()?; - if !f.perms.contains(FilePerms::WRITE) { - return Err(ErrorCode::NotPermitted); - } - f.run_blocking(move |f| f.set_len(size)).await?; - Ok(()) - } - - async fn set_times(self, atim: NewTimestamp, mtim: NewTimestamp) -> Result<(), ErrorCode> { - use fs_set_times::SetTimes as _; - - match self { - Self::File(f) => { - if !f.perms.contains(FilePerms::WRITE) { - return Err(ErrorCode::NotPermitted); - } - let atim = systemtimespec_from(atim)?; - let mtim = systemtimespec_from(mtim)?; - f.run_blocking(|f| f.set_times(atim, mtim)).await?; - Ok(()) - } - Self::Dir(d) => { - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted); - } - let atim = systemtimespec_from(atim)?; - let mtim = systemtimespec_from(mtim)?; - d.run_blocking(|d| d.set_times(atim, mtim)).await?; - Ok(()) - } - } - } - - async fn sync(self) -> Result<(), ErrorCode> { - match self { - Self::File(f) => { - match f.run_blocking(|f| f.sync_all()).await { - Ok(()) => Ok(()), - // On windows, `sync_data` uses `FileFlushBuffers` which fails with - // `ERROR_ACCESS_DENIED` if the file is not upen for writing. Ignore - // this error, for POSIX compatibility. - #[cfg(windows)] - Err(err) - if err.raw_os_error() - == Some(windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED as _) => - { - Ok(()) - } - Err(err) => Err(err.into()), - } - } - Self::Dir(d) => { - d.run_blocking(|d| { - let d = d.open(std::path::Component::CurDir)?; - d.sync_all()?; - Ok(()) - }) - .await - } +impl From for crate::filesystem::OpenFlags { + fn from(flags: types::OpenFlags) -> Self { + let mut out = Self::empty(); + if flags.contains(types::OpenFlags::CREATE) { + out |= Self::CREATE; } - } - - async fn create_directory_at(self, path: String) -> Result<(), ErrorCode> { - let d = self.dir()?; - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted); + if flags.contains(types::OpenFlags::DIRECTORY) { + out |= Self::DIRECTORY; } - d.run_blocking(move |d| d.create_dir(&path)).await?; - Ok(()) - } - - async fn stat(self) -> Result { - match self { - Self::File(f) => { - // No permissions check on stat: if opened, allowed to stat it - let meta = f.run_blocking(|f| f.metadata()).await?; - Ok(meta.into()) - } - Self::Dir(d) => { - // No permissions check on stat: if opened, allowed to stat it - let meta = d.run_blocking(|d| d.dir_metadata()).await?; - Ok(meta.into()) - } - } - } - - async fn stat_at( - self, - path_flags: PathFlags, - path: String, - ) -> Result { - let d = self.dir()?; - if !d.perms.contains(DirPerms::READ) { - return Err(ErrorCode::NotPermitted); - } - - let meta = if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - d.run_blocking(move |d| d.metadata(&path)).await? - } else { - d.run_blocking(move |d| d.symlink_metadata(&path)).await? - }; - Ok(meta.into()) - } - - async fn set_times_at( - self, - path_flags: PathFlags, - path: String, - atim: NewTimestamp, - mtim: NewTimestamp, - ) -> Result<(), ErrorCode> { - use cap_fs_ext::DirExt as _; - - let d = self.dir()?; - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted); + if flags.contains(types::OpenFlags::EXCLUSIVE) { + out |= Self::EXCLUSIVE; } - let atim = systemtimespec_from(atim)?; - let mtim = systemtimespec_from(mtim)?; - if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - d.run_blocking(move |d| { - d.set_times( - &path, - atim.map(cap_fs_ext::SystemTimeSpec::from_std), - mtim.map(cap_fs_ext::SystemTimeSpec::from_std), - ) - }) - .await?; - } else { - d.run_blocking(move |d| { - d.set_symlink_times( - &path, - atim.map(cap_fs_ext::SystemTimeSpec::from_std), - mtim.map(cap_fs_ext::SystemTimeSpec::from_std), - ) - }) - .await?; + if flags.contains(types::OpenFlags::TRUNCATE) { + out |= Self::TRUNCATE; } - Ok(()) + out } +} - async fn link_at( - self, - old_path_flags: PathFlags, - old_path: String, - new_descriptor: Self, - new_path: String, - ) -> Result<(), ErrorCode> { - let old_dir = self.dir()?; - if !old_dir.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted); +impl From for crate::filesystem::PathFlags { + fn from(flags: types::PathFlags) -> Self { + let mut out = Self::empty(); + if flags.contains(types::PathFlags::SYMLINK_FOLLOW) { + out |= Self::SYMLINK_FOLLOW; } - let new_dir = new_descriptor.dir()?; - if !new_dir.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted); - } - if old_path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - return Err(ErrorCode::Invalid); - } - let new_dir_handle = Arc::clone(&new_dir.dir); - old_dir - .run_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path)) - .await?; - Ok(()) + out } +} - async fn open_at( - self, - path_flags: PathFlags, - path: String, - oflags: OpenFlags, - flags: DescriptorFlags, - allow_blocking_current_thread: bool, - ) -> Result { - use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt}; - use system_interface::fs::{FdFlags, GetSetFdFlags}; - - let d = self.dir()?; - if !d.perms.contains(DirPerms::READ) { - return Err(ErrorCode::NotPermitted); - } - - if !d.perms.contains(DirPerms::MUTATE) { - if oflags.contains(OpenFlags::CREATE) || oflags.contains(OpenFlags::TRUNCATE) { - return Err(ErrorCode::NotPermitted); - } - if flags.contains(DescriptorFlags::WRITE) { - return Err(ErrorCode::NotPermitted); - } - } - - // Track whether we are creating file, for permission check: - let mut create = false; - // Track open mode, for permission check and recording in created descriptor: - let mut open_mode = OpenMode::empty(); - // Construct the OpenOptions to give the OS: - let mut opts = cap_std::fs::OpenOptions::new(); - opts.maybe_dir(true); - - if oflags.contains(OpenFlags::CREATE) { - if oflags.contains(OpenFlags::EXCLUSIVE) { - opts.create_new(true); - } else { - opts.create(true); - } - create = true; - opts.write(true); - open_mode |= OpenMode::WRITE; - } - - if oflags.contains(OpenFlags::TRUNCATE) { - opts.truncate(true).write(true); - } - if flags.contains(DescriptorFlags::READ) { - opts.read(true); - open_mode |= OpenMode::READ; - } - if flags.contains(DescriptorFlags::WRITE) { - opts.write(true); - open_mode |= OpenMode::WRITE; - } else { - // If not opened write, open read. This way the OS lets us open - // the file, but we can use perms to reject use of the file later. - opts.read(true); - open_mode |= OpenMode::READ; - } - if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - opts.follow(FollowSymlinks::Yes); - } else { - opts.follow(FollowSymlinks::No); - } - - // These flags are not yet supported in cap-std: - if flags.contains(DescriptorFlags::FILE_INTEGRITY_SYNC) - || flags.contains(DescriptorFlags::DATA_INTEGRITY_SYNC) - || flags.contains(DescriptorFlags::REQUESTED_WRITE_SYNC) - { - return Err(ErrorCode::Unsupported); +impl From for types::DescriptorFlags { + fn from(flags: crate::filesystem::DescriptorFlags) -> Self { + let mut out = Self::empty(); + if flags.contains(crate::filesystem::DescriptorFlags::READ) { + out |= Self::READ; } - - if oflags.contains(OpenFlags::DIRECTORY) { - if oflags.contains(OpenFlags::CREATE) - || oflags.contains(OpenFlags::EXCLUSIVE) - || oflags.contains(OpenFlags::TRUNCATE) - { - return Err(ErrorCode::Invalid); - } + if flags.contains(crate::filesystem::DescriptorFlags::WRITE) { + out |= Self::WRITE; } - - // Now enforce this WasiCtx's permissions before letting the OS have - // its shot: - if !d.perms.contains(DirPerms::MUTATE) && create { - return Err(ErrorCode::NotPermitted); + if flags.contains(crate::filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) { + out |= Self::FILE_INTEGRITY_SYNC; } - if !d.file_perms.contains(FilePerms::WRITE) && open_mode.contains(OpenMode::WRITE) { - return Err(ErrorCode::NotPermitted); + if flags.contains(crate::filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) { + out |= Self::DATA_INTEGRITY_SYNC; } - - // Represents each possible outcome from the spawn_blocking operation. - // This makes sure we don't have to give spawn_blocking any way to - // manipulate the table. - enum OpenResult { - Dir(cap_std::fs::Dir), - File(cap_std::fs::File), - NotDir, + if flags.contains(crate::filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) { + out |= Self::REQUESTED_WRITE_SYNC; } - - let opened = d - .run_blocking::<_, std::io::Result>(move |d| { - let mut opened = d.open_with(&path, &opts)?; - if opened.metadata()?.is_dir() { - Ok(OpenResult::Dir(cap_std::fs::Dir::from_std_file( - opened.into_std(), - ))) - } else if oflags.contains(OpenFlags::DIRECTORY) { - Ok(OpenResult::NotDir) - } else { - // FIXME cap-std needs a nonblocking open option so that files reads and writes - // are nonblocking. Instead we set it after opening here: - let set_fd_flags = opened.new_set_fd_flags(FdFlags::NONBLOCK)?; - opened.set_fd_flags(set_fd_flags)?; - Ok(OpenResult::File(opened)) - } - }) - .await?; - - match opened { - OpenResult::Dir(dir) => Ok(Self::Dir(Dir::new( - dir, - d.perms, - d.file_perms, - open_mode, - allow_blocking_current_thread, - ))), - - OpenResult::File(file) => Ok(Self::File(File::new( - file, - d.file_perms, - open_mode, - allow_blocking_current_thread, - ))), - - OpenResult::NotDir => Err(ErrorCode::NotDirectory), + if flags.contains(crate::filesystem::DescriptorFlags::MUTATE_DIRECTORY) { + out |= Self::MUTATE_DIRECTORY; } + out } +} - async fn readlink_at(self, path: String) -> Result { - let d = self.dir()?; - if !d.perms.contains(DirPerms::READ) { - return Err(ErrorCode::NotPermitted); - } - let link = d.run_blocking(move |d| d.read_link(&path)).await?; - link.into_os_string() - .into_string() - .or(Err(ErrorCode::IllegalByteSequence)) - } - - async fn remove_directory_at(self, path: String) -> Result<(), ErrorCode> { - let d = self.dir()?; - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted); +impl From for crate::filesystem::DescriptorFlags { + fn from(flags: types::DescriptorFlags) -> Self { + let mut out = Self::empty(); + if flags.contains(types::DescriptorFlags::READ) { + out |= Self::READ; } - d.run_blocking(move |d| d.remove_dir(&path)).await?; - Ok(()) - } - - async fn rename_at( - self, - old_path: String, - new_fd: Self, - new_path: String, - ) -> Result<(), ErrorCode> { - let old_dir = self.dir()?; - if !old_dir.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted); + if flags.contains(types::DescriptorFlags::WRITE) { + out |= Self::WRITE; } - let new_dir = new_fd.dir()?; - if !new_dir.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted); + if flags.contains(types::DescriptorFlags::FILE_INTEGRITY_SYNC) { + out |= Self::FILE_INTEGRITY_SYNC; } - let new_dir_handle = Arc::clone(&new_dir.dir); - old_dir - .run_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path)) - .await?; - Ok(()) - } - - async fn symlink_at(self, src_path: String, dest_path: String) -> Result<(), ErrorCode> { - // On windows, Dir.symlink is provided by DirExt - #[cfg(windows)] - use cap_fs_ext::DirExt; - - let d = self.dir()?; - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted); + if flags.contains(types::DescriptorFlags::DATA_INTEGRITY_SYNC) { + out |= Self::DATA_INTEGRITY_SYNC; } - d.run_blocking(move |d| d.symlink(&src_path, &dest_path)) - .await?; - Ok(()) - } - - async fn unlink_file_at(self, path: String) -> Result<(), ErrorCode> { - use cap_fs_ext::DirExt; - - let d = self.dir()?; - if !d.perms.contains(DirPerms::MUTATE) { - return Err(ErrorCode::NotPermitted); + if flags.contains(types::DescriptorFlags::REQUESTED_WRITE_SYNC) { + out |= Self::REQUESTED_WRITE_SYNC; } - d.run_blocking(move |d| d.remove_file_or_symlink(&path)) - .await?; - Ok(()) - } - - async fn is_same_object(self, other: Self) -> wasmtime::Result { - use cap_fs_ext::MetadataExt; - let meta_a = self.get_metadata().await?; - let meta_b = other.get_metadata().await?; - if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() { - // MetadataHashValue does not derive eq, so use a pair of - // comparisons to check equality: - debug_assert_eq!( - MetadataHashValue::from(&meta_a).upper, - MetadataHashValue::from(&meta_b).upper, - ); - debug_assert_eq!( - MetadataHashValue::from(&meta_a).lower, - MetadataHashValue::from(&meta_b).lower, - ); - Ok(true) - } else { - // Hash collisions are possible, so don't assert the negative here - Ok(false) + if flags.contains(types::DescriptorFlags::MUTATE_DIRECTORY) { + out |= Self::MUTATE_DIRECTORY; } - } - async fn metadata_hash(self) -> Result { - let meta = self.get_metadata().await?; - Ok(MetadataHashValue::from(&meta)) - } - async fn metadata_hash_at( - self, - path_flags: PathFlags, - path: String, - ) -> Result { - let d = self.dir()?; - // No permissions check on metadata: if dir opened, allowed to stat it - let meta = d - .run_blocking(move |d| { - if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - d.metadata(path) - } else { - d.symlink_metadata(path) - } - }) - .await?; - Ok(MetadataHashValue::from(&meta)) + out } } -#[derive(Clone)] -pub struct File { - /// The operating system File this struct is mediating access to. - /// - /// Wrapped in an Arc because the same underlying file is used for - /// implementing the stream types. A copy is also needed for - /// [`spawn_blocking`]. - /// - /// [`spawn_blocking`]: Self::spawn_blocking - file: Arc, - /// Permissions to enforce on access to the file. These permissions are - /// specified by a user of the `crate::WasiCtxBuilder`, and are - /// enforced prior to any enforced by the underlying operating system. - perms: FilePerms, - /// The mode the file was opened under: bits for reading, and writing. - /// Required to correctly report the DescriptorFlags, because cap-std - /// doesn't presently provide a cross-platform equivalent of reading the - /// oflags back out using fcntl. - open_mode: OpenMode, - - tasks: Arc>, - - allow_blocking_current_thread: bool, +impl From for types::MetadataHashValue { + fn from( + crate::filesystem::MetadataHashValue { lower, upper }: crate::filesystem::MetadataHashValue, + ) -> Self { + Self { lower, upper } + } } -impl File { - pub fn new( - file: cap_std::fs::File, - perms: FilePerms, - open_mode: OpenMode, - allow_blocking_current_thread: bool, +impl From for types::DescriptorStat { + fn from( + crate::filesystem::DescriptorStat { + type_, + link_count, + size, + data_access_timestamp, + data_modification_timestamp, + status_change_timestamp, + }: crate::filesystem::DescriptorStat, ) -> Self { Self { - file: Arc::new(file), - perms, - open_mode, - tasks: Arc::default(), - allow_blocking_current_thread, - } - } - - /// Execute the blocking `body` function. - /// - /// Depending on how the WasiCtx was configured, the body may either be: - /// - Executed directly on the current thread. In this case the `async` - /// signature of this method is effectively a lie and the returned - /// Future will always be immediately Ready. Or: - /// - Spawned on a background thread using [`tokio::task::spawn_blocking`] - /// and immediately awaited. - /// - /// Intentionally blocking the executor thread might seem unorthodox, but is - /// not actually a problem for specific workloads. See: - /// - [`crate::WasiCtxBuilder::allow_blocking_current_thread`] - /// - [Poor performance of wasmtime file I/O maybe because tokio](https://github.com/bytecodealliance/wasmtime/issues/7973) - /// - [Implement opt-in for enabling WASI to block the current thread](https://github.com/bytecodealliance/wasmtime/pull/8190) - pub(crate) async fn run_blocking(&self, body: F) -> R - where - F: FnOnce(&cap_std::fs::File) -> R + Send + 'static, - R: Send + 'static, - { - match self.as_blocking_file() { - Some(file) => body(file), - None => self.spawn_blocking(body).await, - } - } - - pub(crate) fn spawn_blocking(&self, body: F) -> AbortOnDropJoinHandle - where - F: FnOnce(&cap_std::fs::File) -> R + Send + 'static, - R: Send + 'static, - { - let f = self.file.clone(); - spawn_blocking(move || body(&f)) - } - - /// Returns `Some` when the current thread is allowed to block in filesystem - /// operations, and otherwise returns `None` to indicate that - /// `spawn_blocking` must be used. - pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> { - if self.allow_blocking_current_thread { - Some(&self.file) - } else { - None + type_: type_.into(), + link_count, + size, + data_access_timestamp: data_access_timestamp.map(Into::into), + data_modification_timestamp: data_modification_timestamp.map(Into::into), + status_change_timestamp: status_change_timestamp.map(Into::into), } } } -#[derive(Clone)] -pub struct Dir { - /// The operating system file descriptor this struct is mediating access - /// to. - /// - /// Wrapped in an Arc because a copy is needed for [`spawn_blocking`]. - /// - /// [`spawn_blocking`]: Self::spawn_blocking - dir: Arc, - /// Permissions to enforce on access to this directory. These permissions - /// are specified by a user of the `crate::WasiCtxBuilder`, and - /// are enforced prior to any enforced by the underlying operating system. - /// - /// These permissions are also enforced on any directories opened under - /// this directory. - perms: DirPerms, - /// Permissions to enforce on any files opened under this directory. - file_perms: FilePerms, - /// The mode the directory was opened under: bits for reading, and writing. - /// Required to correctly report the DescriptorFlags, because cap-std - /// doesn't presently provide a cross-platform equivalent of reading the - /// oflags back out using fcntl. - open_mode: OpenMode, - - tasks: Arc>, - - allow_blocking_current_thread: bool, -} - -impl Dir { - pub fn new( - dir: cap_std::fs::Dir, - perms: DirPerms, - file_perms: FilePerms, - open_mode: OpenMode, - allow_blocking_current_thread: bool, - ) -> Self { - Dir { - dir: Arc::new(dir), - perms, - file_perms, - open_mode, - tasks: Arc::default(), - allow_blocking_current_thread, +impl From for types::DescriptorType { + fn from(ty: crate::filesystem::DescriptorType) -> Self { + match ty { + crate::filesystem::DescriptorType::Unknown => Self::Unknown, + crate::filesystem::DescriptorType::BlockDevice => Self::BlockDevice, + crate::filesystem::DescriptorType::CharacterDevice => Self::CharacterDevice, + crate::filesystem::DescriptorType::Directory => Self::Directory, + crate::filesystem::DescriptorType::SymbolicLink => Self::SymbolicLink, + crate::filesystem::DescriptorType::RegularFile => Self::RegularFile, } } +} - /// Execute the blocking `body` function. - /// - /// Depending on how the WasiCtx was configured, the body may either be: - /// - Executed directly on the current thread. In this case the `async` - /// signature of this method is effectively a lie and the returned - /// Future will always be immediately Ready. Or: - /// - Spawned on a background thread using [`tokio::task::spawn_blocking`] - /// and immediately awaited. - /// - /// Intentionally blocking the executor thread might seem unorthodox, but is - /// not actually a problem for specific workloads. See: - /// - [`crate::WasiCtxBuilder::allow_blocking_current_thread`] - /// - [Poor performance of wasmtime file I/O maybe because tokio](https://github.com/bytecodealliance/wasmtime/issues/7973) - /// - [Implement opt-in for enabling WASI to block the current thread](https://github.com/bytecodealliance/wasmtime/pull/8190) - pub(crate) async fn run_blocking(&self, body: F) -> R - where - F: FnOnce(&cap_std::fs::Dir) -> R + Send + 'static, - R: Send + 'static, - { - if self.allow_blocking_current_thread { - body(&self.dir) +impl From for types::DescriptorType { + fn from(ft: cap_std::fs::FileType) -> Self { + use cap_fs_ext::FileTypeExt as _; + if ft.is_dir() { + Self::Directory + } else if ft.is_symlink() { + Self::SymbolicLink + } else if ft.is_block_device() { + Self::BlockDevice + } else if ft.is_char_device() { + Self::CharacterDevice + } else if ft.is_file() { + Self::RegularFile } else { - let d = self.dir.clone(); - spawn_blocking(move || body(&d)).await + Self::Unknown } } } diff --git a/crates/wasi/src/p3/mod.rs b/crates/wasi/src/p3/mod.rs index 506e4e3da1..3316c33ccd 100644 --- a/crates/wasi/src/p3/mod.rs +++ b/crates/wasi/src/p3/mod.rs @@ -1,3 +1,4 @@ +use crate::WasiView; use crate::p3::bindings::LinkOptions; use anyhow::{Result, anyhow, bail}; use core::future::Future; @@ -7,26 +8,21 @@ use std::pin::Pin; use std::sync::Arc; use tokio::sync::mpsc; use wasmtime::component::{ - AbortHandle, Access, Accessor, AccessorTask, FutureWriter, GuardedStreamWriter, HasData, - Linker, Lower, ResourceTable, StreamWriter, VecBuffer, + Access, Accessor, AccessorTask, FutureWriter, GuardedStreamWriter, HasData, JoinHandle, Linker, + Lower, ResourceTable, StreamWriter, VecBuffer, }; pub mod bindings; pub mod cli; pub mod clocks; -mod ctx; pub mod filesystem; pub mod random; pub mod sockets; -mod view; - -pub use self::ctx::{WasiCtx, WasiCtxBuilder}; -pub use self::view::{WasiCtxView, WasiView}; // Default buffer capacity to use for reads of byte-sized values. const DEFAULT_BUFFER_CAPACITY: usize = 8192; -pub struct AbortOnDropHandle(pub AbortHandle); +pub struct AbortOnDropHandle(pub JoinHandle); impl Drop for AbortOnDropHandle { fn drop(&mut self) { @@ -53,7 +49,7 @@ impl Drop for AbortOnDropHandle { /// use wasmtime::{Engine, Result, Store, Config}; /// use wasmtime_wasi::p3::filesystem::{WasiFilesystemCtx, WasiFilesystemView}; /// use wasmtime::component::{Linker, ResourceTable}; -/// use wasmtime_wasi::p3::{WasiCtx, WasiCtxView, WasiView}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// /// fn main() -> Result<()> { /// let mut config = Config::new(); @@ -97,7 +93,7 @@ impl Drop for AbortOnDropHandle { /// ``` pub fn add_to_linker(linker: &mut Linker) -> wasmtime::Result<()> where - T: WasiView + filesystem::WasiFilesystemView + 'static, + T: WasiView + 'static, { let options = LinkOptions::default(); add_to_linker_with_options(linker, &options) @@ -109,13 +105,13 @@ pub fn add_to_linker_with_options( options: &LinkOptions, ) -> wasmtime::Result<()> where - T: WasiView + filesystem::WasiFilesystemView + 'static, + T: WasiView + 'static, { cli::add_to_linker_with_options(linker, &options.into())?; clocks::add_to_linker(linker)?; + filesystem::add_to_linker(linker)?; random::add_to_linker(linker)?; sockets::add_to_linker(linker)?; - filesystem::add_to_linker(linker)?; Ok(()) } @@ -138,12 +134,12 @@ pub trait SpawnExt: Sized { fn spawn( self, task: impl AccessorTask>, - ) -> AbortHandle; + ) -> JoinHandle; fn spawn_fn( self, func: impl FnOnce(&Accessor) -> F + Send + 'static, - ) -> AbortHandle + ) -> JoinHandle where F: Future> + Send, { @@ -170,7 +166,7 @@ pub trait SpawnExt: Sized { ) -> Pin> + Send + '_>> + Send + 'static, - ) -> AbortHandle { + ) -> JoinHandle { struct AccessorTaskFnBox(pub F); impl AccessorTask for AccessorTaskFnBox @@ -192,7 +188,7 @@ impl SpawnExt for &mut Access<'_, T, D> { type Data = T; type AccessorData = D; - fn spawn(self, task: impl AccessorTask>) -> AbortHandle { + fn spawn(self, task: impl AccessorTask>) -> JoinHandle { >::spawn(self, task) } } @@ -201,7 +197,7 @@ impl SpawnExt for &Accessor { type Data = T; type AccessorData = D; - fn spawn(self, task: impl AccessorTask>) -> AbortHandle { + fn spawn(self, task: impl AccessorTask>) -> JoinHandle { >::spawn(self, task) } } diff --git a/crates/wasi/src/p3/random/mod.rs b/crates/wasi/src/p3/random/mod.rs index 68a50520da..fe9dbb8153 100644 --- a/crates/wasi/src/p3/random/mod.rs +++ b/crates/wasi/src/p3/random/mod.rs @@ -1,6 +1,6 @@ mod host; -use crate::p3::bindings::random; +use crate::p3::bindings::random::{insecure, insecure_seed, random}; use crate::random::{WasiRandom, WasiRandomView}; use wasmtime::component::Linker; @@ -55,8 +55,8 @@ pub fn add_to_linker(linker: &mut Linker) -> wasmtime::Result<()> where T: WasiRandomView + 'static, { - random::random::add_to_linker::<_, WasiRandom>(linker, T::random)?; - random::insecure::add_to_linker::<_, WasiRandom>(linker, T::random)?; - random::insecure_seed::add_to_linker::<_, WasiRandom>(linker, T::random)?; + random::add_to_linker::<_, WasiRandom>(linker, T::random)?; + insecure::add_to_linker::<_, WasiRandom>(linker, T::random)?; + insecure_seed::add_to_linker::<_, WasiRandom>(linker, T::random)?; Ok(()) } diff --git a/crates/wasi/src/p3/sockets/conv.rs b/crates/wasi/src/p3/sockets/conv.rs index ee4920dfa0..915bd3e962 100644 --- a/crates/wasi/src/p3/sockets/conv.rs +++ b/crates/wasi/src/p3/sockets/conv.rs @@ -1,13 +1,12 @@ +use crate::p3::bindings::sockets::types; +use crate::p3::sockets::SocketError; +use crate::sockets::SocketAddressFamily; +use crate::sockets::util::{from_ipv4_addr, from_ipv6_addr, to_ipv4_addr, to_ipv6_addr}; use core::net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6}; - -use std::net::ToSocketAddrs; - use rustix::io::Errno; +use std::net::ToSocketAddrs; use tracing::debug; -use crate::p3::bindings::sockets::types; -use crate::sockets::util::{from_ipv4_addr, from_ipv6_addr, to_ipv4_addr, to_ipv6_addr}; - impl From for types::IpAddress { fn from(addr: IpAddr) -> Self { match addr { @@ -123,6 +122,24 @@ impl From for types::IpAddressFamily { } } +impl From for types::IpAddressFamily { + fn from(family: SocketAddressFamily) -> Self { + match family { + SocketAddressFamily::Ipv4 => Self::Ipv4, + SocketAddressFamily::Ipv6 => Self::Ipv6, + } + } +} + +impl From for SocketAddressFamily { + fn from(family: types::IpAddressFamily) -> Self { + match family { + types::IpAddressFamily::Ipv4 => Self::Ipv4, + types::IpAddressFamily::Ipv6 => Self::Ipv6, + } + } +} + impl From for types::ErrorCode { fn from(value: std::io::Error) -> Self { (&value).into() @@ -222,6 +239,14 @@ impl From for types::ErrorCode { crate::sockets::util::ErrorCode::ConnectionReset => Self::ConnectionReset, crate::sockets::util::ErrorCode::ConnectionAborted => Self::ConnectionAborted, crate::sockets::util::ErrorCode::DatagramTooLarge => Self::DatagramTooLarge, + crate::sockets::util::ErrorCode::NotInProgress => Self::InvalidState, + crate::sockets::util::ErrorCode::ConcurrencyConflict => Self::InvalidState, } } } + +impl From for SocketError { + fn from(code: crate::sockets::util::ErrorCode) -> Self { + SocketError::from(types::ErrorCode::from(code)) + } +} diff --git a/crates/wasi/src/p3/sockets/host/types/mod.rs b/crates/wasi/src/p3/sockets/host/types/mod.rs index b64fd44dbd..60c152dab4 100644 --- a/crates/wasi/src/p3/sockets/host/types/mod.rs +++ b/crates/wasi/src/p3/sockets/host/types/mod.rs @@ -1,15 +1,17 @@ +use crate::p3::bindings::sockets::types::{ErrorCode, Host}; +use crate::p3::sockets::{SocketError, WasiSockets}; +use crate::sockets::{SocketAddrCheck, SocketAddrUse, WasiSocketsCtxView}; use core::net::SocketAddr; - use wasmtime::component::Accessor; -use crate::p3::bindings::sockets::types::Host; -use crate::p3::sockets::WasiSockets; -use crate::sockets::{SocketAddrCheck, SocketAddrUse, WasiSocketsCtxView}; - mod tcp; mod udp; -impl Host for WasiSocketsCtxView<'_> {} +impl Host for WasiSocketsCtxView<'_> { + fn convert_error_code(&mut self, error: SocketError) -> anyhow::Result { + error.downcast() + } +} fn get_socket_addr_check(store: &Accessor) -> SocketAddrCheck { store.with(|mut view| view.get().ctx.socket_addr_check.clone()) diff --git a/crates/wasi/src/p3/sockets/host/types/tcp.rs b/crates/wasi/src/p3/sockets/host/types/tcp.rs index de33a6d8e5..0195ed32dc 100644 --- a/crates/wasi/src/p3/sockets/host/types/tcp.rs +++ b/crates/wasi/src/p3/sockets/host/types/tcp.rs @@ -1,57 +1,45 @@ -use core::future::poll_fn; -use core::mem; -use core::net::SocketAddr; -use core::pin::pin; -use core::task::Poll; - -use std::io::Cursor; -use std::net::Shutdown; -use std::sync::Arc; - -use anyhow::{Context as _, ensure}; +use super::is_addr_allowed; +use crate::TrappableError; +use crate::p3::DEFAULT_BUFFER_CAPACITY; +use crate::p3::bindings::sockets::types::{ + Duration, ErrorCode, HostTcpSocket, HostTcpSocketWithStore, IpAddressFamily, IpSocketAddress, + TcpSocket, +}; +use crate::p3::sockets::{SocketResult, WasiSockets}; +use crate::sockets::{NonInheritedOptions, SocketAddrUse, SocketAddressFamily, WasiSocketsCtxView}; +use anyhow::Context; use bytes::BytesMut; use io_lifetimes::AsSocketlike as _; -use rustix::io::Errno; +use std::future::poll_fn; +use std::io::Cursor; +use std::net::{Shutdown, SocketAddr}; +use std::pin::pin; +use std::sync::Arc; +use std::task::Poll; use tokio::net::{TcpListener, TcpStream}; use wasmtime::component::{ Accessor, AccessorTask, FutureReader, FutureWriter, GuardedFutureWriter, GuardedStreamWriter, Resource, ResourceTable, StreamReader, StreamWriter, }; -use crate::p3::DEFAULT_BUFFER_CAPACITY; -use crate::p3::bindings::sockets::types::{ - Duration, ErrorCode, HostTcpSocket, HostTcpSocketWithStore, IpAddressFamily, IpSocketAddress, - TcpSocket, -}; -use crate::p3::sockets::WasiSockets; -use crate::p3::sockets::tcp::{NonInheritedOptions, TcpState}; -use crate::sockets::util::{ - is_valid_address_family, is_valid_remote_address, is_valid_unicast_address, -}; -use crate::sockets::{SocketAddrUse, SocketAddressFamily, WasiSocketsCtxView}; - -use super::is_addr_allowed; - -fn is_tcp_allowed(store: &Accessor) -> bool { - store.with(|mut view| view.get().ctx.allowed_network_uses.tcp) -} - fn get_socket<'a>( table: &'a ResourceTable, socket: &'a Resource, -) -> wasmtime::Result<&'a TcpSocket> { +) -> SocketResult<&'a TcpSocket> { table .get(socket) .context("failed to get socket resource from table") + .map_err(TrappableError::trap) } fn get_socket_mut<'a>( table: &'a mut ResourceTable, socket: &'a Resource, -) -> wasmtime::Result<&'a mut TcpSocket> { +) -> SocketResult<&'a mut TcpSocket> { table .get_mut(socket) .context("failed to get socket resource from table") + .map_err(TrappableError::trap) } struct ListenTask { @@ -76,50 +64,12 @@ impl AccessorTask> for ListenTask { }) else { return Ok(()); }; - let state = match res { - Ok((stream, _addr)) => { - self.options.apply(self.family, &stream); - TcpState::Connected(Arc::new(stream)) - } - Err(err) => { - match Errno::from_io_error(&err) { - // From: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept#:~:text=WSAEINPROGRESS - // > WSAEINPROGRESS: A blocking Windows Sockets 1.1 call is in progress, - // > or the service provider is still processing a callback function. - // - // wasi-sockets doesn't have an equivalent to the EINPROGRESS error, - // because in POSIX this error is only returned by a non-blocking - // `connect` and wasi-sockets has a different solution for that. - #[cfg(windows)] - Some(Errno::INPROGRESS) => TcpState::Error(ErrorCode::Unknown), - - // Normalize Linux' non-standard behavior. - // - // From https://man7.org/linux/man-pages/man2/accept.2.html: - // > Linux accept() passes already-pending network errors on the - // > new socket as an error code from accept(). This behavior - // > differs from other BSD socket implementations. (...) - #[cfg(target_os = "linux")] - Some( - Errno::CONNRESET - | Errno::NETRESET - | Errno::HOSTUNREACH - | Errno::HOSTDOWN - | Errno::NETDOWN - | Errno::NETUNREACH - | Errno::PROTO - | Errno::NOPROTOOPT - | Errno::NONET - | Errno::OPNOTSUPP, - ) => TcpState::Error(ErrorCode::ConnectionAborted), - _ => TcpState::Error(err.into()), - } - } - }; + let socket = TcpSocket::new_accept(res.map(|p| p.0), &self.options, self.family) + .unwrap_or_else(|err| TcpSocket::new_error(err, self.family)); let socket = store.with(|mut view| { view.get() .table - .push(TcpSocket::from_state(state, self.family)) + .push(socket) .context("failed to push socket resource to table") })?; if let Some(socket) = tx.write(Some(socket)).await { @@ -137,20 +87,6 @@ impl AccessorTask> for ListenTask { } } -struct ResultWriteTask { - result: Result<(), ErrorCode>, - result_tx: FutureWriter>, -} - -impl AccessorTask> for ResultWriteTask { - async fn run(self, store: &Accessor) -> wasmtime::Result<()> { - GuardedFutureWriter::new(store, self.result_tx) - .write(self.result) - .await; - Ok(()) - } -} - struct ReceiveTask { stream: Arc, data_tx: StreamWriter, @@ -199,13 +135,9 @@ impl AccessorTask> for ReceiveTask { .stream .as_socketlike_view::() .shutdown(Shutdown::Read); - - // Write the result async from a separate task to ensure that all resources used by this - // task are freed - store.spawn(ResultWriteTask { - result: res, - result_tx: result_tx.into(), - }); + drop(self.stream); + drop(data_tx); + result_tx.write(res).await; Ok(()) } } @@ -215,16 +147,16 @@ impl HostTcpSocketWithStore for WasiSockets { store: &Accessor, socket: Resource, local_address: IpSocketAddress, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let local_address = SocketAddr::from(local_address); - if !is_tcp_allowed(store) - || !is_addr_allowed(store, local_address, SocketAddrUse::TcpBind).await - { - return Ok(Err(ErrorCode::AccessDenied)); + if !is_addr_allowed(store, local_address, SocketAddrUse::TcpBind).await { + return Err(ErrorCode::AccessDenied.into()); } store.with(|mut view| { let socket = get_socket_mut(view.get().table, &socket)?; - Ok(socket.bind(local_address)) + socket.start_bind(local_address)?; + socket.finish_bind()?; + Ok(()) }) } @@ -232,105 +164,42 @@ impl HostTcpSocketWithStore for WasiSockets { store: &Accessor, socket: Resource, remote_address: IpSocketAddress, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let remote_address = SocketAddr::from(remote_address); - if !is_tcp_allowed(store) - || !is_addr_allowed(store, remote_address, SocketAddrUse::TcpConnect).await - { - return Ok(Err(ErrorCode::AccessDenied)); + if !is_addr_allowed(store, remote_address, SocketAddrUse::TcpConnect).await { + return Err(ErrorCode::AccessDenied.into()); } - match store.with(|mut view| { - let ip = remote_address.ip(); + let sock = store.with(|mut view| -> SocketResult<_> { let socket = get_socket_mut(view.get().table, &socket)?; - if !is_valid_unicast_address(ip) - || !is_valid_remote_address(remote_address) - || !is_valid_address_family(ip, socket.family) - { - return anyhow::Ok(Err(ErrorCode::InvalidArgument)); - } - match mem::replace(&mut socket.tcp_state, TcpState::Connecting) { - TcpState::Default(sock) | TcpState::Bound(sock) => Ok(Ok(sock)), - tcp_state => { - socket.tcp_state = tcp_state; - Ok(Err(ErrorCode::InvalidState)) - } - } - })? { - Ok(sock) => { - // FIXME: handle possible cancellation of the outer `connect` - // https://github.com/bytecodealliance/wasmtime/pull/11291#discussion_r2223917986 - let res = sock.connect(remote_address).await; - store.with(|mut view| { - let socket = get_socket_mut(view.get().table, &socket)?; - ensure!( - matches!(socket.tcp_state, TcpState::Connecting), - "corrupted socket state" - ); - match res { - Ok(stream) => { - socket.tcp_state = TcpState::Connected(Arc::new(stream)); - Ok(Ok(())) - } - Err(err) => { - socket.tcp_state = TcpState::Closed; - Ok(Err(err.into())) - } - } - }) - } - Err(err) => Ok(Err(err)), - } + Ok(socket.start_connect(&remote_address)?) + })?; + + // FIXME: handle possible cancellation of the outer `connect` + // https://github.com/bytecodealliance/wasmtime/pull/11291#discussion_r2223917986 + let res = sock.connect(remote_address).await; + store.with(|mut view| -> SocketResult<_> { + let socket = get_socket_mut(view.get().table, &socket)?; + socket.finish_connect(res)?; + Ok(()) + }) } async fn listen( store: &Accessor, socket: Resource, - ) -> wasmtime::Result>, ErrorCode>> { + ) -> SocketResult>> { store.with(|mut view| { - if !view.get().ctx.allowed_network_uses.tcp { - return anyhow::Ok(Err(ErrorCode::AccessDenied)); - } - let TcpSocket { - tcp_state, - listen_backlog_size, - family, - options, - } = get_socket_mut(view.get().table, &socket)?; - let sock = match mem::replace(tcp_state, TcpState::Closed) { - TcpState::Default(sock) | TcpState::Bound(sock) => sock, - prev => { - *tcp_state = prev; - return Ok(Err(ErrorCode::InvalidState)); - } - }; - let listener = match sock.listen(*listen_backlog_size) { - Ok(listener) => listener, - Err(err) => { - match Errno::from_io_error(&err) { - // See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen#:~:text=WSAEMFILE - // According to the docs, `listen` can return EMFILE on Windows. - // This is odd, because we're not trying to create a new socket - // or file descriptor of any kind. So we rewrite it to less - // surprising error code. - // - // At the time of writing, this behavior has never been experimentally - // observed by any of the wasmtime authors, so we're relying fully - // on Microsoft's documentation here. - #[cfg(windows)] - Some(Errno::MFILE) => return Ok(Err(ErrorCode::OutOfMemory)), - - _ => return Ok(Err(err.into())), - } - } - }; - let listener = Arc::new(listener); - *tcp_state = TcpState::Listening(Arc::clone(&listener)); - let family = *family; - let options = options.clone(); + let socket = get_socket_mut(view.get().table, &socket)?; + socket.start_listen()?; + socket.finish_listen()?; + let listener = socket.tcp_listener_arc().unwrap().clone(); + let family = socket.address_family(); + let options = socket.non_inherited_options().clone(); let (tx, rx) = view .instance() .stream(&mut view) - .context("failed to create stream")?; + .context("failed to create stream") + .map_err(TrappableError::trap)?; let task = ListenTask { listener, family, @@ -338,27 +207,21 @@ impl HostTcpSocketWithStore for WasiSockets { options, }; view.spawn(task); - Ok(Ok(rx)) + Ok(rx) }) } async fn send( store: &Accessor, socket: Resource, - data: StreamReader, - ) -> wasmtime::Result> { - let (stream, mut data) = match store.with(|mut view| -> wasmtime::Result<_> { + mut data: StreamReader, + ) -> SocketResult<()> { + let stream = store.with(|mut view| -> SocketResult<_> { let sock = get_socket(view.get().table, &socket)?; - if let TcpState::Connected(stream) | TcpState::Receiving(stream) = &sock.tcp_state { - Ok(Ok((Arc::clone(&stream), data))) - } else { - Ok(Err(ErrorCode::InvalidState)) - } - })? { - Ok((stream, data)) => (stream, data), - Err(err) => return Ok(Err(err)), - }; - let mut buf = Vec::with_capacity(8096); + let stream = sock.tcp_stream_arc()?; + Ok(Arc::clone(stream)) + })?; + let mut buf = Vec::with_capacity(DEFAULT_BUFFER_CAPACITY); let mut result = Ok(()); while !data.is_closed() { buf = data.read(store, buf).await; @@ -368,12 +231,12 @@ impl HostTcpSocketWithStore for WasiSockets { Ok(n) => slice = &slice[n..], Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { if let Err(err) = stream.writable().await { - result = Err(err.into()); + result = Err(ErrorCode::from(err).into()); break; } } Err(err) => { - result = Err(err.into()); + result = Err(ErrorCode::from(err).into()); break; } } @@ -383,7 +246,7 @@ impl HostTcpSocketWithStore for WasiSockets { _ = stream .as_socketlike_view::() .shutdown(Shutdown::Write); - Ok(result) + result } async fn receive( @@ -395,10 +258,10 @@ impl HostTcpSocketWithStore for WasiSockets { let (mut data_tx, data_rx) = instance .stream(&mut view) .context("failed to create stream")?; - let TcpSocket { tcp_state, .. } = get_socket_mut(view.get().table, &socket)?; - match mem::replace(tcp_state, TcpState::Closed) { - TcpState::Connected(stream) => { - *tcp_state = TcpState::Receiving(Arc::clone(&stream)); + let socket = get_socket_mut(view.get().table, &socket)?; + match socket.start_receive() { + Some(stream) => { + let stream = stream.clone(); let (result_tx, result_rx) = instance .future(&mut view, || unreachable!()) .context("failed to create future")?; @@ -409,8 +272,7 @@ impl HostTcpSocketWithStore for WasiSockets { }); Ok((data_rx, result_rx)) } - prev => { - *tcp_state = prev; + None => { let (mut result_tx, result_rx) = instance .future(&mut view, || Err(ErrorCode::InvalidState)) .context("failed to create future")?; @@ -425,26 +287,22 @@ impl HostTcpSocketWithStore for WasiSockets { impl HostTcpSocket for WasiSocketsCtxView<'_> { fn new(&mut self, address_family: IpAddressFamily) -> wasmtime::Result> { - let socket = TcpSocket::new(address_family.into()).context("failed to create socket")?; + let family = address_family.into(); + let socket = + TcpSocket::new(self.ctx, family).unwrap_or_else(|e| TcpSocket::new_error(e, family)); self.table .push(socket) .context("failed to push socket resource to table") } - fn local_address( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn local_address(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.local_address()) + Ok(sock.local_address()?.into()) } - fn remote_address( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn remote_address(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.remote_address()) + Ok(sock.remote_address()?.into()) } fn is_listening(&mut self, socket: Resource) -> wasmtime::Result { @@ -454,135 +312,118 @@ impl HostTcpSocket for WasiSocketsCtxView<'_> { fn address_family(&mut self, socket: Resource) -> wasmtime::Result { let sock = get_socket(self.table, &socket)?; - Ok(sock.address_family()) + Ok(sock.address_family().into()) } fn set_listen_backlog_size( &mut self, socket: Resource, value: u64, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let sock = get_socket_mut(self.table, &socket)?; - Ok(sock.set_listen_backlog_size(value)) + sock.set_listen_backlog_size(value)?; + Ok(()) } - fn keep_alive_enabled( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn keep_alive_enabled(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.keep_alive_enabled()) + Ok(sock.keep_alive_enabled()?) } fn set_keep_alive_enabled( &mut self, socket: Resource, value: bool, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let sock = get_socket(self.table, &socket)?; - Ok(sock.set_keep_alive_enabled(value)) + sock.set_keep_alive_enabled(value)?; + Ok(()) } - fn keep_alive_idle_time( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn keep_alive_idle_time(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.keep_alive_idle_time()) + Ok(sock.keep_alive_idle_time()?) } fn set_keep_alive_idle_time( &mut self, socket: Resource, value: Duration, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let sock = get_socket_mut(self.table, &socket)?; - Ok(sock.set_keep_alive_idle_time(value)) + sock.set_keep_alive_idle_time(value)?; + Ok(()) } - fn keep_alive_interval( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn keep_alive_interval(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.keep_alive_interval()) + Ok(sock.keep_alive_interval()?) } fn set_keep_alive_interval( &mut self, socket: Resource, value: Duration, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let sock = get_socket(self.table, &socket)?; - Ok(sock.set_keep_alive_interval(value)) + sock.set_keep_alive_interval(value)?; + Ok(()) } - fn keep_alive_count( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn keep_alive_count(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.keep_alive_count()) + Ok(sock.keep_alive_count()?) } fn set_keep_alive_count( &mut self, socket: Resource, value: u32, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let sock = get_socket(self.table, &socket)?; - Ok(sock.set_keep_alive_count(value)) + sock.set_keep_alive_count(value)?; + Ok(()) } - fn hop_limit( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn hop_limit(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.hop_limit()) + Ok(sock.hop_limit()?) } - fn set_hop_limit( - &mut self, - socket: Resource, - value: u8, - ) -> wasmtime::Result> { + fn set_hop_limit(&mut self, socket: Resource, value: u8) -> SocketResult<()> { let sock = get_socket_mut(self.table, &socket)?; - Ok(sock.set_hop_limit(value)) + sock.set_hop_limit(value)?; + Ok(()) } - fn receive_buffer_size( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn receive_buffer_size(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.receive_buffer_size()) + Ok(sock.receive_buffer_size()?) } fn set_receive_buffer_size( &mut self, socket: Resource, value: u64, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let sock = get_socket_mut(self.table, &socket)?; - Ok(sock.set_receive_buffer_size(value)) + sock.set_receive_buffer_size(value)?; + Ok(()) } - fn send_buffer_size( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn send_buffer_size(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.send_buffer_size()) + Ok(sock.send_buffer_size()?) } fn set_send_buffer_size( &mut self, socket: Resource, value: u64, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let sock = get_socket_mut(self.table, &socket)?; - Ok(sock.set_send_buffer_size(value)) + sock.set_send_buffer_size(value)?; + Ok(()) } fn drop(&mut self, sock: Resource) -> wasmtime::Result<()> { diff --git a/crates/wasi/src/p3/sockets/host/types/udp.rs b/crates/wasi/src/p3/sockets/host/types/udp.rs index 2518590c8b..e9f4ecf0c3 100644 --- a/crates/wasi/src/p3/sockets/host/types/udp.rs +++ b/crates/wasi/src/p3/sockets/host/types/udp.rs @@ -1,37 +1,32 @@ -use core::net::SocketAddr; - -use anyhow::Context as _; -use wasmtime::component::{Accessor, Resource, ResourceTable}; - +use super::is_addr_allowed; +use crate::TrappableError; use crate::p3::bindings::sockets::types::{ ErrorCode, HostUdpSocket, HostUdpSocketWithStore, IpAddressFamily, IpSocketAddress, }; -use crate::p3::sockets::WasiSockets; -use crate::p3::sockets::udp::UdpSocket; -use crate::sockets::{MAX_UDP_DATAGRAM_SIZE, SocketAddrUse, WasiSocketsCtxView}; - -use super::is_addr_allowed; - -fn is_udp_allowed(store: &Accessor) -> bool { - store.with(|mut view| view.get().ctx.allowed_network_uses.udp) -} +use crate::p3::sockets::{SocketResult, WasiSockets}; +use crate::sockets::{MAX_UDP_DATAGRAM_SIZE, SocketAddrUse, UdpSocket, WasiSocketsCtxView}; +use anyhow::Context; +use std::net::SocketAddr; +use wasmtime::component::{Accessor, Resource, ResourceTable}; fn get_socket<'a>( table: &'a ResourceTable, socket: &'a Resource, -) -> wasmtime::Result<&'a UdpSocket> { +) -> SocketResult<&'a UdpSocket> { table .get(socket) .context("failed to get socket resource from table") + .map_err(TrappableError::trap) } fn get_socket_mut<'a>( table: &'a mut ResourceTable, socket: &'a Resource, -) -> wasmtime::Result<&'a mut UdpSocket> { +) -> SocketResult<&'a mut UdpSocket> { table .get_mut(socket) .context("failed to get socket resource from table") + .map_err(TrappableError::trap) } impl HostUdpSocketWithStore for WasiSockets { @@ -39,16 +34,16 @@ impl HostUdpSocketWithStore for WasiSockets { store: &Accessor, socket: Resource, local_address: IpSocketAddress, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let local_address = SocketAddr::from(local_address); - if !is_udp_allowed(store) - || !is_addr_allowed(store, local_address, SocketAddrUse::UdpBind).await - { - return Ok(Err(ErrorCode::AccessDenied)); + if !is_addr_allowed(store, local_address, SocketAddrUse::UdpBind).await { + return Err(ErrorCode::AccessDenied.into()); } store.with(|mut view| { let socket = get_socket_mut(view.get().table, &socket)?; - Ok(socket.bind(local_address)) + socket.bind(local_address)?; + socket.finish_bind()?; + Ok(()) }) } @@ -56,16 +51,15 @@ impl HostUdpSocketWithStore for WasiSockets { store: &Accessor, socket: Resource, remote_address: IpSocketAddress, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let remote_address = SocketAddr::from(remote_address); - if !is_udp_allowed(store) - || !is_addr_allowed(store, remote_address, SocketAddrUse::UdpConnect).await - { - return Ok(Err(ErrorCode::AccessDenied)); + if !is_addr_allowed(store, remote_address, SocketAddrUse::UdpConnect).await { + return Err(ErrorCode::AccessDenied.into()); } store.with(|mut view| { let socket = get_socket_mut(view.get().table, &socket)?; - Ok(socket.connect(remote_address)) + socket.connect(remote_address)?; + Ok(()) }) } @@ -74,129 +68,112 @@ impl HostUdpSocketWithStore for WasiSockets { socket: Resource, data: Vec, remote_address: Option, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { if data.len() > MAX_UDP_DATAGRAM_SIZE { - return Ok(Err(ErrorCode::DatagramTooLarge)); - } - if !is_udp_allowed(store) { - return Ok(Err(ErrorCode::AccessDenied)); + return Err(ErrorCode::DatagramTooLarge.into()); } if let Some(addr) = remote_address { let addr = SocketAddr::from(addr); if !is_addr_allowed(store, addr, SocketAddrUse::UdpOutgoingDatagram).await { - return Ok(Err(ErrorCode::AccessDenied)); + return Err(ErrorCode::AccessDenied.into()); } let fut = store.with(|mut view| { get_socket(view.get().table, &socket).map(|sock| sock.send_to(data, addr)) })?; - Ok(fut.await) + fut.await?; + Ok(()) } else { let fut = store.with(|mut view| { get_socket(view.get().table, &socket).map(|sock| sock.send(data)) })?; - Ok(fut.await) + fut.await?; + Ok(()) } } async fn receive( store: &Accessor, socket: Resource, - ) -> wasmtime::Result, IpSocketAddress), ErrorCode>> { - if !is_udp_allowed(store) { - return Ok(Err(ErrorCode::AccessDenied)); - } + ) -> SocketResult<(Vec, IpSocketAddress)> { let fut = store .with(|mut view| get_socket(view.get().table, &socket).map(|sock| sock.receive()))?; - Ok(fut.await) + let (result, addr) = fut.await?; + Ok((result, addr.into())) } } impl HostUdpSocket for WasiSocketsCtxView<'_> { fn new(&mut self, address_family: IpAddressFamily) -> wasmtime::Result> { - let socket = UdpSocket::new(address_family.into()).context("failed to create socket")?; + let socket = UdpSocket::new(self.ctx, address_family.into())?; self.table .push(socket) .context("failed to push socket resource to table") } - fn disconnect( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn disconnect(&mut self, socket: Resource) -> SocketResult<()> { let socket = get_socket_mut(self.table, &socket)?; - Ok(socket.disconnect()) + socket.disconnect()?; + Ok(()) } - fn local_address( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn local_address(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.local_address()) + Ok(sock.local_address()?.into()) } - fn remote_address( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn remote_address(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.remote_address()) + Ok(sock.remote_address()?.into()) } fn address_family(&mut self, socket: Resource) -> wasmtime::Result { let sock = get_socket(self.table, &socket)?; - Ok(sock.address_family()) + Ok(sock.address_family().into()) } - fn unicast_hop_limit( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn unicast_hop_limit(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.unicast_hop_limit()) + Ok(sock.unicast_hop_limit()?) } fn set_unicast_hop_limit( &mut self, socket: Resource, value: u8, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let sock = get_socket(self.table, &socket)?; - Ok(sock.set_unicast_hop_limit(value)) + sock.set_unicast_hop_limit(value)?; + Ok(()) } - fn receive_buffer_size( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn receive_buffer_size(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.receive_buffer_size()) + Ok(sock.receive_buffer_size()?) } fn set_receive_buffer_size( &mut self, socket: Resource, value: u64, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let sock = get_socket(self.table, &socket)?; - Ok(sock.set_receive_buffer_size(value)) + sock.set_receive_buffer_size(value)?; + Ok(()) } - fn send_buffer_size( - &mut self, - socket: Resource, - ) -> wasmtime::Result> { + fn send_buffer_size(&mut self, socket: Resource) -> SocketResult { let sock = get_socket(self.table, &socket)?; - Ok(sock.send_buffer_size()) + Ok(sock.send_buffer_size()?) } fn set_send_buffer_size( &mut self, socket: Resource, value: u64, - ) -> wasmtime::Result> { + ) -> SocketResult<()> { let sock = get_socket(self.table, &socket)?; - Ok(sock.set_send_buffer_size(value)) + sock.set_send_buffer_size(value)?; + Ok(()) } fn drop(&mut self, sock: Resource) -> wasmtime::Result<()> { diff --git a/crates/wasi/src/p3/sockets/mod.rs b/crates/wasi/src/p3/sockets/mod.rs index 322b48a9e8..3577e29c97 100644 --- a/crates/wasi/src/p3/sockets/mod.rs +++ b/crates/wasi/src/p3/sockets/mod.rs @@ -1,11 +1,13 @@ -use crate::p3::bindings::sockets; -use crate::sockets::{WasiSocketsCtxView, WasiSocketsView}; -use wasmtime::component::{HasData, Linker}; +use crate::TrappableError; +use crate::p3::bindings::sockets::{ip_name_lookup, types}; +use crate::sockets::{WasiSockets, WasiSocketsView}; +use wasmtime::component::Linker; mod conv; mod host; -pub mod tcp; -pub mod udp; + +pub type SocketResult = Result; +pub type SocketError = TrappableError; /// Add all WASI interfaces from this module into the `linker` provided. /// @@ -63,13 +65,7 @@ pub fn add_to_linker(linker: &mut Linker) -> wasmtime::Result<()> where T: WasiSocketsView + 'static, { - sockets::ip_name_lookup::add_to_linker::<_, WasiSockets>(linker, T::sockets)?; - sockets::types::add_to_linker::<_, WasiSockets>(linker, T::sockets)?; + ip_name_lookup::add_to_linker::<_, WasiSockets>(linker, T::sockets)?; + types::add_to_linker::<_, WasiSockets>(linker, T::sockets)?; Ok(()) } - -struct WasiSockets; - -impl HasData for WasiSockets { - type Data<'a> = WasiSocketsCtxView<'a>; -} diff --git a/crates/wasi/src/p3/sockets/tcp.rs b/crates/wasi/src/p3/sockets/tcp.rs deleted file mode 100644 index e327b1ef75..0000000000 --- a/crates/wasi/src/p3/sockets/tcp.rs +++ /dev/null @@ -1,409 +0,0 @@ -use core::fmt::Debug; -use core::mem; -use core::net::SocketAddr; - -use std::sync::Arc; - -use cap_net_ext::AddressFamily; -use io_lifetimes::AsSocketlike as _; -use io_lifetimes::views::SocketlikeView; -use rustix::net::sockopt; - -use crate::p3::bindings::sockets::types::{Duration, ErrorCode, IpAddressFamily, IpSocketAddress}; -use crate::runtime::with_ambient_tokio_runtime; -use crate::sockets::util::{ - get_unicast_hop_limit, is_valid_address_family, is_valid_unicast_address, receive_buffer_size, - send_buffer_size, set_keep_alive_count, set_keep_alive_idle_time, set_keep_alive_interval, - set_receive_buffer_size, set_send_buffer_size, set_unicast_hop_limit, tcp_bind, -}; -use crate::sockets::{DEFAULT_TCP_BACKLOG, SocketAddressFamily}; - -/// The state of a TCP socket. -/// -/// This represents the various states a socket can be in during the -/// activities of binding, listening, accepting, and connecting. -pub enum TcpState { - /// The initial state for a newly-created socket. - Default(tokio::net::TcpSocket), - - /// Binding finished. The socket has an address but is not yet listening for connections. - Bound(tokio::net::TcpSocket), - - /// The socket is now listening and waiting for an incoming connection. - Listening(Arc), - - /// An outgoing connection is started. - Connecting, - - /// A connection has been established. - Connected(Arc), - - /// A connection has been established and `receive` has been called. - Receiving(Arc), - - Error(ErrorCode), - - Closed, -} - -impl Debug for TcpState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Default(_) => f.debug_tuple("Default").finish(), - Self::Bound(_) => f.debug_tuple("Bound").finish(), - Self::Listening { .. } => f.debug_tuple("Listening").finish(), - Self::Connecting => f.debug_tuple("Connecting").finish(), - Self::Connected { .. } => f.debug_tuple("Connected").finish(), - Self::Receiving { .. } => f.debug_tuple("Receiving").finish(), - Self::Error(..) => f.debug_tuple("Error").finish(), - Self::Closed => write!(f, "Closed"), - } - } -} - -/// A host TCP socket, plus associated bookkeeping. -pub struct TcpSocket { - /// The current state in the bind/listen/accept/connect progression. - pub tcp_state: TcpState, - - /// The desired listen queue size. - pub listen_backlog_size: u32, - - pub family: SocketAddressFamily, - - pub options: NonInheritedOptions, -} - -impl TcpSocket { - /// Create a new socket in the given family. - pub fn new(family: AddressFamily) -> std::io::Result { - with_ambient_tokio_runtime(|| { - let (socket, family) = match family { - AddressFamily::Ipv4 => { - let socket = tokio::net::TcpSocket::new_v4()?; - (socket, SocketAddressFamily::Ipv4) - } - AddressFamily::Ipv6 => { - let socket = tokio::net::TcpSocket::new_v6()?; - sockopt::set_ipv6_v6only(&socket, true)?; - (socket, SocketAddressFamily::Ipv6) - } - }; - - Ok(Self::from_state(TcpState::Default(socket), family)) - }) - } - - /// Create a `TcpSocket` from an existing socket. - pub fn from_state(state: TcpState, family: SocketAddressFamily) -> Self { - Self { - tcp_state: state, - listen_backlog_size: DEFAULT_TCP_BACKLOG, - family, - options: Default::default(), - } - } - - pub fn as_std_view(&self) -> Result, ErrorCode> { - match &self.tcp_state { - TcpState::Default(socket) | TcpState::Bound(socket) => Ok(socket.as_socketlike_view()), - TcpState::Connected(stream) | TcpState::Receiving(stream) => { - Ok(stream.as_socketlike_view()) - } - TcpState::Listening(listener) => Ok(listener.as_socketlike_view()), - TcpState::Connecting | TcpState::Closed => Err(ErrorCode::InvalidState), - TcpState::Error(err) => Err(*err), - } - } - - pub fn bind(&mut self, addr: SocketAddr) -> Result<(), ErrorCode> { - let ip = addr.ip(); - if !is_valid_unicast_address(ip) || !is_valid_address_family(ip, self.family) { - return Err(ErrorCode::InvalidArgument); - } - match mem::replace(&mut self.tcp_state, TcpState::Closed) { - TcpState::Default(sock) => { - if let Err(err) = tcp_bind(&sock, addr) { - self.tcp_state = TcpState::Default(sock); - Err(err.into()) - } else { - self.tcp_state = TcpState::Bound(sock); - Ok(()) - } - } - tcp_state => { - self.tcp_state = tcp_state; - Err(ErrorCode::InvalidState) - } - } - } - - pub fn local_address(&self) -> Result { - match &self.tcp_state { - TcpState::Bound(socket) => { - let addr = socket.local_addr()?; - Ok(addr.into()) - } - TcpState::Connected(stream) | TcpState::Receiving(stream) => { - let addr = stream.local_addr()?; - Ok(addr.into()) - } - TcpState::Listening(listener) => { - let addr = listener.local_addr()?; - Ok(addr.into()) - } - TcpState::Error(err) => Err(*err), - _ => Err(ErrorCode::InvalidState), - } - } - - pub fn remote_address(&self) -> Result { - match &self.tcp_state { - TcpState::Connected(stream) | TcpState::Receiving(stream) => { - let addr = stream.peer_addr()?; - Ok(addr.into()) - } - TcpState::Error(err) => Err(*err), - _ => Err(ErrorCode::InvalidState), - } - } - - pub fn is_listening(&self) -> bool { - matches!(self.tcp_state, TcpState::Listening { .. }) - } - - pub fn address_family(&self) -> IpAddressFamily { - match self.family { - SocketAddressFamily::Ipv4 => IpAddressFamily::Ipv4, - SocketAddressFamily::Ipv6 => IpAddressFamily::Ipv6, - } - } - - pub fn set_listen_backlog_size(&mut self, value: u64) -> Result<(), ErrorCode> { - const MIN_BACKLOG: u32 = 1; - const MAX_BACKLOG: u32 = i32::MAX as u32; // OS'es will most likely limit it down even further. - - if value == 0 { - return Err(ErrorCode::InvalidArgument); - } - // Silently clamp backlog size. This is OK for us to do, because operating systems do this too. - let value = value - .try_into() - .unwrap_or(MAX_BACKLOG) - .clamp(MIN_BACKLOG, MAX_BACKLOG); - match &self.tcp_state { - TcpState::Default(..) | TcpState::Bound(..) => { - // Socket not listening yet. Stash value for first invocation to `listen`. - self.listen_backlog_size = value; - Ok(()) - } - TcpState::Listening(listener) => { - // Try to update the backlog by calling `listen` again. - // Not all platforms support this. We'll only update our own value if the OS supports changing the backlog size after the fact. - if rustix::net::listen(&listener, value.try_into().unwrap_or(i32::MAX)).is_err() { - return Err(ErrorCode::NotSupported); - } - self.listen_backlog_size = value; - Ok(()) - } - TcpState::Error(err) => Err(*err), - _ => Err(ErrorCode::InvalidState), - } - } - - pub fn keep_alive_enabled(&self) -> Result { - let fd = &*self.as_std_view()?; - let v = sockopt::socket_keepalive(fd)?; - Ok(v) - } - - pub fn set_keep_alive_enabled(&self, value: bool) -> Result<(), ErrorCode> { - let fd = &*self.as_std_view()?; - sockopt::set_socket_keepalive(fd, value)?; - Ok(()) - } - - pub fn keep_alive_idle_time(&self) -> Result { - let fd = &*self.as_std_view()?; - let v = sockopt::tcp_keepidle(fd)?; - Ok(v.as_nanos().try_into().unwrap_or(u64::MAX)) - } - - pub fn set_keep_alive_idle_time(&mut self, value: Duration) -> Result<(), ErrorCode> { - let value = { - let fd = self.as_std_view()?; - set_keep_alive_idle_time(&*fd, value)? - }; - self.options.set_keep_alive_idle_time(value); - Ok(()) - } - - pub fn keep_alive_interval(&self) -> Result { - let fd = &*self.as_std_view()?; - let v = sockopt::tcp_keepintvl(fd)?; - Ok(v.as_nanos().try_into().unwrap_or(u64::MAX)) - } - - pub fn set_keep_alive_interval(&self, value: Duration) -> Result<(), ErrorCode> { - let fd = &*self.as_std_view()?; - set_keep_alive_interval(fd, core::time::Duration::from_nanos(value))?; - Ok(()) - } - - pub fn keep_alive_count(&self) -> Result { - let fd = &*self.as_std_view()?; - let v = sockopt::tcp_keepcnt(fd)?; - Ok(v) - } - - pub fn set_keep_alive_count(&self, value: u32) -> Result<(), ErrorCode> { - let fd = &*self.as_std_view()?; - set_keep_alive_count(fd, value)?; - Ok(()) - } - - pub fn hop_limit(&self) -> Result { - let fd = &*self.as_std_view()?; - let n = get_unicast_hop_limit(fd, self.family)?; - Ok(n) - } - - pub fn set_hop_limit(&mut self, value: u8) -> Result<(), ErrorCode> { - { - let fd = &*self.as_std_view()?; - set_unicast_hop_limit(fd, self.family, value)?; - } - self.options.set_hop_limit(value); - Ok(()) - } - - pub fn receive_buffer_size(&self) -> Result { - let fd = &*self.as_std_view()?; - let n = receive_buffer_size(fd)?; - Ok(n) - } - - pub fn set_receive_buffer_size(&mut self, value: u64) -> Result<(), ErrorCode> { - let res = { - let fd = &*self.as_std_view()?; - set_receive_buffer_size(fd, value)? - }; - self.options.set_receive_buffer_size(res); - Ok(()) - } - - pub fn send_buffer_size(&self) -> Result { - let fd = &*self.as_std_view()?; - let n = send_buffer_size(fd)?; - Ok(n) - } - - pub fn set_send_buffer_size(&mut self, value: u64) -> Result<(), ErrorCode> { - let res = { - let fd = &*self.as_std_view()?; - set_send_buffer_size(fd, value)? - }; - self.options.set_send_buffer_size(res); - Ok(()) - } -} - -#[cfg(not(target_os = "macos"))] -pub use inherits_option::*; -#[cfg(not(target_os = "macos"))] -mod inherits_option { - use crate::sockets::SocketAddressFamily; - use tokio::net::TcpStream; - - #[derive(Default, Clone)] - pub struct NonInheritedOptions; - - impl NonInheritedOptions { - pub fn set_keep_alive_idle_time(&mut self, _value: u64) {} - - pub fn set_hop_limit(&mut self, _value: u8) {} - - pub fn set_receive_buffer_size(&mut self, _value: usize) {} - - pub fn set_send_buffer_size(&mut self, _value: usize) {} - - pub fn apply(&self, _family: SocketAddressFamily, _stream: &TcpStream) {} - } -} - -#[cfg(target_os = "macos")] -pub use does_not_inherit_options::*; -#[cfg(target_os = "macos")] -mod does_not_inherit_options { - use crate::sockets::SocketAddressFamily; - use rustix::net::sockopt; - use std::sync::Arc; - use std::sync::atomic::{AtomicU8, AtomicU64, AtomicUsize, Ordering::Relaxed}; - use std::time::Duration; - use tokio::net::TcpStream; - - // The socket options below are not automatically inherited from the listener - // on all platforms. So we keep track of which options have been explicitly - // set and manually apply those values to newly accepted clients. - #[derive(Default, Clone)] - pub struct NonInheritedOptions(Arc); - - #[derive(Default)] - struct Inner { - receive_buffer_size: AtomicUsize, - send_buffer_size: AtomicUsize, - hop_limit: AtomicU8, - keep_alive_idle_time: AtomicU64, // nanoseconds - } - - impl NonInheritedOptions { - pub fn set_keep_alive_idle_time(&mut self, value: u64) { - self.0.keep_alive_idle_time.store(value, Relaxed); - } - - pub fn set_hop_limit(&mut self, value: u8) { - self.0.hop_limit.store(value, Relaxed); - } - - pub fn set_receive_buffer_size(&mut self, value: usize) { - self.0.receive_buffer_size.store(value, Relaxed); - } - - pub fn set_send_buffer_size(&mut self, value: usize) { - self.0.send_buffer_size.store(value, Relaxed); - } - - pub fn apply(&self, family: SocketAddressFamily, stream: &TcpStream) { - // Manually inherit socket options from listener. We only have to - // do this on platforms that don't already do this automatically - // and only if a specific value was explicitly set on the listener. - - let receive_buffer_size = self.0.receive_buffer_size.load(Relaxed); - if receive_buffer_size > 0 { - // Ignore potential error. - _ = sockopt::set_socket_recv_buffer_size(&stream, receive_buffer_size); - } - - let send_buffer_size = self.0.send_buffer_size.load(Relaxed); - if send_buffer_size > 0 { - // Ignore potential error. - _ = sockopt::set_socket_send_buffer_size(&stream, send_buffer_size); - } - - // For some reason, IP_TTL is inherited, but IPV6_UNICAST_HOPS isn't. - if family == SocketAddressFamily::Ipv6 { - let hop_limit = self.0.hop_limit.load(Relaxed); - if hop_limit > 0 { - // Ignore potential error. - _ = sockopt::set_ipv6_unicast_hops(&stream, Some(hop_limit)); - } - } - - let keep_alive_idle_time = self.0.keep_alive_idle_time.load(Relaxed); - if keep_alive_idle_time > 0 { - // Ignore potential error. - _ = sockopt::set_tcp_keepidle(&stream, Duration::from_nanos(keep_alive_idle_time)); - } - } - } -} diff --git a/crates/wasi/src/p3/sockets/util.rs b/crates/wasi/src/p3/sockets/util.rs deleted file mode 100644 index 514bf9d3f0..0000000000 --- a/crates/wasi/src/p3/sockets/util.rs +++ /dev/null @@ -1,356 +0,0 @@ -use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; - -use std::net::ToSocketAddrs; - -use rustix::fd::AsFd; -use rustix::io::Errno; -use rustix::net::sockopt; -use tracing::debug; - -use crate::p3::bindings::sockets::types::{self, ErrorCode}; -use crate::p3::sockets::SocketAddressFamily; - -fn is_deprecated_ipv4_compatible(addr: Ipv6Addr) -> bool { - matches!(addr.segments(), [0, 0, 0, 0, 0, 0, _, _]) - && addr != Ipv6Addr::UNSPECIFIED - && addr != Ipv6Addr::LOCALHOST -} - -pub fn is_valid_address_family(addr: IpAddr, socket_family: SocketAddressFamily) -> bool { - match (socket_family, addr) { - (SocketAddressFamily::Ipv4, IpAddr::V4(..)) => true, - (SocketAddressFamily::Ipv6, IpAddr::V6(ipv6)) => { - !is_deprecated_ipv4_compatible(ipv6) && ipv6.to_ipv4_mapped().is_none() - } - _ => false, - } -} - -pub fn is_valid_remote_address(addr: SocketAddr) -> bool { - !addr.ip().to_canonical().is_unspecified() && addr.port() != 0 -} - -pub fn is_valid_unicast_address(addr: IpAddr) -> bool { - match addr.to_canonical() { - IpAddr::V4(ipv4) => !ipv4.is_multicast() && !ipv4.is_broadcast(), - IpAddr::V6(ipv6) => !ipv6.is_multicast(), - } -} - -pub fn to_ipv4_addr(addr: types::Ipv4Address) -> Ipv4Addr { - let (x0, x1, x2, x3) = addr; - Ipv4Addr::new(x0, x1, x2, x3) -} - -pub fn from_ipv4_addr(addr: Ipv4Addr) -> types::Ipv4Address { - let [x0, x1, x2, x3] = addr.octets(); - (x0, x1, x2, x3) -} - -pub fn to_ipv6_addr(addr: types::Ipv6Address) -> Ipv6Addr { - let (x0, x1, x2, x3, x4, x5, x6, x7) = addr; - Ipv6Addr::new(x0, x1, x2, x3, x4, x5, x6, x7) -} - -pub fn from_ipv6_addr(addr: Ipv6Addr) -> types::Ipv6Address { - let [x0, x1, x2, x3, x4, x5, x6, x7] = addr.segments(); - (x0, x1, x2, x3, x4, x5, x6, x7) -} - -pub fn normalize_get_buffer_size(value: usize) -> usize { - if cfg!(target_os = "linux") { - // Linux doubles the value passed to setsockopt to allow space for bookkeeping overhead. - // getsockopt returns this internally doubled value. - // We'll half the value to at least get it back into the same ballpark that the application requested it in. - // - // This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs - value / 2 - } else { - value - } -} - -pub fn normalize_set_buffer_size(value: usize) -> usize { - value.clamp(1, i32::MAX as usize) -} - -impl From for types::IpAddress { - fn from(addr: IpAddr) -> Self { - match addr { - IpAddr::V4(v4) => Self::Ipv4(from_ipv4_addr(v4)), - IpAddr::V6(v6) => Self::Ipv6(from_ipv6_addr(v6)), - } - } -} - -impl From for IpAddr { - fn from(addr: types::IpAddress) -> Self { - match addr { - types::IpAddress::Ipv4(v4) => Self::V4(to_ipv4_addr(v4)), - types::IpAddress::Ipv6(v6) => Self::V6(to_ipv6_addr(v6)), - } - } -} - -impl From for SocketAddr { - fn from(addr: types::IpSocketAddress) -> Self { - match addr { - types::IpSocketAddress::Ipv4(ipv4) => Self::V4(ipv4.into()), - types::IpSocketAddress::Ipv6(ipv6) => Self::V6(ipv6.into()), - } - } -} - -impl From for types::IpSocketAddress { - fn from(addr: SocketAddr) -> Self { - match addr { - SocketAddr::V4(v4) => Self::Ipv4(v4.into()), - SocketAddr::V6(v6) => Self::Ipv6(v6.into()), - } - } -} - -impl From for SocketAddrV4 { - fn from(addr: types::Ipv4SocketAddress) -> Self { - Self::new(to_ipv4_addr(addr.address), addr.port) - } -} - -impl From for types::Ipv4SocketAddress { - fn from(addr: SocketAddrV4) -> Self { - Self { - address: from_ipv4_addr(*addr.ip()), - port: addr.port(), - } - } -} - -impl From for SocketAddrV6 { - fn from(addr: types::Ipv6SocketAddress) -> Self { - Self::new( - to_ipv6_addr(addr.address), - addr.port, - addr.flow_info, - addr.scope_id, - ) - } -} - -impl From for types::Ipv6SocketAddress { - fn from(addr: SocketAddrV6) -> Self { - Self { - address: from_ipv6_addr(*addr.ip()), - port: addr.port(), - flow_info: addr.flowinfo(), - scope_id: addr.scope_id(), - } - } -} - -impl ToSocketAddrs for types::IpSocketAddress { - type Iter = ::Iter; - - fn to_socket_addrs(&self) -> std::io::Result { - SocketAddr::from(*self).to_socket_addrs() - } -} - -impl ToSocketAddrs for types::Ipv4SocketAddress { - type Iter = ::Iter; - - fn to_socket_addrs(&self) -> std::io::Result { - SocketAddrV4::from(*self).to_socket_addrs() - } -} - -impl ToSocketAddrs for types::Ipv6SocketAddress { - type Iter = ::Iter; - - fn to_socket_addrs(&self) -> std::io::Result { - SocketAddrV6::from(*self).to_socket_addrs() - } -} - -impl From for cap_net_ext::AddressFamily { - fn from(family: types::IpAddressFamily) -> Self { - match family { - types::IpAddressFamily::Ipv4 => Self::Ipv4, - types::IpAddressFamily::Ipv6 => Self::Ipv6, - } - } -} - -impl From for types::IpAddressFamily { - fn from(family: cap_net_ext::AddressFamily) -> Self { - match family { - cap_net_ext::AddressFamily::Ipv4 => Self::Ipv4, - cap_net_ext::AddressFamily::Ipv6 => Self::Ipv6, - } - } -} - -impl From for types::ErrorCode { - fn from(value: std::io::Error) -> Self { - (&value).into() - } -} - -impl From<&std::io::Error> for types::ErrorCode { - fn from(value: &std::io::Error) -> Self { - // Attempt the more detailed native error code first: - if let Some(errno) = Errno::from_io_error(value) { - return errno.into(); - } - - match value.kind() { - std::io::ErrorKind::AddrInUse => Self::AddressInUse, - std::io::ErrorKind::AddrNotAvailable => Self::AddressNotBindable, - std::io::ErrorKind::ConnectionAborted => Self::ConnectionAborted, - std::io::ErrorKind::ConnectionRefused => Self::ConnectionRefused, - std::io::ErrorKind::ConnectionReset => Self::ConnectionReset, - std::io::ErrorKind::InvalidInput => Self::InvalidArgument, - std::io::ErrorKind::NotConnected => Self::InvalidState, - std::io::ErrorKind::OutOfMemory => Self::OutOfMemory, - std::io::ErrorKind::PermissionDenied => Self::AccessDenied, - std::io::ErrorKind::TimedOut => Self::Timeout, - std::io::ErrorKind::Unsupported => Self::NotSupported, - _ => { - debug!("unknown I/O error: {value}"); - Self::Unknown - } - } - } -} - -impl From for types::ErrorCode { - fn from(value: Errno) -> Self { - (&value).into() - } -} - -impl From<&Errno> for types::ErrorCode { - fn from(value: &Errno) -> Self { - match *value { - #[cfg(not(windows))] - Errno::PERM => Self::AccessDenied, - Errno::ACCESS => Self::AccessDenied, - Errno::ADDRINUSE => Self::AddressInUse, - Errno::ADDRNOTAVAIL => Self::AddressNotBindable, - Errno::TIMEDOUT => Self::Timeout, - Errno::CONNREFUSED => Self::ConnectionRefused, - Errno::CONNRESET => Self::ConnectionReset, - Errno::CONNABORTED => Self::ConnectionAborted, - Errno::INVAL => Self::InvalidArgument, - Errno::HOSTUNREACH => Self::RemoteUnreachable, - Errno::HOSTDOWN => Self::RemoteUnreachable, - Errno::NETDOWN => Self::RemoteUnreachable, - Errno::NETUNREACH => Self::RemoteUnreachable, - #[cfg(target_os = "linux")] - Errno::NONET => Self::RemoteUnreachable, - Errno::ISCONN => Self::InvalidState, - Errno::NOTCONN => Self::InvalidState, - Errno::DESTADDRREQ => Self::InvalidState, - Errno::MSGSIZE => Self::DatagramTooLarge, - #[cfg(not(windows))] - Errno::NOMEM => Self::OutOfMemory, - Errno::NOBUFS => Self::OutOfMemory, - Errno::OPNOTSUPP => Self::NotSupported, - Errno::NOPROTOOPT => Self::NotSupported, - Errno::PFNOSUPPORT => Self::NotSupported, - Errno::PROTONOSUPPORT => Self::NotSupported, - Errno::PROTOTYPE => Self::NotSupported, - Errno::SOCKTNOSUPPORT => Self::NotSupported, - Errno::AFNOSUPPORT => Self::NotSupported, - - // FYI, EINPROGRESS should have already been handled by connect. - _ => { - debug!("unknown I/O error: {value}"); - Self::Unknown - } - } - } -} - -pub fn get_ip_ttl(fd: impl AsFd) -> Result { - let v = sockopt::ip_ttl(fd)?; - let Ok(v) = v.try_into() else { - return Err(ErrorCode::NotSupported); - }; - Ok(v) -} - -pub fn get_ipv6_unicast_hops(fd: impl AsFd) -> Result { - let v = sockopt::ipv6_unicast_hops(fd)?; - Ok(v) -} - -pub fn get_unicast_hop_limit(fd: impl AsFd, family: SocketAddressFamily) -> Result { - match family { - SocketAddressFamily::Ipv4 => get_ip_ttl(fd), - SocketAddressFamily::Ipv6 => get_ipv6_unicast_hops(fd), - } -} - -pub fn set_unicast_hop_limit( - fd: impl AsFd, - family: SocketAddressFamily, - value: u8, -) -> Result<(), ErrorCode> { - if value == 0 { - // WIT: "If the provided value is 0, an `invalid-argument` error is returned." - // - // A well-behaved IP application should never send out new packets with TTL 0. - // We validate the value ourselves because OS'es are not consistent in this. - // On Linux the validation is even inconsistent between their IPv4 and IPv6 implementation. - return Err(ErrorCode::InvalidArgument); - } - match family { - SocketAddressFamily::Ipv4 => { - sockopt::set_ip_ttl(fd, value.into())?; - } - SocketAddressFamily::Ipv6 => { - sockopt::set_ipv6_unicast_hops(fd, Some(value))?; - } - } - Ok(()) -} - -pub fn receive_buffer_size(fd: impl AsFd) -> Result { - let v = sockopt::socket_recv_buffer_size(fd)?; - Ok(normalize_get_buffer_size(v).try_into().unwrap_or(u64::MAX)) -} - -pub fn set_receive_buffer_size(fd: impl AsFd, value: u64) -> Result { - if value == 0 { - // WIT: "If the provided value is 0, an `invalid-argument` error is returned." - return Err(ErrorCode::InvalidArgument); - } - let value = value.try_into().unwrap_or(usize::MAX); - let value = normalize_set_buffer_size(value); - match sockopt::set_socket_recv_buffer_size(fd, value) { - Err(Errno::NOBUFS) => {} - Err(err) => return Err(err.into()), - _ => {} - }; - Ok(value) -} - -pub fn send_buffer_size(fd: impl AsFd) -> Result { - let v = sockopt::socket_send_buffer_size(fd)?; - Ok(normalize_get_buffer_size(v).try_into().unwrap_or(u64::MAX)) -} - -pub fn set_send_buffer_size(fd: impl AsFd, value: u64) -> Result { - if value == 0 { - // WIT: "If the provided value is 0, an `invalid-argument` error is returned." - return Err(ErrorCode::InvalidArgument); - } - let value = value.try_into().unwrap_or(usize::MAX); - let value = normalize_set_buffer_size(value); - match sockopt::set_socket_send_buffer_size(fd, value) { - Err(Errno::NOBUFS) => {} - Err(err) => return Err(err.into()), - _ => {} - }; - Ok(value) -} diff --git a/crates/wasi/src/p3/wit/deps/sockets/types.wit b/crates/wasi/src/p3/wit/deps/sockets/types.wit index 7cfb739596..86315314fc 100644 --- a/crates/wasi/src/p3/wit/deps/sockets/types.wit +++ b/crates/wasi/src/p3/wit/deps/sockets/types.wit @@ -125,7 +125,7 @@ interface types { /// - `connecting` /// - `connected` /// - `closed` - /// See + /// See /// for more information. /// /// Note: Except where explicitly mentioned, whenever this documentation uses @@ -174,10 +174,10 @@ interface types { /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) /// - `address-in-use`: Address is already in use. (EADDRINUSE) /// - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) - /// + /// /// # Implementors note /// When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT - /// state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR + /// state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR /// socket option should be set implicitly on all platforms, except on Windows where this is the default behavior /// and SO_REUSEADDR performs something different entirely. /// diff --git a/crates/wasi/src/p3/wit/package.wit b/crates/wasi/src/p3/wit/package.wit index c223b458e8..74db4ae942 100644 --- a/crates/wasi/src/p3/wit/package.wit +++ b/crates/wasi/src/p3/wit/package.wit @@ -1 +1,2 @@ +// We actually don't use this; it's just to let bindgen! find the corresponding world in wit/deps. package wasmtime:wasi; diff --git a/crates/wasi/src/sockets/mod.rs b/crates/wasi/src/sockets/mod.rs index 463c80adca..3783895814 100644 --- a/crates/wasi/src/sockets/mod.rs +++ b/crates/wasi/src/sockets/mod.rs @@ -1,26 +1,37 @@ use core::future::Future; use core::ops::Deref; - use std::net::SocketAddr; use std::pin::Pin; use std::sync::Arc; +use wasmtime::component::{HasData, ResourceTable}; +mod tcp; +mod udp; pub(crate) mod util; -use wasmtime::component::ResourceTable; +#[cfg(feature = "p3")] +pub(crate) use tcp::NonInheritedOptions; +pub use tcp::TcpSocket; +pub use udp::UdpSocket; + +pub(crate) struct WasiSockets; + +impl HasData for WasiSockets { + type Data<'a> = WasiSocketsCtxView<'a>; +} /// Value taken from rust std library. -pub const DEFAULT_TCP_BACKLOG: u32 = 128; +pub(crate) const DEFAULT_TCP_BACKLOG: u32 = 128; /// Theoretical maximum byte size of a UDP datagram, the real limit is lower, /// but we do not account for e.g. the transport layer here for simplicity. /// In practice, datagrams are typically less than 1500 bytes. -pub const MAX_UDP_DATAGRAM_SIZE: usize = u16::MAX as usize; +pub(crate) const MAX_UDP_DATAGRAM_SIZE: usize = u16::MAX as usize; #[derive(Clone, Default)] pub struct WasiSocketsCtx { - pub socket_addr_check: SocketAddrCheck, - pub allowed_network_uses: AllowedNetworkUses, + pub(crate) socket_addr_check: SocketAddrCheck, + pub(crate) allowed_network_uses: AllowedNetworkUses, } pub struct WasiSocketsCtxView<'a> { @@ -33,10 +44,10 @@ pub trait WasiSocketsView: Send { } #[derive(Copy, Clone)] -pub struct AllowedNetworkUses { - pub ip_name_lookup: bool, - pub udp: bool, - pub tcp: bool, +pub(crate) struct AllowedNetworkUses { + pub(crate) ip_name_lookup: bool, + pub(crate) udp: bool, + pub(crate) tcp: bool, } impl Default for AllowedNetworkUses { @@ -75,8 +86,8 @@ impl AllowedNetworkUses { /// A check that will be called for each socket address that is used of whether the address is permitted. #[derive(Clone)] -pub struct SocketAddrCheck( - pub(crate) Arc< +pub(crate) struct SocketAddrCheck( + Arc< dyn Fn(SocketAddr, SocketAddrUse) -> Pin + Send + Sync>> + Send + Sync, @@ -88,7 +99,7 @@ impl SocketAddrCheck { /// /// Returning `true` will permit socket connections to the `SocketAddr`, /// while returning `false` will reject the connection. - pub fn new( + pub(crate) fn new( f: impl Fn(SocketAddr, SocketAddrUse) -> Pin + Send + Sync>> + Send + Sync @@ -97,7 +108,11 @@ impl SocketAddrCheck { Self(Arc::new(f)) } - pub async fn check(&self, addr: SocketAddr, reason: SocketAddrUse) -> std::io::Result<()> { + pub(crate) async fn check( + &self, + addr: SocketAddr, + reason: SocketAddrUse, + ) -> std::io::Result<()> { if (self.0)(addr, reason).await { Ok(()) } else { @@ -141,7 +156,7 @@ pub enum SocketAddrUse { } #[derive(Copy, Clone, Eq, PartialEq)] -pub enum SocketAddressFamily { +pub(crate) enum SocketAddressFamily { Ipv4, Ipv6, } diff --git a/crates/wasi/src/sockets/tcp.rs b/crates/wasi/src/sockets/tcp.rs new file mode 100644 index 0000000000..09dd2ba8de --- /dev/null +++ b/crates/wasi/src/sockets/tcp.rs @@ -0,0 +1,816 @@ +use crate::p2::P2TcpStreamingState; +use crate::runtime::with_ambient_tokio_runtime; +use crate::sockets::util::{ + ErrorCode, get_unicast_hop_limit, is_valid_address_family, is_valid_remote_address, + is_valid_unicast_address, receive_buffer_size, send_buffer_size, set_keep_alive_count, + set_keep_alive_idle_time, set_keep_alive_interval, set_receive_buffer_size, + set_send_buffer_size, set_unicast_hop_limit, tcp_bind, +}; +use crate::sockets::{DEFAULT_TCP_BACKLOG, SocketAddressFamily, WasiSocketsCtx}; +use io_lifetimes::AsSocketlike as _; +use io_lifetimes::views::SocketlikeView; +use rustix::io::Errno; +use rustix::net::sockopt; +use std::fmt::Debug; +use std::io; +use std::mem; +use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll, Waker}; +use std::time::Duration; + +/// The state of a TCP socket. +/// +/// This represents the various states a socket can be in during the +/// activities of binding, listening, accepting, and connecting. Note that this +/// state machine encompasses both WASIp2 and WASIp3. +enum TcpState { + /// The initial state for a newly-created socket. + /// + /// From here a socket can transition to `BindStarted`, `ListenStarted`, or + /// `Connecting`. + Default(tokio::net::TcpSocket), + + /// A state indicating that a bind has been started and must be finished + /// subsequently with `finish_bind`. + /// + /// From here a socket can transition to `Bound`. + BindStarted(tokio::net::TcpSocket), + + /// Binding finished. The socket has an address but is not yet listening for + /// connections. + /// + /// From here a socket can transition to `ListenStarted`, or `Connecting`. + Bound(tokio::net::TcpSocket), + + /// Listening on a socket has started and must be completed with + /// `finish_listen`. + /// + /// From here a socket can transition to `Listening`. + ListenStarted(tokio::net::TcpSocket), + + /// The socket is now listening and waiting for an incoming connection. + /// + /// Sockets will not leave this state. + Listening { + /// The raw tokio-basd TCP listener managing the underyling socket. + listener: Arc, + + /// The last-accepted connection, set during the `ready` method and read + /// during the `accept` method. Note that this is only used for WASIp2 + /// at this time. + pending_accept: Option>, + }, + + /// An outgoing connection is started. + /// + /// This is created via the `start_connect` method. The payload here is an + /// optionally-specified owned future for the result of the connect. In + /// WASIp2 the future lives here, but in WASIp3 it lives on the event loop + /// so this is `None`. + /// + /// From here a socket can transition to `ConnectReady` or `Connected`. + Connecting(Option> + Send>>>), + + /// A connection via `Connecting` has completed. + /// + /// This is present for WASIp2 where the `Connecting` state stores `Some` of + /// a future, and the result of that future is recorded here when it + /// finishes as part of the `ready` method. + /// + /// From here a socket can transition to `Connected`. + ConnectReady(io::Result), + + /// A connection has been established. + /// + /// This is created either via `finish_connect` or for freshly accepted + /// sockets from a TCP listener. + /// + /// From here a socket can transition to `Receiving` or `P2Streaming`. + Connected(Arc), + + /// A connection has been established and `receive` has been called. + /// + /// A socket will not transition out of this state. + #[cfg(feature = "p3")] + Receiving(Arc), + + /// This is a WASIp2-bound socket which stores some extra state for + /// read/write streams to handle TCP shutdown. + /// + /// A socket will not transition out of this state. + P2Streaming(Box), + + /// This is not actually a socket but a deferred error. + /// + /// This error came out of `accept` and is deferred until the socket is + /// operated on. + #[cfg(feature = "p3")] + Error(io::Error), + + /// The socket is closed and no more operations can be performed. + Closed, +} + +impl Debug for TcpState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Default(_) => f.debug_tuple("Default").finish(), + Self::BindStarted(_) => f.debug_tuple("BindStarted").finish(), + Self::Bound(_) => f.debug_tuple("Bound").finish(), + Self::ListenStarted { .. } => f.debug_tuple("ListenStarted").finish(), + Self::Listening { .. } => f.debug_tuple("Listening").finish(), + Self::Connecting(..) => f.debug_tuple("Connecting").finish(), + Self::ConnectReady(..) => f.debug_tuple("ConnectReady").finish(), + Self::Connected { .. } => f.debug_tuple("Connected").finish(), + #[cfg(feature = "p3")] + Self::Receiving { .. } => f.debug_tuple("Receiving").finish(), + Self::P2Streaming(_) => f.debug_tuple("P2Streaming").finish(), + #[cfg(feature = "p3")] + Self::Error(..) => f.debug_tuple("Error").finish(), + Self::Closed => write!(f, "Closed"), + } + } +} + +/// A host TCP socket, plus associated bookkeeping. +pub struct TcpSocket { + /// The current state in the bind/listen/accept/connect progression. + tcp_state: TcpState, + + /// The desired listen queue size. + listen_backlog_size: u32, + + family: SocketAddressFamily, + + options: NonInheritedOptions, +} + +impl TcpSocket { + /// Create a new socket in the given family. + pub(crate) fn new(ctx: &WasiSocketsCtx, family: SocketAddressFamily) -> std::io::Result { + ctx.allowed_network_uses.check_allowed_tcp()?; + + with_ambient_tokio_runtime(|| { + let socket = match family { + SocketAddressFamily::Ipv4 => tokio::net::TcpSocket::new_v4()?, + SocketAddressFamily::Ipv6 => { + let socket = tokio::net::TcpSocket::new_v6()?; + sockopt::set_ipv6_v6only(&socket, true)?; + socket + } + }; + + Ok(Self::from_state(TcpState::Default(socket), family)) + }) + } + + #[cfg(feature = "p3")] + pub(crate) fn new_error(err: io::Error, family: SocketAddressFamily) -> Self { + TcpSocket::from_state(TcpState::Error(err), family) + } + + /// Creates a new socket with the `result` of an accepted socket from a + /// `TcpListener`. + /// + /// This will handle the `result` internally and `result` should be the raw + /// result from a TCP listen operation. + pub(crate) fn new_accept( + result: io::Result, + options: &NonInheritedOptions, + family: SocketAddressFamily, + ) -> io::Result { + let client = result.map_err(|err| match Errno::from_io_error(&err) { + // From: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept#:~:text=WSAEINPROGRESS + // > WSAEINPROGRESS: A blocking Windows Sockets 1.1 call is in progress, + // > or the service provider is still processing a callback function. + // + // wasi-sockets doesn't have an equivalent to the EINPROGRESS error, + // because in POSIX this error is only returned by a non-blocking + // `connect` and wasi-sockets has a different solution for that. + #[cfg(windows)] + Some(Errno::INPROGRESS) => Errno::INTR.into(), + + // Normalize Linux' non-standard behavior. + // + // From https://man7.org/linux/man-pages/man2/accept.2.html: + // > Linux accept() passes already-pending network errors on the + // > new socket as an error code from accept(). This behavior + // > differs from other BSD socket implementations. (...) + #[cfg(target_os = "linux")] + Some( + Errno::CONNRESET + | Errno::NETRESET + | Errno::HOSTUNREACH + | Errno::HOSTDOWN + | Errno::NETDOWN + | Errno::NETUNREACH + | Errno::PROTO + | Errno::NOPROTOOPT + | Errno::NONET + | Errno::OPNOTSUPP, + ) => Errno::CONNABORTED.into(), + + _ => err, + })?; + options.apply(family, &client); + Ok(Self::from_state( + TcpState::Connected(Arc::new(client)), + family, + )) + } + + /// Create a `TcpSocket` from an existing socket. + fn from_state(state: TcpState, family: SocketAddressFamily) -> Self { + Self { + tcp_state: state, + listen_backlog_size: DEFAULT_TCP_BACKLOG, + family, + options: Default::default(), + } + } + + pub(crate) fn as_std_view(&self) -> Result, ErrorCode> { + match &self.tcp_state { + TcpState::Default(socket) + | TcpState::BindStarted(socket) + | TcpState::Bound(socket) + | TcpState::ListenStarted(socket) => Ok(socket.as_socketlike_view()), + TcpState::Connected(stream) => Ok(stream.as_socketlike_view()), + #[cfg(feature = "p3")] + TcpState::Receiving(stream) => Ok(stream.as_socketlike_view()), + TcpState::Listening { listener, .. } => Ok(listener.as_socketlike_view()), + TcpState::P2Streaming(state) => Ok(state.stream.as_socketlike_view()), + TcpState::Connecting(..) | TcpState::ConnectReady(_) | TcpState::Closed => { + Err(ErrorCode::InvalidState) + } + #[cfg(feature = "p3")] + TcpState::Error(err) => Err(err.into()), + } + } + + pub(crate) fn start_bind(&mut self, addr: SocketAddr) -> Result<(), ErrorCode> { + let ip = addr.ip(); + if !is_valid_unicast_address(ip) || !is_valid_address_family(ip, self.family) { + return Err(ErrorCode::InvalidArgument); + } + match mem::replace(&mut self.tcp_state, TcpState::Closed) { + TcpState::Default(sock) => { + if let Err(err) = tcp_bind(&sock, addr) { + self.tcp_state = TcpState::Default(sock); + Err(err) + } else { + self.tcp_state = TcpState::BindStarted(sock); + Ok(()) + } + } + tcp_state => { + self.tcp_state = tcp_state; + Err(ErrorCode::InvalidState) + } + } + } + + pub(crate) fn finish_bind(&mut self) -> Result<(), ErrorCode> { + match mem::replace(&mut self.tcp_state, TcpState::Closed) { + TcpState::BindStarted(socket) => { + self.tcp_state = TcpState::Bound(socket); + Ok(()) + } + current_state => { + // Reset the state so that the outside world doesn't see this socket as closed + self.tcp_state = current_state; + Err(ErrorCode::NotInProgress) + } + } + } + + pub(crate) fn start_connect( + &mut self, + addr: &SocketAddr, + ) -> Result { + match self.tcp_state { + TcpState::Default(..) | TcpState::Bound(..) => {} + TcpState::Connecting(..) => { + return Err(ErrorCode::ConcurrencyConflict); + } + _ => return Err(ErrorCode::InvalidState), + }; + + if !is_valid_unicast_address(addr.ip()) + || !is_valid_remote_address(*addr) + || !is_valid_address_family(addr.ip(), self.family) + { + return Err(ErrorCode::InvalidArgument); + }; + + let (TcpState::Default(tokio_socket) | TcpState::Bound(tokio_socket)) = + mem::replace(&mut self.tcp_state, TcpState::Connecting(None)) + else { + unreachable!(); + }; + + Ok(tokio_socket) + } + + /// For WASIp2 this is used to record the actual connection future as part + /// of `start_connect` within this socket state. + pub(crate) fn set_pending_connect( + &mut self, + future: impl Future> + Send + 'static, + ) -> Result<(), ErrorCode> { + match &mut self.tcp_state { + TcpState::Connecting(slot @ None) => { + *slot = Some(Box::pin(future)); + Ok(()) + } + _ => Err(ErrorCode::InvalidState), + } + } + + /// For WASIp2 this retreives the result from the future passed to + /// `set_pending_connect`. + /// + /// Return states here are: + /// + /// * `Ok(Some(res))` - where `res` is the result of the connect operation. + /// * `Ok(None)` - the connect operation isn't ready yet. + /// * `Err(e)` - a connect operation is not in progress. + pub(crate) fn take_pending_connect( + &mut self, + ) -> Result>, ErrorCode> { + match mem::replace(&mut self.tcp_state, TcpState::Connecting(None)) { + TcpState::ConnectReady(result) => Ok(Some(result)), + TcpState::Connecting(Some(mut future)) => { + let mut cx = Context::from_waker(Waker::noop()); + match with_ambient_tokio_runtime(|| future.as_mut().poll(&mut cx)) { + Poll::Ready(result) => Ok(Some(result)), + Poll::Pending => { + self.tcp_state = TcpState::Connecting(Some(future)); + Ok(None) + } + } + } + current_state => { + self.tcp_state = current_state; + Err(ErrorCode::NotInProgress) + } + } + } + + pub(crate) fn finish_connect( + &mut self, + result: io::Result, + ) -> Result<(), ErrorCode> { + if !matches!(self.tcp_state, TcpState::Connecting(None)) { + return Err(ErrorCode::InvalidState); + } + match result { + Ok(stream) => { + self.tcp_state = TcpState::Connected(Arc::new(stream)); + Ok(()) + } + Err(err) => { + self.tcp_state = TcpState::Closed; + Err(ErrorCode::from(err)) + } + } + } + + pub(crate) fn start_listen(&mut self) -> Result<(), ErrorCode> { + match mem::replace(&mut self.tcp_state, TcpState::Closed) { + TcpState::Bound(tokio_socket) => { + self.tcp_state = TcpState::ListenStarted(tokio_socket); + Ok(()) + } + previous_state => { + self.tcp_state = previous_state; + Err(ErrorCode::InvalidState) + } + } + } + + pub(crate) fn finish_listen(&mut self) -> Result<(), ErrorCode> { + let tokio_socket = match mem::replace(&mut self.tcp_state, TcpState::Closed) { + TcpState::ListenStarted(tokio_socket) => tokio_socket, + previous_state => { + self.tcp_state = previous_state; + return Err(ErrorCode::NotInProgress); + } + }; + + match with_ambient_tokio_runtime(|| tokio_socket.listen(self.listen_backlog_size)) { + Ok(listener) => { + self.tcp_state = TcpState::Listening { + listener: Arc::new(listener), + pending_accept: None, + }; + Ok(()) + } + Err(err) => { + self.tcp_state = TcpState::Closed; + + Err(match Errno::from_io_error(&err) { + // See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen#:~:text=WSAEMFILE + // According to the docs, `listen` can return EMFILE on Windows. + // This is odd, because we're not trying to create a new socket + // or file descriptor of any kind. So we rewrite it to less + // surprising error code. + // + // At the time of writing, this behavior has never been experimentally + // observed by any of the wasmtime authors, so we're relying fully + // on Microsoft's documentation here. + #[cfg(windows)] + Some(Errno::MFILE) => Errno::NOBUFS.into(), + + _ => err.into(), + }) + } + } + } + + pub(crate) fn accept(&mut self) -> Result, ErrorCode> { + let TcpState::Listening { + listener, + pending_accept, + } = &mut self.tcp_state + else { + return Err(ErrorCode::InvalidState); + }; + + let result = match pending_accept.take() { + Some(result) => result, + None => { + let mut cx = std::task::Context::from_waker(Waker::noop()); + match with_ambient_tokio_runtime(|| listener.poll_accept(&mut cx)) + .map_ok(|(stream, _)| stream) + { + Poll::Ready(result) => result, + Poll::Pending => return Ok(None), + } + } + }; + + Ok(Some(Self::new_accept(result, &self.options, self.family)?)) + } + + #[cfg(feature = "p3")] + pub(crate) fn start_receive(&mut self) -> Option<&Arc> { + match mem::replace(&mut self.tcp_state, TcpState::Closed) { + TcpState::Connected(stream) => { + self.tcp_state = TcpState::Receiving(stream); + Some(self.tcp_stream_arc().unwrap()) + } + prev => { + self.tcp_state = prev; + None + } + } + } + + pub(crate) fn local_address(&self) -> Result { + match &self.tcp_state { + TcpState::Bound(socket) => Ok(socket.local_addr()?), + TcpState::Connected(stream) => Ok(stream.local_addr()?), + #[cfg(feature = "p3")] + TcpState::Receiving(stream) => Ok(stream.local_addr()?), + TcpState::P2Streaming(state) => Ok(state.stream.local_addr()?), + TcpState::Listening { listener, .. } => Ok(listener.local_addr()?), + #[cfg(feature = "p3")] + TcpState::Error(err) => Err(err.into()), + _ => Err(ErrorCode::InvalidState), + } + } + + pub(crate) fn remote_address(&self) -> Result { + let stream = self.tcp_stream_arc()?; + let addr = stream.peer_addr()?; + Ok(addr) + } + + pub(crate) fn is_listening(&self) -> bool { + matches!(self.tcp_state, TcpState::Listening { .. }) + } + + pub(crate) fn address_family(&self) -> SocketAddressFamily { + self.family + } + + pub(crate) fn set_listen_backlog_size(&mut self, value: u64) -> Result<(), ErrorCode> { + const MIN_BACKLOG: u32 = 1; + const MAX_BACKLOG: u32 = i32::MAX as u32; // OS'es will most likely limit it down even further. + + if value == 0 { + return Err(ErrorCode::InvalidArgument); + } + // Silently clamp backlog size. This is OK for us to do, because operating systems do this too. + let value = value + .try_into() + .unwrap_or(MAX_BACKLOG) + .clamp(MIN_BACKLOG, MAX_BACKLOG); + match &self.tcp_state { + TcpState::Default(..) | TcpState::Bound(..) => { + // Socket not listening yet. Stash value for first invocation to `listen`. + self.listen_backlog_size = value; + Ok(()) + } + TcpState::Listening { listener, .. } => { + // Try to update the backlog by calling `listen` again. + // Not all platforms support this. We'll only update our own value if the OS supports changing the backlog size after the fact. + if rustix::net::listen(&listener, value.try_into().unwrap_or(i32::MAX)).is_err() { + return Err(ErrorCode::NotSupported); + } + self.listen_backlog_size = value; + Ok(()) + } + #[cfg(feature = "p3")] + TcpState::Error(err) => Err(err.into()), + _ => Err(ErrorCode::InvalidState), + } + } + + pub(crate) fn keep_alive_enabled(&self) -> Result { + let fd = &*self.as_std_view()?; + let v = sockopt::socket_keepalive(fd)?; + Ok(v) + } + + pub(crate) fn set_keep_alive_enabled(&self, value: bool) -> Result<(), ErrorCode> { + let fd = &*self.as_std_view()?; + sockopt::set_socket_keepalive(fd, value)?; + Ok(()) + } + + pub(crate) fn keep_alive_idle_time(&self) -> Result { + let fd = &*self.as_std_view()?; + let v = sockopt::tcp_keepidle(fd)?; + Ok(v.as_nanos().try_into().unwrap_or(u64::MAX)) + } + + pub(crate) fn set_keep_alive_idle_time(&mut self, value: u64) -> Result<(), ErrorCode> { + let value = { + let fd = self.as_std_view()?; + set_keep_alive_idle_time(&*fd, value)? + }; + self.options.set_keep_alive_idle_time(value); + Ok(()) + } + + pub(crate) fn keep_alive_interval(&self) -> Result { + let fd = &*self.as_std_view()?; + let v = sockopt::tcp_keepintvl(fd)?; + Ok(v.as_nanos().try_into().unwrap_or(u64::MAX)) + } + + pub(crate) fn set_keep_alive_interval(&self, value: u64) -> Result<(), ErrorCode> { + let fd = &*self.as_std_view()?; + set_keep_alive_interval(fd, Duration::from_nanos(value))?; + Ok(()) + } + + pub(crate) fn keep_alive_count(&self) -> Result { + let fd = &*self.as_std_view()?; + let v = sockopt::tcp_keepcnt(fd)?; + Ok(v) + } + + pub(crate) fn set_keep_alive_count(&self, value: u32) -> Result<(), ErrorCode> { + let fd = &*self.as_std_view()?; + set_keep_alive_count(fd, value)?; + Ok(()) + } + + pub(crate) fn hop_limit(&self) -> Result { + let fd = &*self.as_std_view()?; + let n = get_unicast_hop_limit(fd, self.family)?; + Ok(n) + } + + pub(crate) fn set_hop_limit(&mut self, value: u8) -> Result<(), ErrorCode> { + { + let fd = &*self.as_std_view()?; + set_unicast_hop_limit(fd, self.family, value)?; + } + self.options.set_hop_limit(value); + Ok(()) + } + + pub(crate) fn receive_buffer_size(&self) -> Result { + let fd = &*self.as_std_view()?; + let n = receive_buffer_size(fd)?; + Ok(n) + } + + pub(crate) fn set_receive_buffer_size(&mut self, value: u64) -> Result<(), ErrorCode> { + let res = { + let fd = &*self.as_std_view()?; + set_receive_buffer_size(fd, value)? + }; + self.options.set_receive_buffer_size(res); + Ok(()) + } + + pub(crate) fn send_buffer_size(&self) -> Result { + let fd = &*self.as_std_view()?; + let n = send_buffer_size(fd)?; + Ok(n) + } + + pub(crate) fn set_send_buffer_size(&mut self, value: u64) -> Result<(), ErrorCode> { + let res = { + let fd = &*self.as_std_view()?; + set_send_buffer_size(fd, value)? + }; + self.options.set_send_buffer_size(res); + Ok(()) + } + + #[cfg(feature = "p3")] + pub(crate) fn non_inherited_options(&self) -> &NonInheritedOptions { + &self.options + } + + #[cfg(feature = "p3")] + pub(crate) fn tcp_listener_arc(&self) -> Result<&Arc, ErrorCode> { + match &self.tcp_state { + TcpState::Listening { listener, .. } => Ok(listener), + #[cfg(feature = "p3")] + TcpState::Error(err) => Err(err.into()), + _ => Err(ErrorCode::InvalidState), + } + } + + pub(crate) fn tcp_stream_arc(&self) -> Result<&Arc, ErrorCode> { + match &self.tcp_state { + TcpState::Connected(socket) => Ok(socket), + #[cfg(feature = "p3")] + TcpState::Receiving(socket) => Ok(socket), + TcpState::P2Streaming(state) => Ok(&state.stream), + #[cfg(feature = "p3")] + TcpState::Error(err) => Err(err.into()), + _ => Err(ErrorCode::InvalidState), + } + } + + pub(crate) fn p2_streaming_state(&self) -> Result<&P2TcpStreamingState, ErrorCode> { + match &self.tcp_state { + TcpState::P2Streaming(state) => Ok(state), + #[cfg(feature = "p3")] + TcpState::Error(err) => Err(err.into()), + _ => Err(ErrorCode::InvalidState), + } + } + + pub(crate) fn set_p2_streaming_state( + &mut self, + state: P2TcpStreamingState, + ) -> Result<(), ErrorCode> { + if !matches!(self.tcp_state, TcpState::Connected(_)) { + return Err(ErrorCode::InvalidState); + } + self.tcp_state = TcpState::P2Streaming(Box::new(state)); + Ok(()) + } + + /// Used for `Pollable` in the WASIp2 implementation this awaits the socket + /// to be connected, if in the connecting state, or for a TCP accept to be + /// ready, if this is in the listening state. + /// + /// For all other states this method immediately returns. + pub(crate) async fn ready(&mut self) { + match &mut self.tcp_state { + TcpState::Default(..) + | TcpState::BindStarted(..) + | TcpState::Bound(..) + | TcpState::ListenStarted(..) + | TcpState::ConnectReady(..) + | TcpState::Closed + | TcpState::Connected { .. } + | TcpState::Connecting(None) + | TcpState::Listening { + pending_accept: Some(_), + .. + } + | TcpState::P2Streaming(_) => {} + + #[cfg(feature = "p3")] + TcpState::Receiving(_) | TcpState::Error(_) => {} + + TcpState::Connecting(Some(future)) => { + self.tcp_state = TcpState::ConnectReady(future.as_mut().await); + } + + TcpState::Listening { + listener, + pending_accept: slot @ None, + } => { + let result = futures::future::poll_fn(|cx| { + listener.poll_accept(cx).map_ok(|(stream, _)| stream) + }) + .await; + *slot = Some(result); + } + } + } +} + +#[cfg(not(target_os = "macos"))] +pub use inherits_option::*; +#[cfg(not(target_os = "macos"))] +mod inherits_option { + use crate::sockets::SocketAddressFamily; + use tokio::net::TcpStream; + + #[derive(Default, Clone)] + pub struct NonInheritedOptions; + + impl NonInheritedOptions { + pub fn set_keep_alive_idle_time(&mut self, _value: u64) {} + + pub fn set_hop_limit(&mut self, _value: u8) {} + + pub fn set_receive_buffer_size(&mut self, _value: usize) {} + + pub fn set_send_buffer_size(&mut self, _value: usize) {} + + pub(crate) fn apply(&self, _family: SocketAddressFamily, _stream: &TcpStream) {} + } +} + +#[cfg(target_os = "macos")] +pub use does_not_inherit_options::*; +#[cfg(target_os = "macos")] +mod does_not_inherit_options { + use crate::sockets::SocketAddressFamily; + use rustix::net::sockopt; + use std::sync::Arc; + use std::sync::atomic::{AtomicU8, AtomicU64, AtomicUsize, Ordering::Relaxed}; + use std::time::Duration; + use tokio::net::TcpStream; + + // The socket options below are not automatically inherited from the listener + // on all platforms. So we keep track of which options have been explicitly + // set and manually apply those values to newly accepted clients. + #[derive(Default, Clone)] + pub struct NonInheritedOptions(Arc); + + #[derive(Default)] + struct Inner { + receive_buffer_size: AtomicUsize, + send_buffer_size: AtomicUsize, + hop_limit: AtomicU8, + keep_alive_idle_time: AtomicU64, // nanoseconds + } + + impl NonInheritedOptions { + pub fn set_keep_alive_idle_time(&mut self, value: u64) { + self.0.keep_alive_idle_time.store(value, Relaxed); + } + + pub fn set_hop_limit(&mut self, value: u8) { + self.0.hop_limit.store(value, Relaxed); + } + + pub fn set_receive_buffer_size(&mut self, value: usize) { + self.0.receive_buffer_size.store(value, Relaxed); + } + + pub fn set_send_buffer_size(&mut self, value: usize) { + self.0.send_buffer_size.store(value, Relaxed); + } + + pub(crate) fn apply(&self, family: SocketAddressFamily, stream: &TcpStream) { + // Manually inherit socket options from listener. We only have to + // do this on platforms that don't already do this automatically + // and only if a specific value was explicitly set on the listener. + + let receive_buffer_size = self.0.receive_buffer_size.load(Relaxed); + if receive_buffer_size > 0 { + // Ignore potential error. + _ = sockopt::set_socket_recv_buffer_size(&stream, receive_buffer_size); + } + + let send_buffer_size = self.0.send_buffer_size.load(Relaxed); + if send_buffer_size > 0 { + // Ignore potential error. + _ = sockopt::set_socket_send_buffer_size(&stream, send_buffer_size); + } + + // For some reason, IP_TTL is inherited, but IPV6_UNICAST_HOPS isn't. + if family == SocketAddressFamily::Ipv6 { + let hop_limit = self.0.hop_limit.load(Relaxed); + if hop_limit > 0 { + // Ignore potential error. + _ = sockopt::set_ipv6_unicast_hops(&stream, Some(hop_limit)); + } + } + + let keep_alive_idle_time = self.0.keep_alive_idle_time.load(Relaxed); + if keep_alive_idle_time > 0 { + // Ignore potential error. + _ = sockopt::set_tcp_keepidle(&stream, Duration::from_nanos(keep_alive_idle_time)); + } + } + } +} diff --git a/crates/wasi/src/p3/sockets/udp.rs b/crates/wasi/src/sockets/udp.rs similarity index 67% rename from crates/wasi/src/p3/sockets/udp.rs rename to crates/wasi/src/sockets/udp.rs index 6e2d635cfd..b7a044da3c 100644 --- a/crates/wasi/src/p3/sockets/udp.rs +++ b/crates/wasi/src/sockets/udp.rs @@ -1,24 +1,20 @@ -use core::future::Future; -use core::net::SocketAddr; - -use std::sync::Arc; - +use crate::runtime::with_ambient_tokio_runtime; +use crate::sockets::util::{ + ErrorCode, get_unicast_hop_limit, is_valid_address_family, is_valid_remote_address, + receive_buffer_size, send_buffer_size, set_receive_buffer_size, set_send_buffer_size, + set_unicast_hop_limit, udp_bind, udp_disconnect, udp_socket, +}; +use crate::sockets::{SocketAddrCheck, SocketAddressFamily, WasiSocketsCtx}; use cap_net_ext::AddressFamily; use io_lifetimes::AsSocketlike as _; use io_lifetimes::raw::{FromRawSocketlike as _, IntoRawSocketlike as _}; use rustix::io::Errno; use rustix::net::connect; +use std::io; +use std::net::SocketAddr; +use std::sync::Arc; use tracing::debug; -use crate::p3::bindings::sockets::types::{ErrorCode, IpAddressFamily, IpSocketAddress}; -use crate::runtime::with_ambient_tokio_runtime; -use crate::sockets::util::{ - get_unicast_hop_limit, is_valid_address_family, is_valid_remote_address, receive_buffer_size, - send_buffer_size, set_receive_buffer_size, set_send_buffer_size, set_unicast_hop_limit, - udp_bind, udp_disconnect, udp_socket, -}; -use crate::sockets::{MAX_UDP_DATAGRAM_SIZE, SocketAddressFamily}; - /// The state of a UDP socket. /// /// This represents the various states a socket can be in during the @@ -27,11 +23,18 @@ enum UdpState { /// The initial state for a newly-created socket. Default, + /// TODO + BindStarted, + /// Binding finished via `finish_bind`. The socket has an address but /// is not yet listening for connections. Bound, /// The socket is "connected" to a peer address. + #[cfg_attr( + not(feature = "p3"), + expect(dead_code, reason = "p2 has its own way of managing sending/receiving") + )] Connected(SocketAddr), } @@ -47,11 +50,17 @@ pub struct UdpSocket { /// Socket address family. family: SocketAddressFamily, + + /// If set, use this custom check for addrs, otherwise use what's in + /// `WasiSocketsCtx`. + socket_addr_check: Option, } impl UdpSocket { /// Create a new socket in the given family. - pub fn new(family: AddressFamily) -> std::io::Result { + pub(crate) fn new(cx: &WasiSocketsCtx, family: AddressFamily) -> io::Result { + cx.allowed_network_uses.check_allowed_udp()?; + // Delegate socket creation to cap_net_ext. They handle a couple of things for us: // - On Windows: call WSAStartup if not done before. // - Set the NONBLOCK and CLOEXEC flags. Either immediately during socket creation, @@ -77,10 +86,11 @@ impl UdpSocket { socket: Arc::new(socket), udp_state: UdpState::Default, family: socket_address_family, + socket_addr_check: None, }) } - pub fn bind(&mut self, addr: SocketAddr) -> Result<(), ErrorCode> { + pub(crate) fn bind(&mut self, addr: SocketAddr) -> Result<(), ErrorCode> { if !matches!(self.udp_state, UdpState::Default) { return Err(ErrorCode::InvalidState); } @@ -88,12 +98,30 @@ impl UdpSocket { return Err(ErrorCode::InvalidArgument); } udp_bind(&self.socket, addr)?; - self.udp_state = UdpState::Bound; + self.udp_state = UdpState::BindStarted; Ok(()) } - pub fn disconnect(&mut self) -> Result<(), ErrorCode> { - if !matches!(self.udp_state, UdpState::Connected(..)) { + pub(crate) fn finish_bind(&mut self) -> Result<(), ErrorCode> { + match self.udp_state { + UdpState::BindStarted => { + self.udp_state = UdpState::Bound; + Ok(()) + } + _ => Err(ErrorCode::NotInProgress), + } + } + + pub(crate) fn is_connected(&self) -> bool { + matches!(self.udp_state, UdpState::Connected(..)) + } + + pub(crate) fn is_bound(&self) -> bool { + matches!(self.udp_state, UdpState::Connected(..) | UdpState::Bound) + } + + pub(crate) fn disconnect(&mut self) -> Result<(), ErrorCode> { + if !self.is_connected() { return Err(ErrorCode::InvalidState); } udp_disconnect(&self.socket)?; @@ -101,11 +129,16 @@ impl UdpSocket { Ok(()) } - pub fn connect(&mut self, addr: SocketAddr) -> Result<(), ErrorCode> { + pub(crate) fn connect(&mut self, addr: SocketAddr) -> Result<(), ErrorCode> { if !is_valid_address_family(addr.ip(), self.family) || !is_valid_remote_address(addr) { return Err(ErrorCode::InvalidArgument); } + match self.udp_state { + UdpState::Bound | UdpState::Connected(_) => {} + _ => return Err(ErrorCode::InvalidState), + } + // We disconnect & (re)connect in two distinct steps for two reasons: // - To leave our socket instance in a consistent state in case the // connect fails. @@ -130,7 +163,8 @@ impl UdpSocket { Ok(()) } - pub fn send(&self, buf: Vec) -> impl Future> + use<> { + #[cfg(feature = "p3")] + pub(crate) fn send(&self, buf: Vec) -> impl Future> + use<> { let socket = if let UdpState::Connected(..) = self.udp_state { Ok(Arc::clone(&self.socket)) } else { @@ -142,7 +176,8 @@ impl UdpSocket { } } - pub fn send_to( + #[cfg(feature = "p3")] + pub(crate) fn send_to( &self, buf: Vec, addr: SocketAddr, @@ -152,6 +187,7 @@ impl UdpSocket { SendTo(Arc, SocketAddr), } let socket = match &self.udp_state { + UdpState::BindStarted => Err(ErrorCode::InvalidState), UdpState::Default | UdpState::Bound => Ok(Mode::SendTo(Arc::clone(&self.socket), addr)), UdpState::Connected(caddr) if addr == *caddr => { Ok(Mode::Send(Arc::clone(&self.socket))) @@ -166,21 +202,22 @@ impl UdpSocket { } } - pub fn receive( + #[cfg(feature = "p3")] + pub(crate) fn receive( &self, - ) -> impl Future, IpSocketAddress), ErrorCode>> + use<> { + ) -> impl Future, SocketAddr), ErrorCode>> + use<> { enum Mode { - Recv(Arc, IpSocketAddress), + Recv(Arc, SocketAddr), RecvFrom(Arc), } let socket = match self.udp_state { - UdpState::Default => Err(ErrorCode::InvalidState), + UdpState::Default | UdpState::BindStarted => Err(ErrorCode::InvalidState), UdpState::Bound => Ok(Mode::RecvFrom(Arc::clone(&self.socket))), - UdpState::Connected(addr) => Ok(Mode::Recv(Arc::clone(&self.socket), addr.into())), + UdpState::Connected(addr) => Ok(Mode::Recv(Arc::clone(&self.socket), addr)), }; async move { let socket = socket?; - let mut buf = vec![0; MAX_UDP_DATAGRAM_SIZE]; + let mut buf = vec![0; super::MAX_UDP_DATAGRAM_SIZE]; let (n, addr) = match socket { Mode::Recv(socket, addr) => { let n = socket.recv(&mut buf).await?; @@ -188,7 +225,7 @@ impl UdpSocket { } Mode::RecvFrom(socket) => { let (n, addr) = socket.recv_from(&mut buf).await?; - (n, addr.into()) + (n, addr) } }; buf.truncate(n); @@ -196,18 +233,18 @@ impl UdpSocket { } } - pub fn local_address(&self) -> Result { - if matches!(self.udp_state, UdpState::Default) { + pub(crate) fn local_address(&self) -> Result { + if matches!(self.udp_state, UdpState::Default | UdpState::BindStarted) { return Err(ErrorCode::InvalidState); } let addr = self .socket .as_socketlike_view::() .local_addr()?; - Ok(addr.into()) + Ok(addr) } - pub fn remote_address(&self) -> Result { + pub(crate) fn remote_address(&self) -> Result { if !matches!(self.udp_state, UdpState::Connected(..)) { return Err(ErrorCode::InvalidState); } @@ -215,47 +252,57 @@ impl UdpSocket { .socket .as_socketlike_view::() .peer_addr()?; - Ok(addr.into()) + Ok(addr) } - pub fn address_family(&self) -> IpAddressFamily { - match self.family { - SocketAddressFamily::Ipv4 => IpAddressFamily::Ipv4, - SocketAddressFamily::Ipv6 => IpAddressFamily::Ipv6, - } + pub(crate) fn address_family(&self) -> SocketAddressFamily { + self.family } - pub fn unicast_hop_limit(&self) -> Result { + pub(crate) fn unicast_hop_limit(&self) -> Result { let n = get_unicast_hop_limit(&self.socket, self.family)?; Ok(n) } - pub fn set_unicast_hop_limit(&self, value: u8) -> Result<(), ErrorCode> { + pub(crate) fn set_unicast_hop_limit(&self, value: u8) -> Result<(), ErrorCode> { set_unicast_hop_limit(&self.socket, self.family, value)?; Ok(()) } - pub fn receive_buffer_size(&self) -> Result { + pub(crate) fn receive_buffer_size(&self) -> Result { let n = receive_buffer_size(&self.socket)?; Ok(n) } - pub fn set_receive_buffer_size(&self, value: u64) -> Result<(), ErrorCode> { + pub(crate) fn set_receive_buffer_size(&self, value: u64) -> Result<(), ErrorCode> { set_receive_buffer_size(&self.socket, value)?; Ok(()) } - pub fn send_buffer_size(&self) -> Result { + pub(crate) fn send_buffer_size(&self) -> Result { let n = send_buffer_size(&self.socket)?; Ok(n) } - pub fn set_send_buffer_size(&self, value: u64) -> Result<(), ErrorCode> { + pub(crate) fn set_send_buffer_size(&self, value: u64) -> Result<(), ErrorCode> { set_send_buffer_size(&self.socket, value)?; Ok(()) } + + pub(crate) fn socket(&self) -> &Arc { + &self.socket + } + + pub(crate) fn socket_addr_check(&self) -> Option<&SocketAddrCheck> { + self.socket_addr_check.as_ref() + } + + pub(crate) fn set_socket_addr_check(&mut self, check: Option) { + self.socket_addr_check = check; + } } +#[cfg(feature = "p3")] async fn send(socket: &tokio::net::UdpSocket, buf: &[u8]) -> Result<(), ErrorCode> { let n = socket.send(buf).await?; // From Rust stdlib docs: @@ -270,6 +317,7 @@ async fn send(socket: &tokio::net::UdpSocket, buf: &[u8]) -> Result<(), ErrorCod } } +#[cfg(feature = "p3")] async fn send_to( socket: &tokio::net::UdpSocket, buf: &[u8], diff --git a/crates/wasi/src/sockets/util.rs b/crates/wasi/src/sockets/util.rs index dafe6e2584..a9679a09d6 100644 --- a/crates/wasi/src/sockets/util.rs +++ b/crates/wasi/src/sockets/util.rs @@ -27,6 +27,8 @@ pub enum ErrorCode { ConnectionReset, ConnectionAborted, DatagramTooLarge, + NotInProgress, + ConcurrencyConflict, } impl fmt::Display for ErrorCode { diff --git a/crates/wasi/src/p3/view.rs b/crates/wasi/src/view.rs similarity index 60% rename from crates/wasi/src/p3/view.rs rename to crates/wasi/src/view.rs index 4613ceace8..c2e84af1f3 100644 --- a/crates/wasi/src/p3/view.rs +++ b/crates/wasi/src/view.rs @@ -1,20 +1,19 @@ +use crate::WasiCtx; use wasmtime::component::ResourceTable; -use crate::p3::ctx::WasiCtx; - /// A trait which provides access to the [`WasiCtx`] inside the embedder's `T` /// of [`Store`][`Store`]. /// /// This crate's WASI Host implementations depend on the contents of /// [`WasiCtx`]. The `T` type [`Store`][`Store`] is defined in each /// embedding of Wasmtime. These implementations are connected to the -/// [`Linker`][`Linker`] by the -/// [`add_to_linker`](crate::p3::add_to_linker) function. +/// [`Linker`][`Linker`] by [`add_to_linker`](crate::p2::add_to_linker) +/// functions. /// /// # Example /// /// ``` -/// use wasmtime_wasi::p3::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; +/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; /// use wasmtime::component::ResourceTable; /// /// struct MyState { @@ -40,24 +39,42 @@ pub trait WasiView: Send { fn ctx(&mut self) -> WasiCtxView<'_>; } +/// Structure returned from [`WasiView::ctx`] which provides accesss to WASI +/// state for host functions to be implemented with. pub struct WasiCtxView<'a> { + /// The [`WasiCtx`], or configuration, of the guest. pub ctx: &'a mut WasiCtx, + /// Resources, such as files/streams, that the guest is using. pub table: &'a mut ResourceTable, } -impl crate::sockets::WasiSocketsView for T { - fn sockets(&mut self) -> crate::sockets::WasiSocketsCtxView<'_> { +impl crate::cli::WasiCliView for T { + fn cli(&mut self) -> crate::cli::WasiCliCtxView<'_> { let WasiCtxView { ctx, table } = self.ctx(); - crate::sockets::WasiSocketsCtxView { - ctx: &mut ctx.sockets, + crate::cli::WasiCliCtxView { + ctx: &mut ctx.cli, table, } } } impl crate::clocks::WasiClocksView for T { - fn clocks(&mut self) -> &mut crate::clocks::WasiClocksCtx { - &mut self.ctx().ctx.clocks + fn clocks(&mut self) -> crate::clocks::WasiClocksCtxView<'_> { + let WasiCtxView { ctx, table } = self.ctx(); + crate::clocks::WasiClocksCtxView { + ctx: &mut ctx.clocks, + table, + } + } +} + +impl crate::filesystem::WasiFilesystemView for T { + fn filesystem(&mut self) -> crate::filesystem::WasiFilesystemCtxView<'_> { + let WasiCtxView { ctx, table } = self.ctx(); + crate::filesystem::WasiFilesystemCtxView { + ctx: &mut ctx.filesystem, + table, + } } } @@ -67,11 +84,11 @@ impl crate::random::WasiRandomView for T { } } -impl crate::p3::cli::WasiCliView for T { - fn cli(&mut self) -> crate::p3::cli::WasiCliCtxView<'_> { +impl crate::sockets::WasiSocketsView for T { + fn sockets(&mut self) -> crate::sockets::WasiSocketsCtxView<'_> { let WasiCtxView { ctx, table } = self.ctx(); - crate::p3::cli::WasiCliCtxView { - ctx: &mut ctx.cli, + crate::sockets::WasiSocketsCtxView { + ctx: &mut ctx.sockets, table, } } diff --git a/crates/wasi/tests/all/main.rs b/crates/wasi/tests/all/main.rs index d859c6afb5..9fcf355cfa 100644 --- a/crates/wasi/tests/all/main.rs +++ b/crates/wasi/tests/all/main.rs @@ -1,3 +1,15 @@ +macro_rules! assert_test_exists { + ($name:ident) => { + #[expect(unused_imports, reason = "just here to ensure a name exists")] + use self::$name as _; + }; +} + +mod store; + +#[cfg(feature = "p1")] +mod p1; +#[cfg(feature = "p2")] mod p2; #[cfg(feature = "p3")] mod p3; diff --git a/crates/wasi/tests/all/p2/preview1.rs b/crates/wasi/tests/all/p1.rs similarity index 97% rename from crates/wasi/tests/all/p2/preview1.rs rename to crates/wasi/tests/all/p1.rs index 6bd21d8e45..ee7af15271 100644 --- a/crates/wasi/tests/all/p2/preview1.rs +++ b/crates/wasi/tests/all/p1.rs @@ -1,8 +1,9 @@ -use super::*; +use crate::store::Ctx; +use anyhow::Result; use std::path::Path; use test_programs_artifacts::*; use wasmtime::{Linker, Module}; -use wasmtime_wasi::preview1::add_to_linker_async; +use wasmtime_wasi::p1::{WasiP1Ctx, add_to_linker_async}; async fn run(path: &str, inherit_stdio: bool) -> Result<()> { let path = Path::new(path); @@ -10,14 +11,15 @@ async fn run(path: &str, inherit_stdio: bool) -> Result<()> { let engine = test_programs_artifacts::engine(|config| { config.async_support(true); }); - let mut linker = Linker::::new(&engine); + let mut linker = Linker::>::new(&engine); add_to_linker_async(&mut linker, |t| &mut t.wasi)?; let module = Module::from_file(&engine, path)?; - let (mut store, _td) = store(&engine, name, |builder| { + let (mut store, _td) = Ctx::new(&engine, name, |builder| { if inherit_stdio { builder.inherit_stdio(); } + builder.build_p1() })?; let instance = linker.instantiate_async(&mut store, &module).await?; let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?; diff --git a/crates/wasi/tests/all/p2/api.rs b/crates/wasi/tests/all/p2/api.rs index e4448c5d78..306b395ef7 100644 --- a/crates/wasi/tests/all/p2/api.rs +++ b/crates/wasi/tests/all/p2/api.rs @@ -4,26 +4,24 @@ use std::sync::Mutex; use std::time::Duration; use wasmtime::Store; use wasmtime::component::{Component, Linker, ResourceTable}; -use wasmtime_wasi::p2::bindings::Command; -use wasmtime_wasi::p2::{ - IoView, WasiCtx, WasiCtxBuilder, WasiView, add_to_linker_async, - bindings::{clocks::wall_clock, filesystem::types as filesystem}, +use wasmtime_wasi::p2::add_to_linker_async; +use wasmtime_wasi::p2::bindings::{Command, clocks::wall_clock, filesystem::types as filesystem}; +use wasmtime_wasi::{ + DirPerms, FilePerms, HostMonotonicClock, HostWallClock, WasiCtx, WasiCtxBuilder, WasiCtxView, + WasiView, }; -use wasmtime_wasi::{DirPerms, FilePerms, HostMonotonicClock, HostWallClock}; struct CommandCtx { table: ResourceTable, wasi: WasiCtx, } -impl IoView for CommandCtx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} impl WasiView for CommandCtx { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi, + table: &mut self.table, + } } } @@ -172,7 +170,7 @@ async fn api_reactor() -> Result<()> { // `host` crate for `streams`, not because of `with` in the bindgen macro. let writepipe = wasmtime_wasi::p2::pipe::MemoryOutputPipe::new(4096); let stream: wasmtime_wasi::p2::DynOutputStream = Box::new(writepipe.clone()); - let table_ix = store.data_mut().table().push(stream)?; + let table_ix = store.data_mut().table.push(stream)?; let r = reactor.call_write_strings_to(&mut store, table_ix).await?; assert_eq!(r, Ok(())); diff --git a/crates/wasi/tests/all/p2/async_.rs b/crates/wasi/tests/all/p2/async_.rs index 5094e8add2..d5ecc4c39e 100644 --- a/crates/wasi/tests/all/p2/async_.rs +++ b/crates/wasi/tests/all/p2/async_.rs @@ -1,6 +1,8 @@ -use super::*; +use crate::store::{Ctx, MyWasiCtx}; +use anyhow::Result; use std::path::Path; use test_programs_artifacts::*; +use wasmtime::component::{Component, Linker}; use wasmtime_wasi::p2::add_to_linker_async; use wasmtime_wasi::p2::bindings::Command; @@ -13,10 +15,14 @@ async fn run(path: &str, inherit_stdio: bool) -> Result<()> { let mut linker = Linker::new(&engine); add_to_linker_async(&mut linker)?; - let (mut store, _td) = store(&engine, name, |builder| { + let (mut store, _td) = Ctx::new(&engine, name, |builder| { if inherit_stdio { builder.inherit_stdio(); } + MyWasiCtx { + wasi: builder.build(), + table: Default::default(), + } })?; let component = Component::from_file(&engine, path)?; let command = Command::instantiate_async(&mut store, &component, &linker).await?; diff --git a/crates/wasi/tests/all/p2/mod.rs b/crates/wasi/tests/all/p2/mod.rs index 0e98b5c0dd..77f499e4df 100644 --- a/crates/wasi/tests/all/p2/mod.rs +++ b/crates/wasi/tests/all/p2/mod.rs @@ -1,92 +1,3 @@ -use anyhow::Result; -use tempfile::TempDir; -use wasmtime::{ - Engine, Store, - component::{Component, Linker, ResourceTable}, -}; -use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView, pipe::MemoryOutputPipe}; -use wasmtime_wasi::preview1::WasiP1Ctx; -use wasmtime_wasi::{DirPerms, FilePerms}; - -struct Ctx { - stdout: MemoryOutputPipe, - stderr: MemoryOutputPipe, - wasi: WasiP1Ctx, -} - -impl IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - self.wasi.table() - } -} -impl WasiView for Ctx { - fn ctx(&mut self) -> &mut WasiCtx { - self.wasi.ctx() - } -} - -fn prepare_workspace(exe_name: &str) -> Result { - let prefix = format!("wasi_components_{exe_name}_"); - let tempdir = tempfile::Builder::new().prefix(&prefix).tempdir()?; - Ok(tempdir) -} - -fn store( - engine: &Engine, - name: &str, - configure: impl FnOnce(&mut WasiCtxBuilder), -) -> Result<(Store, TempDir)> { - let stdout = MemoryOutputPipe::new(4096); - let stderr = MemoryOutputPipe::new(4096); - let workspace = prepare_workspace(name)?; - - // Create our wasi context. - let mut builder = WasiCtxBuilder::new(); - builder.stdout(stdout.clone()).stderr(stderr.clone()); - - builder - .args(&[name, "."]) - .inherit_network() - .allow_ip_name_lookup(true); - println!("preopen: {workspace:?}"); - builder.preopened_dir(workspace.path(), ".", DirPerms::all(), FilePerms::all())?; - for (var, val) in test_programs_artifacts::wasi_tests_environment() { - builder.env(var, val); - } - - configure(&mut builder); - let ctx = Ctx { - wasi: builder.build_p1(), - stderr, - stdout, - }; - - Ok((Store::new(&engine, ctx), workspace)) -} - -impl Drop for Ctx { - fn drop(&mut self) { - let stdout = self.stdout.contents(); - if !stdout.is_empty() { - println!("[guest] stdout:\n{}\n===", String::from_utf8_lossy(&stdout)); - } - let stderr = self.stderr.contents(); - if !stderr.is_empty() { - println!("[guest] stderr:\n{}\n===", String::from_utf8_lossy(&stderr)); - } - } -} - -// Assert that each of `sync` and `async` below are testing everything through -// assertion of the existence of the test function itself. -macro_rules! assert_test_exists { - ($name:ident) => { - #[expect(unused_imports, reason = "just here to ensure a name exists")] - use self::$name as _; - }; -} - mod api; mod async_; -mod preview1; mod sync; diff --git a/crates/wasi/tests/all/p2/sync.rs b/crates/wasi/tests/all/p2/sync.rs index 435de4959c..ecce347672 100644 --- a/crates/wasi/tests/all/p2/sync.rs +++ b/crates/wasi/tests/all/p2/sync.rs @@ -1,6 +1,8 @@ -use super::*; +use crate::store::{Ctx, MyWasiCtx}; use std::path::Path; use test_programs_artifacts::*; +use wasmtime::Result; +use wasmtime::component::{Component, Linker}; use wasmtime_wasi::p2::add_to_linker_sync; use wasmtime_wasi::p2::bindings::sync::Command; @@ -14,11 +16,15 @@ fn run(path: &str, inherit_stdio: bool) -> Result<()> { let component = Component::from_file(&engine, path)?; for blocking in [false, true] { - let (mut store, _td) = store(&engine, name, |builder| { + let (mut store, _td) = Ctx::new(&engine, name, |builder| { if inherit_stdio { builder.inherit_stdio(); } builder.allow_blocking_current_thread(blocking); + MyWasiCtx { + wasi: builder.build(), + table: Default::default(), + } })?; let command = Command::instantiate(&mut store, &component, &linker)?; command diff --git a/crates/wasi/tests/all/p3/mod.rs b/crates/wasi/tests/all/p3/mod.rs index cbb0940422..4efc033968 100644 --- a/crates/wasi/tests/all/p3/mod.rs +++ b/crates/wasi/tests/all/p3/mod.rs @@ -1,126 +1,29 @@ -#![cfg(feature = "p3")] - -use std::path::Path; - +use crate::store::{Ctx, MyWasiCtx}; use anyhow::{Context as _, anyhow}; +use std::path::Path; use test_programs_artifacts::*; -use wasmtime::Store; -use wasmtime::component::{Component, Linker, ResourceTable}; -use wasmtime_wasi::p2::{self, IoView}; -use wasmtime_wasi::p3::ResourceView; +use wasmtime::Result; +use wasmtime::component::{Component, Linker}; use wasmtime_wasi::p3::bindings::Command; -use wasmtime_wasi::p3::filesystem::{WasiFilesystemCtx, WasiFilesystemView}; -use wasmtime_wasi::p3::{self, WasiCtxView}; -use wasmtime_wasi::{DirPerms, FilePerms}; - -macro_rules! assert_test_exists { - ($name:ident) => { - #[expect(unused_imports, reason = "just here to assert it exists")] - use self::$name as _; - }; -} - -struct Ctx { - filesystem: WasiFilesystemCtx, - table: ResourceTable, - p2: p2::WasiCtx, - p3: p3::WasiCtx, -} - -impl Default for Ctx { - fn default() -> Self { - Self { - filesystem: WasiFilesystemCtx::default(), - table: ResourceTable::default(), - p2: p2::WasiCtxBuilder::new().inherit_stdio().build(), - p3: p3::WasiCtxBuilder::new().inherit_stdio().build(), - } - } -} - -impl p2::WasiView for Ctx { - fn ctx(&mut self) -> &mut p2::WasiCtx { - &mut self.p2 - } -} - -impl p3::WasiView for Ctx { - fn ctx(&mut self) -> WasiCtxView<'_> { - WasiCtxView { - ctx: &mut self.p3, - table: &mut self.table, - } - } -} - -impl IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} - -impl ResourceView for Ctx { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } -} -impl WasiFilesystemView for Ctx { - fn filesystem(&self) -> &WasiFilesystemCtx { - &self.filesystem - } -} - -async fn run(path: &str) -> anyhow::Result<()> { - let _ = env_logger::try_init(); +async fn run(path: &str) -> Result<()> { let path = Path::new(path); + let name = path.file_stem().unwrap().to_str().unwrap(); let engine = test_programs_artifacts::engine(|config| { config.async_support(true); config.wasm_component_model_async(true); }); - let component = Component::from_file(&engine, path).context("failed to compile component")?; - let mut linker = Linker::new(&engine); + // TODO: Remove once test components are not built for `wasm32-wasip1` wasmtime_wasi::p2::add_to_linker_async(&mut linker) .context("failed to link `wasi:cli@0.2.x`")?; wasmtime_wasi::p3::add_to_linker(&mut linker).context("failed to link `wasi:cli@0.3.x`")?; - let mut filesystem = WasiFilesystemCtx::default(); - let table = ResourceTable::default(); - - let p2 = p2::WasiCtx::builder() - .inherit_stdout() - .inherit_stderr() - .build(); - - let mut p3 = p3::WasiCtxBuilder::new(); - let name = path.file_stem().unwrap().to_str().unwrap(); - let tempdir = tempfile::Builder::new() - .prefix(&format!( - "wasi_components_{}_", - path.file_stem().unwrap().to_str().unwrap() - )) - .tempdir()?; - p3.args(&[name, "."]) - .inherit_network() - .allow_ip_name_lookup(true); - println!("preopen: {tempdir:?}"); - filesystem.preopened_dir(tempdir.path(), ".", DirPerms::all(), FilePerms::all())?; - p3.preopened_dir(tempdir.path(), ".", DirPerms::all(), FilePerms::all())?; - for (var, val) in test_programs_artifacts::wasi_tests_environment() { - p3.env(var, val); - } - let p3 = p3.build(); - - let mut store = Store::new( - &engine, - Ctx { - table, - p2, - p3, - filesystem, - }, - ); + let (mut store, _td) = Ctx::new(&engine, name, |builder| MyWasiCtx { + wasi: builder.build(), + table: Default::default(), + })?; + let component = Component::from_file(&engine, path)?; let instance = linker.instantiate_async(&mut store, &component).await?; let command = Command::new(&mut store, &instance).context("failed to instantiate `wasi:cli/command`")?; @@ -146,6 +49,11 @@ async fn p3_clocks_sleep() -> anyhow::Result<()> { run(P3_CLOCKS_SLEEP_COMPONENT).await } +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p3_filesystem_file_read_write() -> anyhow::Result<()> { + run(P3_FILESYSTEM_FILE_READ_WRITE_COMPONENT).await +} + #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn p3_random_imports() -> anyhow::Result<()> { run(P3_RANDOM_IMPORTS_COMPONENT).await @@ -210,8 +118,3 @@ async fn p3_sockets_udp_sockopts() -> anyhow::Result<()> { async fn p3_sockets_udp_states() -> anyhow::Result<()> { run(P3_SOCKETS_UDP_STATES_COMPONENT).await } - -#[test_log::test(tokio::test(flavor = "multi_thread"))] -async fn p3_filesystem_file_read_write() -> anyhow::Result<()> { - run(P3_FILESYSTEM_FILE_READ_WRITE_COMPONENT).await -} diff --git a/crates/wasi/tests/all/store.rs b/crates/wasi/tests/all/store.rs new file mode 100644 index 0000000000..72384a7459 --- /dev/null +++ b/crates/wasi/tests/all/store.rs @@ -0,0 +1,80 @@ +use anyhow::Result; +use tempfile::TempDir; +use wasmtime::component::ResourceTable; +use wasmtime::{Engine, Store}; +use wasmtime_wasi::{ + DirPerms, FilePerms, WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView, p2::pipe::MemoryOutputPipe, +}; + +pub struct Ctx { + stdout: MemoryOutputPipe, + stderr: MemoryOutputPipe, + pub wasi: T, +} + +fn prepare_workspace(exe_name: &str) -> Result { + let prefix = format!("wasi_components_{exe_name}_"); + let tempdir = tempfile::Builder::new().prefix(&prefix).tempdir()?; + Ok(tempdir) +} + +impl Ctx { + pub fn new( + engine: &Engine, + name: &str, + configure: impl FnOnce(&mut WasiCtxBuilder) -> T, + ) -> Result<(Store>, TempDir)> { + let stdout = MemoryOutputPipe::new(4096); + let stderr = MemoryOutputPipe::new(4096); + let workspace = prepare_workspace(name)?; + + // Create our wasi context. + let mut builder = WasiCtxBuilder::new(); + builder.stdout(stdout.clone()).stderr(stderr.clone()); + + builder + .args(&[name, "."]) + .inherit_network() + .allow_ip_name_lookup(true); + println!("preopen: {workspace:?}"); + builder.preopened_dir(workspace.path(), ".", DirPerms::all(), FilePerms::all())?; + for (var, val) in test_programs_artifacts::wasi_tests_environment() { + builder.env(var, val); + } + + let ctx = Ctx { + wasi: configure(&mut builder), + stderr, + stdout, + }; + + Ok((Store::new(&engine, ctx), workspace)) + } +} + +impl Drop for Ctx { + fn drop(&mut self) { + let stdout = self.stdout.contents(); + if !stdout.is_empty() { + println!("[guest] stdout:\n{}\n===", String::from_utf8_lossy(&stdout)); + } + let stderr = self.stderr.contents(); + if !stderr.is_empty() { + println!("[guest] stderr:\n{}\n===", String::from_utf8_lossy(&stderr)); + } + } +} + +pub struct MyWasiCtx { + pub wasi: WasiCtx, + pub table: ResourceTable, +} + +impl WasiView for Ctx { + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi.wasi, + table: &mut self.wasi.table, + } + } +} diff --git a/crates/wasi/tests/process_stdin.rs b/crates/wasi/tests/process_stdin.rs index 49d8c14e7b..0f2e1bd915 100644 --- a/crates/wasi/tests/process_stdin.rs +++ b/crates/wasi/tests/process_stdin.rs @@ -1,6 +1,7 @@ use std::io::{BufRead, Write}; use std::process::Command; -use wasmtime_wasi::p2::{InputStream, Pollable}; +use wasmtime_wasi::cli::StdinStream; +use wasmtime_wasi::p2::Pollable; const VAR_NAME: &str = "__CHILD_PROCESS"; @@ -31,7 +32,7 @@ fn main() { .block_on(async { 'task: loop { println!("child: creating stdin"); - let mut stdin = wasmtime_wasi::p2::stdin(); + let mut stdin = wasmtime_wasi::cli::stdin().p2_stream(); println!("child: checking that stdin is not ready"); assert!( diff --git a/crates/wasi/witx/preview0/typenames.witx b/crates/wasi/witx/p0/typenames.witx similarity index 100% rename from crates/wasi/witx/preview0/typenames.witx rename to crates/wasi/witx/p0/typenames.witx diff --git a/crates/wasi/witx/preview0/wasi_unstable.witx b/crates/wasi/witx/p0/wasi_unstable.witx similarity index 100% rename from crates/wasi/witx/preview0/wasi_unstable.witx rename to crates/wasi/witx/p0/wasi_unstable.witx diff --git a/crates/wasi/witx/preview1/typenames.witx b/crates/wasi/witx/p1/typenames.witx similarity index 100% rename from crates/wasi/witx/preview1/typenames.witx rename to crates/wasi/witx/p1/typenames.witx diff --git a/crates/wasi/witx/preview1/wasi_snapshot_preview1.witx b/crates/wasi/witx/p1/wasi_snapshot_preview1.witx similarity index 100% rename from crates/wasi/witx/preview1/wasi_snapshot_preview1.witx rename to crates/wasi/witx/p1/wasi_snapshot_preview1.witx diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index f8f67e81aa..5295f487bb 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -100,10 +100,10 @@ env_logger = { workspace = true } proptest = { workspace = true } rand = { workspace = true } tempfile = { workspace = true } -wasi-common = { path = "../wasi-common", default-features = true } libtest-mimic = { workspace = true } cranelift-native = { workspace = true } wasmtime-test-util = { workspace = true } +tokio = { workspace = true, features = ["macros", "sync"] } [build-dependencies] cc = { workspace = true, optional = true } @@ -113,6 +113,10 @@ wasmtime-versioned-export-macros = { workspace = true, optional = true } name = "host_segfault" harness = false +[[test]] +name = "engine_across_forks" +harness = false + # ============================================================================= # # Features for the Wasmtime crate. diff --git a/crates/wasmtime/src/compile.rs b/crates/wasmtime/src/compile.rs index 9db99d0f9d..8325c6eaa9 100644 --- a/crates/wasmtime/src/compile.rs +++ b/crates/wasmtime/src/compile.rs @@ -840,6 +840,10 @@ the use case. None } })); + log::trace!( + "call graph edges for {output_index:?} = {:?}: {calls:?}", + output.key + ); Ok(()) } })?; @@ -865,6 +869,7 @@ the use case. &mut layer_outputs, |output: &mut CompileOutput<'_>| { debug_assert_eq!(output.key.kind(), CompileKind::WasmFunction); + log::trace!("processing inlining for {:?}", output.key); let caller_translation = output.translation.unwrap(); let caller_module = output.key.module(); @@ -885,11 +890,15 @@ the use case. { (caller_module, def_func, Some(caller_needs_gc_heap)) } else { - let (def_module, def_func) = - caller_translation.known_imported_functions[callee].expect( - "a direct call to an imported function must have a \ - statically-known import", - ); + let (def_module, def_func) = caller_translation + .known_imported_functions[callee] + .unwrap_or_else(|| { + panic!( + "a direct call to an imported function must have a \ + statically-known definition, but direct call to imported \ + function {callee:?} has no statically-known definition", + ) + }); (def_module, def_func, None) }; diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 068e7f8732..61b3e6bf3c 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -1398,8 +1398,8 @@ impl Config { /// Set a custom [`Cache`]. /// - /// To load a cache from a file, use [`Cache::from_file`]. Otherwise, you can create a new - /// cache config using [`CacheConfig::new`] and passing that to [`Cache::new`]. + /// To load a cache configuration from a file, use [`Cache::from_file`]. Otherwise, you can + /// create a new cache config using [`CacheConfig::new`] and passing that to [`Cache::new`]. /// /// If you want to disable the cache, you can call this method with `None`. /// diff --git a/crates/wasmtime/src/profiling_agent/jitdump.rs b/crates/wasmtime/src/profiling_agent/jitdump.rs index 526658537e..dc760aad4f 100644 --- a/crates/wasmtime/src/profiling_agent/jitdump.rs +++ b/crates/wasmtime/src/profiling_agent/jitdump.rs @@ -17,7 +17,6 @@ use object::elf; use std::process; use std::sync::Mutex; use target_lexicon::Architecture; -use wasmtime_environ::Unsigned; use wasmtime_jit_debug::perf_jitdump::*; /// Interface for driving the creation of jitdump files @@ -55,7 +54,10 @@ impl ProfilingAgent for JitDumpAgent { let mut jitdump_file = JITDUMP_FILE.lock().unwrap(); let jitdump_file = jitdump_file.as_mut().unwrap(); let timestamp = jitdump_file.get_time_stamp(); - let tid = rustix::thread::gettid().as_raw_nonzero().get().unsigned(); + let tid = rustix::thread::gettid() + .as_raw_nonzero() + .get() + .cast_unsigned(); if let Err(err) = jitdump_file.dump_code_load_record(&name, code, timestamp, self.pid, tid) { println!("Jitdump: write_code_load_failed_record failed: {err:?}\n"); diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index f14aa9e450..8e5af865b1 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -87,7 +87,7 @@ use wasmtime_environ::component::{ TypeTupleIndex, }; -pub use abort::AbortHandle; +pub use abort::JoinHandle; pub use futures_and_streams::{ ErrorContext, FutureReader, FutureWriter, GuardedFutureReader, GuardedFutureWriter, GuardedStreamReader, GuardedStreamWriter, ReadBuffer, StreamReader, StreamWriter, VecBuffer, @@ -226,7 +226,7 @@ where /// Spawn a background task. /// /// See [`Accessor::spawn`] for details. - pub fn spawn(&mut self, task: impl AccessorTask>) -> AbortHandle + pub fn spawn(&mut self, task: impl AccessorTask>) -> JoinHandle where T: 'static, { @@ -497,14 +497,14 @@ where /// or `future` such that the code to write to the write end of that /// `stream` or `future` must run after the function returns. /// - /// The returned [`AbortHandle`] may be used to cancel the task. + /// The returned [`JoinHandle`] may be used to cancel the task. /// /// # Panics /// /// Panics if called within a closure provided to the [`Accessor::with`] /// function. This can only be called outside an active invocation of /// [`Accessor::with`]. - pub fn spawn(&self, task: impl AccessorTask>) -> AbortHandle + pub fn spawn(&self, task: impl AccessorTask>) -> JoinHandle where T: 'static, { @@ -1123,7 +1123,7 @@ impl ConcurrentState { WaitableState::HostTask => { let id = TableId::::new(rep); let task = self.get(id)?; - if task.abort_handle.is_some() { + if task.join_handle.is_some() { bail!("cannot drop a subtask which has not yet resolved"); } (Waitable::Host(id), task.caller_instance, true) @@ -1344,7 +1344,7 @@ impl Instance { self, mut store: impl AsContextMut, task: impl AccessorTask, Result<()>>, - ) -> AbortHandle { + ) -> JoinHandle { let mut store = store.as_context_mut(); let accessor = Accessor::new(StoreToken::new(store.as_context_mut()), Some(self)); self.spawn_with_accessor(store, accessor, task) @@ -1357,7 +1357,7 @@ impl Instance { mut store: StoreContextMut, accessor: Accessor, task: impl AccessorTask>, - ) -> AbortHandle + ) -> JoinHandle where T: 'static, D: HasData + ?Sized, @@ -1368,7 +1368,7 @@ impl Instance { // hook calls to poll and possibly spawn more background tasks on each // iteration. let (handle, future) = - AbortHandle::run(async move { HostTaskOutput::Result(task.run(&accessor).await) }); + JoinHandle::run(async move { HostTaskOutput::Result(task.run(&accessor).await) }); self.concurrent_state_mut(store.0) .push_future(Box::pin(async move { future.await.unwrap_or(HostTaskOutput::Result(Ok(()))) @@ -2500,7 +2500,7 @@ impl Instance { /// it an `&Accessor`. /// /// See the `Accessor` documentation for details. - pub(crate) fn wrap_call( + pub(crate) fn wrap_call( self, store: StoreContextMut, closure: F, @@ -2544,7 +2544,7 @@ impl Instance { // Create an abortable future which hooks calls to poll and manages call // context state for the future. - let (abort_handle, future) = AbortHandle::run(async move { + let (join_handle, future) = JoinHandle::run(async move { let mut future = pin!(future); let mut call_context = None; future::poll_fn(move |cx| { @@ -2586,7 +2586,7 @@ impl Instance { // We create a new host task even though it might complete immediately // (in which case we won't need to pass a waitable back to the guest). // If it does complete immediately, we'll remove it before we return. - let task = state.push(HostTask::new(caller_instance, Some(abort_handle)))?; + let task = state.push(HostTask::new(caller_instance, Some(join_handle)))?; log::trace!("new host task child of {caller:?}: {task:?}"); let token = StoreToken::new(store.as_context_mut()); @@ -2604,7 +2604,7 @@ impl Instance { let mut store = token.as_context_mut(store); lower(store.as_context_mut(), instance, result?)?; let state = instance.concurrent_state_mut(store.0); - state.get_mut(task)?.abort_handle.take(); + state.get_mut(task)?.join_handle.take(); Waitable::Host(task).set_event( state, Some(Event::Subtask { @@ -3106,7 +3106,7 @@ impl Instance { log::trace!("subtask_cancel {waitable:?} (handle {task_id})"); if let Waitable::Host(host_task) = waitable { - if let Some(handle) = concurrent_state.get_mut(host_task)?.abort_handle.take() { + if let Some(handle) = concurrent_state.get_mut(host_task)?.join_handle.take() { handle.abort(); return Ok(Status::ReturnCancelled as u32); } @@ -3678,18 +3678,18 @@ type HostTaskFuture = Pin + Send + 'stat struct HostTask { common: WaitableCommon, caller_instance: RuntimeComponentInstanceIndex, - abort_handle: Option, + join_handle: Option, } impl HostTask { fn new( caller_instance: RuntimeComponentInstanceIndex, - abort_handle: Option, + join_handle: Option, ) -> Self { Self { common: WaitableCommon::default(), caller_instance, - abort_handle, + join_handle, } } } diff --git a/crates/wasmtime/src/runtime/component/concurrent/abort.rs b/crates/wasmtime/src/runtime/component/concurrent/abort.rs index 677eb2256f..67a4779917 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/abort.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/abort.rs @@ -1,71 +1,282 @@ -use std::future; -use std::pin::pin; +use std::mem::{self, ManuallyDrop}; +use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll, Waker}; -/// Handle to a task which may be used to abort it. +/// Handle to a task which may be used to join on the result of executing it. /// /// This represents a handle to a running task which can be cancelled with -/// [`AbortHandle::abort`]. -pub struct AbortHandle { - state: Arc>, +/// [`JoinHandle::abort`]. The final result and drop of the task can be +/// determined by `await`-ing this handle. +/// +/// Note that dropping this handle does not affect the running task it's +/// connected to. A manual invocation of [`JoinHandle::abort`] is required to +/// affect the task. +pub struct JoinHandle { + state: Arc>, } -#[derive(Default)] -struct AbortState { - aborted: bool, - waker: Option, +enum JoinState { + /// The task this is connected to is still running and has not completed or + /// been dropped. + Running { + /// The waker that the running task has registered which is signaled + /// upon abort. + waiting_for_abort_signal: Option, + + /// The waker that the `JoinHandle` has registered to await + /// destruction of the running task itself. + waiting_for_abort_to_complete: Option, + }, + + /// An abort as been requested through an `JoinHandle`. The task specified + /// here is used for `Future for JoinHandle`. + AbortRequested { + waiting_for_abort_to_complete: Option, + }, + + /// The running task has completed, so no need to abort it and nothing else + /// needs to wait. + Complete, } -impl AbortHandle { +impl JoinHandle { /// Abort the task. /// /// This flags the connected task should abort in the near future, but note /// that if this is called while the future is being polled then that call /// will still complete. + /// + /// Note that this `JoinHandle` is itself a `Future` and can be used to + /// await the result and destruction of the task that this is associated + /// with. pub fn abort(&self) { - let waker = { - let mut state = self.state.lock().unwrap(); - state.aborted = true; - state.waker.take() - }; - if let Some(waker) = waker { - waker.wake(); - } - } - - fn is_aborted(&self, cx: &mut Context<'_>) -> bool { let mut state = self.state.lock().unwrap(); - if state.aborted { - return true; + + match &mut *state { + // If this task is still running, then fall through to below to + // transition it into the `AbortRequested` state. If present the + // waker for the running task is notified to indicate that an abort + // signal has been received. + JoinState::Running { + waiting_for_abort_signal, + waiting_for_abort_to_complete, + } => { + if let Some(task) = waiting_for_abort_signal.take() { + task.wake(); + } + + *state = JoinState::AbortRequested { + waiting_for_abort_to_complete: waiting_for_abort_to_complete.take(), + }; + } + + // If this task has already been aborted or has completed, nothing + // is left to do. + JoinState::AbortRequested { .. } | JoinState::Complete => {} } - state.waker = Some(cx.waker().clone()); - false } /// Wraps the `future` provided in a new future which is "abortable" where - /// if the returned `AbortHandle` is flagged then the future will resolve + /// if the returned `JoinHandle` is flagged then the future will resolve /// ASAP with `None` and drop the provided `future`. - pub(crate) fn run(future: F) -> (AbortHandle, impl Future>) + pub(crate) fn run(future: F) -> (JoinHandle, impl Future>) where F: Future, { - let handle = AbortHandle { - state: Default::default(), + let handle = JoinHandle { + state: Arc::new(Mutex::new(JoinState::Running { + waiting_for_abort_signal: None, + waiting_for_abort_to_complete: None, + })), }; - let handle2 = AbortHandle { + let future = JoinHandleFuture { + future: ManuallyDrop::new(future), state: handle.state.clone(), }; - let future = async move { - let mut future = pin!(future); - future::poll_fn(|cx| { - if handle2.is_aborted(cx) { + (handle, future) + } +} + +impl Future for JoinHandle { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut state = self.state.lock().unwrap(); + match &mut *state { + // If this task is running or still only has requested an abort, + // wait further for the task to get dropped. + JoinState::Running { + waiting_for_abort_to_complete, + .. + } + | JoinState::AbortRequested { + waiting_for_abort_to_complete, + } => { + *waiting_for_abort_to_complete = Some(cx.waker().clone()); + Poll::Pending + } + + // The task is dropped, done! + JoinState::Complete => Poll::Ready(()), + } + } +} + +struct JoinHandleFuture { + future: ManuallyDrop, + state: Arc>, +} + +impl Future for JoinHandleFuture +where + F: Future, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // SAFETY: this is a pin-projection from `Self` to the state and `Pin` + // of the internal future. This is the exclusive access of these fields + // apart from the destructor and should be safe. + let (state, future) = unsafe { + let me = self.get_unchecked_mut(); + (&me.state, Pin::new_unchecked(&mut *me.future)) + }; + + // First, before polling the future, check to see if we've been + // aborted. If not register our task as awaiting such an abort. + { + let mut state = state.lock().unwrap(); + match &mut *state { + JoinState::Running { + waiting_for_abort_signal, + .. + } => { + *waiting_for_abort_signal = Some(cx.waker().clone()); + } + JoinState::AbortRequested { .. } | JoinState::Complete => { return Poll::Ready(None); } - future.as_mut().poll(cx).map(Some) - }) - .await + } + } + + future.poll(cx).map(Some) + } +} + +impl Drop for JoinHandleFuture { + fn drop(&mut self) { + // SAFETY: this is the exclusive owner of this future and it's safe to + // drop here during the owning destructor. + // + // Note that this explicitly happens before notifying the abort handle + // that the task completed so that when the notification goes through + // it's guaranteed that the future has been destroyed. + unsafe { + ManuallyDrop::drop(&mut self.future); + } + + // After the future dropped see if there was a task awaiting its + // destruction. Simultaneously flag this state as complete. + let prev = mem::replace(&mut *self.state.lock().unwrap(), JoinState::Complete); + let task = match prev { + JoinState::Running { + waiting_for_abort_to_complete, + .. + } + | JoinState::AbortRequested { + waiting_for_abort_to_complete, + } => waiting_for_abort_to_complete, + JoinState::Complete => None, }; - (handle, future) + if let Some(task) = task { + task.wake(); + } + } +} + +#[cfg(test)] +mod tests { + use super::JoinHandle; + use std::pin::{Pin, pin}; + use std::task::{Context, Poll, Waker}; + use tokio::sync::oneshot; + + fn is_ready(future: Pin<&mut F>) -> bool + where + F: Future, + { + match future.poll(&mut Context::from_waker(Waker::noop())) { + Poll::Ready(_) => true, + Poll::Pending => false, + } + } + + #[tokio::test] + async fn abort_in_progress() { + let (tx, rx) = oneshot::channel::<()>(); + let (mut handle, future) = JoinHandle::run(rx); + let mut handle = Pin::new(&mut handle); + { + let mut future = pin!(future); + assert!(!is_ready(future.as_mut())); + assert!(!is_ready(handle.as_mut())); + handle.abort(); + assert!(is_ready(future.as_mut())); + assert!(!is_ready(handle.as_mut())); + assert!(!tx.is_closed()); + } + assert!(is_ready(handle.as_mut())); + assert!(tx.is_closed()); + } + + #[tokio::test] + async fn abort_complete() { + let (tx, rx) = oneshot::channel::<()>(); + let (mut handle, future) = JoinHandle::run(rx); + let mut handle = Pin::new(&mut handle); + tx.send(()).unwrap(); + assert!(!is_ready(handle.as_mut())); + { + let mut future = pin!(future); + assert!(is_ready(future.as_mut())); + assert!(!is_ready(handle.as_mut())); + } + assert!(is_ready(handle.as_mut())); + handle.abort(); + assert!(is_ready(handle.as_mut())); + } + + #[tokio::test] + async fn abort_dropped() { + let (tx, rx) = oneshot::channel::<()>(); + let (mut handle, future) = JoinHandle::run(rx); + let mut handle = Pin::new(&mut handle); + drop(future); + assert!(is_ready(handle.as_mut())); + handle.abort(); + assert!(is_ready(handle.as_mut())); + assert!(tx.is_closed()); + } + + #[tokio::test] + async fn await_completion() { + let (tx, rx) = oneshot::channel::<()>(); + tx.send(()).unwrap(); + let (handle, future) = JoinHandle::run(rx); + let task = tokio::task::spawn(future); + handle.await; + task.await.unwrap(); + } + + #[tokio::test] + async fn await_abort() { + let (tx, rx) = oneshot::channel::<()>(); + tx.send(()).unwrap(); + let (handle, future) = JoinHandle::run(rx); + handle.abort(); + let task = tokio::task::spawn(future); + handle.await; + task.await.unwrap(); } } diff --git a/crates/wasmtime/src/runtime/component/concurrent/tls.rs b/crates/wasmtime/src/runtime/component/concurrent/tls.rs index a8496a2b79..ec4dc9aca6 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/tls.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/tls.rs @@ -72,16 +72,27 @@ pub fn set(store: &mut dyn VMStore, f: impl FnOnce() -> R) -> R { pub fn get(f: impl FnOnce(&mut dyn VMStore) -> R) -> R { try_get(|val| match val { TryGet::Some(store) => f(store), - TryGet::None | TryGet::Taken => get_failed(), + TryGet::None => get_failed(false), + TryGet::Taken => get_failed(true), }) } #[cold] -fn get_failed() -> ! { - panic!( - "attempted to recursively call `tls::get` when the pointer was not \ - present or already taken by a previous call to `tls::get`" - ); +fn get_failed(taken: bool) -> ! { + if taken { + panic!( + "attempted to recursively call `Accessor::with` when the pointer \ + was already taken by a previous call to `Accessor::with`; try \ + using `RUST_BACKTRACE=1` to find two stack frames to \ + `Accessor::with` on the stack" + ); + } else { + panic!( + "`Accessor::with` was called when the TLS pointer was not \ + previously set; this is likely a bug in Wasmtime and we would \ + appreciate an issue being filed to help fix this." + ); + } } /// Values yielded to the [`try_get`] closure as an argument. diff --git a/crates/wasmtime/src/runtime/component/func/host.rs b/crates/wasmtime/src/runtime/component/func/host.rs index d858a71c13..9906731056 100644 --- a/crates/wasmtime/src/runtime/component/func/host.rs +++ b/crates/wasmtime/src/runtime/component/func/host.rs @@ -40,7 +40,7 @@ enum HostResult { } impl HostFunc { - fn from_canonical(func: F) -> Arc + fn from_canonical(func: F) -> Arc where F: Fn(StoreContextMut<'_, T>, Instance, P) -> HostResult + Send + Sync + 'static, P: ComponentNamedList + Lift + 'static, @@ -55,8 +55,9 @@ impl HostFunc { }) } - pub(crate) fn from_closure(func: F) -> Arc + pub(crate) fn from_closure(func: F) -> Arc where + T: 'static, F: Fn(StoreContextMut, P) -> Result + Send + Sync + 'static, P: ComponentNamedList + Lift + 'static, R: ComponentNamedList + Lower + 'static, @@ -67,7 +68,7 @@ impl HostFunc { } #[cfg(feature = "component-model-async")] - pub(crate) fn from_concurrent(func: F) -> Arc + pub(crate) fn from_concurrent(func: F) -> Arc where T: 'static, F: Fn(&Accessor, P) -> Pin> + Send + '_>> @@ -86,7 +87,7 @@ impl HostFunc { }) } - extern "C" fn entrypoint( + extern "C" fn entrypoint( cx: NonNull, data: NonNull, ty: u32, @@ -115,7 +116,7 @@ impl HostFunc { } } - fn new_dynamic_canonical(func: F) -> Arc + fn new_dynamic_canonical(func: F) -> Arc where F: Fn( StoreContextMut<'_, T>, @@ -152,7 +153,7 @@ impl HostFunc { } #[cfg(feature = "component-model-async")] - pub(crate) fn new_dynamic_concurrent(func: F) -> Arc + pub(crate) fn new_dynamic_concurrent(func: F) -> Arc where T: 'static, F: for<'a> Fn( @@ -922,7 +923,7 @@ pub(crate) fn validate_inbounds_dynamic( Ok(ptr) } -extern "C" fn dynamic_entrypoint( +extern "C" fn dynamic_entrypoint( cx: NonNull, data: NonNull, ty: u32, diff --git a/crates/wasmtime/src/runtime/component/mod.rs b/crates/wasmtime/src/runtime/component/mod.rs index 9219fd8513..4ed9e306f4 100644 --- a/crates/wasmtime/src/runtime/component/mod.rs +++ b/crates/wasmtime/src/runtime/component/mod.rs @@ -119,10 +119,9 @@ mod values; pub use self::component::{Component, ComponentExportIndex}; #[cfg(feature = "component-model-async")] pub use self::concurrent::{ - AbortHandle, Access, Accessor, AccessorTask, AsAccessor, ErrorContext, FutureReader, - FutureWriter, GuardedFutureReader, GuardedFutureWriter, GuardedStreamReader, - GuardedStreamWriter, ReadBuffer, StreamReader, StreamWriter, VMComponentAsyncStore, VecBuffer, - WriteBuffer, + Access, Accessor, AccessorTask, AsAccessor, ErrorContext, FutureReader, FutureWriter, + GuardedFutureReader, GuardedFutureWriter, GuardedStreamReader, GuardedStreamWriter, JoinHandle, + ReadBuffer, StreamReader, StreamWriter, VMComponentAsyncStore, VecBuffer, WriteBuffer, }; pub use self::func::{ ComponentNamedList, ComponentType, Func, Lift, Lower, TypedFunc, WasmList, WasmStr, diff --git a/crates/wasmtime/src/runtime/externals/global.rs b/crates/wasmtime/src/runtime/externals/global.rs index ecad1f3dd7..90af1d8500 100644 --- a/crates/wasmtime/src/runtime/externals/global.rs +++ b/crates/wasmtime/src/runtime/externals/global.rs @@ -158,7 +158,7 @@ impl Global { HeapType::NoFunc => Ref::Func(None), HeapType::Extern => Ref::Extern(definition.as_gc_ref().map(|r| { - let r = store.unwrap_gc_store_mut().clone_gc_ref(r); + let r = store.clone_gc_ref(r); ExternRef::from_cloned_gc_ref(&mut store, r) })), @@ -180,7 +180,7 @@ impl Global { | HeapType::ConcreteExn(_) => definition .as_gc_ref() .map(|r| { - let r = store.unwrap_gc_store_mut().clone_gc_ref(r); + let r = store.clone_gc_ref(r); AnyRef::from_cloned_gc_ref(&mut store, r) }) .into(), @@ -236,7 +236,7 @@ impl Global { Some(e) => Some(e.try_gc_ref(&store)?.unchecked_copy()), }; let new = new.as_ref(); - definition.write_gc_ref(store.unwrap_gc_store_mut(), new); + definition.write_gc_ref(&mut store, new); } Val::AnyRef(a) => { let new = match a { @@ -244,7 +244,7 @@ impl Global { Some(a) => Some(a.try_gc_ref(&store)?.unchecked_copy()), }; let new = new.as_ref(); - definition.write_gc_ref(store.unwrap_gc_store_mut(), new); + definition.write_gc_ref(&mut store, new); } Val::ExnRef(e) => { let new = match e { @@ -252,7 +252,7 @@ impl Global { Some(e) => Some(e.try_gc_ref(&store)?.unchecked_copy()), }; let new = new.as_ref(); - definition.write_gc_ref(store.unwrap_gc_store_mut(), new); + definition.write_gc_ref(&mut store, new); } } } diff --git a/crates/wasmtime/src/runtime/gc/enabled/anyref.rs b/crates/wasmtime/src/runtime/gc/enabled/anyref.rs index 906efaca43..bcbfc02567 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/anyref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/anyref.rs @@ -280,11 +280,7 @@ impl AnyRef { // (Not actually memory unsafe since we have indexed GC heaps.) pub(crate) fn _from_raw(store: &mut AutoAssertNoGc, raw: u32) -> Option> { let gc_ref = VMGcRef::from_raw_u32(raw)?; - let gc_ref = if gc_ref.is_i31() { - gc_ref.copy_i31() - } else { - store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref) - }; + let gc_ref = store.clone_gc_ref(&gc_ref); Some(Self::from_cloned_gc_ref(store, gc_ref)) } @@ -340,7 +336,7 @@ impl AnyRef { let raw = if gc_ref.is_i31() { gc_ref.as_raw_non_zero_u32() } else { - store.gc_store_mut()?.expose_gc_ref_to_wasm(gc_ref) + store.require_gc_store_mut()?.expose_gc_ref_to_wasm(gc_ref) }; Ok(raw.get()) } @@ -364,7 +360,7 @@ impl AnyRef { return Ok(HeapType::I31); } - let header = store.gc_store()?.header(gc_ref); + let header = store.require_gc_store()?.header(gc_ref); if header.kind().matches(VMGcKind::ExternRef) { return Ok(HeapType::Any); @@ -437,7 +433,11 @@ impl AnyRef { pub(crate) fn _is_eqref(&self, store: &StoreOpaque) -> Result { assert!(self.comes_from_same_store(store)); let gc_ref = self.inner.try_gc_ref(store)?; - Ok(gc_ref.is_i31() || store.gc_store()?.kind(gc_ref).matches(VMGcKind::EqRef)) + Ok(gc_ref.is_i31() + || store + .require_gc_store()? + .kind(gc_ref) + .matches(VMGcKind::EqRef)) } /// Downcast this `anyref` to an `eqref`. @@ -558,7 +558,11 @@ impl AnyRef { pub(crate) fn _is_struct(&self, store: &StoreOpaque) -> Result { let gc_ref = self.inner.try_gc_ref(store)?; - Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::StructRef)) + Ok(!gc_ref.is_i31() + && store + .require_gc_store()? + .kind(gc_ref) + .matches(VMGcKind::StructRef)) } /// Downcast this `anyref` to a `structref`. @@ -622,7 +626,11 @@ impl AnyRef { pub(crate) fn _is_array(&self, store: &StoreOpaque) -> Result { let gc_ref = self.inner.try_gc_ref(store)?; - Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::ArrayRef)) + Ok(!gc_ref.is_i31() + && store + .require_gc_store()? + .kind(gc_ref) + .matches(VMGcKind::ArrayRef)) } /// Downcast this `anyref` to an `arrayref`. diff --git a/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs b/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs index efc9aa21dc..d44ff776bf 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs @@ -411,7 +411,7 @@ impl ArrayRef { // Allocate the array and write each field value into the appropriate // offset. let arrayref = store - .gc_store_mut()? + .require_gc_store_mut()? .alloc_uninit_array(allocator.type_index(), len, allocator.layout()) .context("unrecoverable error when allocating new `arrayref`")? .map_err(|n| GcHeapOutOfMemory::new((), n))?; @@ -432,7 +432,7 @@ impl ArrayRef { })() { Ok(()) => Ok(Rooted::new(&mut store, arrayref.into())), Err(e) => { - store.gc_store_mut()?.dealloc_uninit_array(arrayref); + store.require_gc_store_mut()?.dealloc_uninit_array(arrayref); Err(e) } } @@ -633,7 +633,7 @@ impl ArrayRef { assert!(self.comes_from_same_store(store)); let gc_ref = self.inner.try_gc_ref(store)?; debug_assert!({ - let header = store.gc_store()?.header(gc_ref); + let header = store.require_gc_store()?.header(gc_ref); header.kind().matches(VMGcKind::ArrayRef) }); let arrayref = gc_ref.as_arrayref_unchecked(); @@ -667,7 +667,7 @@ impl ArrayRef { let store = AutoAssertNoGc::new(store); let gc_ref = self.inner.try_gc_ref(&store)?; - let header = store.gc_store()?.header(gc_ref); + let header = store.require_gc_store()?.header(gc_ref); debug_assert!(header.kind().matches(VMGcKind::ArrayRef)); let len = self._len(&store)?; @@ -720,7 +720,7 @@ impl ArrayRef { fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> { assert!(self.comes_from_same_store(&store)); let gc_ref = self.inner.try_gc_ref(store)?; - Ok(store.gc_store()?.header(gc_ref)) + Ok(store.require_gc_store()?.header(gc_ref)) } fn arrayref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMArrayRef> { @@ -843,7 +843,7 @@ impl ArrayRef { pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result { let gc_ref = self.inner.try_gc_ref(store)?; - let header = store.gc_store()?.header(gc_ref); + let header = store.require_gc_store()?.header(gc_ref); debug_assert!(header.kind().matches(VMGcKind::ArrayRef)); Ok(header.ty().expect("arrayrefs should have concrete types")) } diff --git a/crates/wasmtime/src/runtime/gc/enabled/eqref.rs b/crates/wasmtime/src/runtime/gc/enabled/eqref.rs index 5c47f672ba..7b6d27a16b 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/eqref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/eqref.rs @@ -198,7 +198,7 @@ impl EqRef { return Ok(HeapType::I31); } - let header = store.gc_store()?.header(gc_ref); + let header = store.require_gc_store()?.header(gc_ref); if header.kind().matches(VMGcKind::StructRef) { return Ok(HeapType::ConcreteStruct( @@ -320,7 +320,11 @@ impl EqRef { pub(crate) fn _is_struct(&self, store: &StoreOpaque) -> Result { let gc_ref = self.inner.try_gc_ref(store)?; - Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::StructRef)) + Ok(!gc_ref.is_i31() + && store + .require_gc_store()? + .kind(gc_ref) + .matches(VMGcKind::StructRef)) } /// Downcast this `eqref` to a `structref`. @@ -384,7 +388,11 @@ impl EqRef { pub(crate) fn _is_array(&self, store: &StoreOpaque) -> Result { let gc_ref = self.inner.try_gc_ref(store)?; - Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::ArrayRef)) + Ok(!gc_ref.is_i31() + && store + .require_gc_store()? + .kind(gc_ref) + .matches(VMGcKind::ArrayRef)) } /// Downcast this `eqref` to an `arrayref`. diff --git a/crates/wasmtime/src/runtime/gc/enabled/exnref.rs b/crates/wasmtime/src/runtime/gc/enabled/exnref.rs index ed7a65fb51..27c866baf4 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/exnref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/exnref.rs @@ -166,7 +166,7 @@ impl ExnRef { // (Not actually memory unsafe since we have indexed GC heaps.) pub(crate) fn _from_raw(store: &mut AutoAssertNoGc, raw: u32) -> Option> { let gc_ref = VMGcRef::from_raw_u32(raw)?; - let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref); + let gc_ref = store.clone_gc_ref(&gc_ref); Some(Self::from_cloned_gc_ref(store, gc_ref)) } @@ -335,7 +335,7 @@ impl ExnRef { // Allocate the exn and write each field value into the appropriate // offset. let exnref = store - .gc_store_mut()? + .require_gc_store_mut()? .alloc_uninit_exn(allocator.type_index(), &allocator.layout()) .context("unrecoverable error when allocating new `exnref`")? .map_err(|n| GcHeapOutOfMemory::new((), n))?; @@ -361,7 +361,7 @@ impl ExnRef { })() { Ok(()) => Ok(Rooted::new(&mut store, exnref.into())), Err(e) => { - store.gc_store_mut()?.dealloc_uninit_exn(exnref); + store.require_gc_store_mut()?.dealloc_uninit_exn(exnref); Err(e) } } @@ -369,7 +369,7 @@ impl ExnRef { pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result { let gc_ref = self.inner.try_gc_ref(store)?; - let header = store.gc_store()?.header(gc_ref); + let header = store.require_gc_store()?.header(gc_ref); debug_assert!(header.kind().matches(VMGcKind::ExnRef)); Ok(header.ty().expect("exnrefs should have concrete types")) } @@ -421,7 +421,7 @@ impl ExnRef { let raw = if gc_ref.is_i31() { gc_ref.as_raw_non_zero_u32() } else { - store.gc_store_mut()?.expose_gc_ref_to_wasm(gc_ref) + store.require_gc_store_mut()?.expose_gc_ref_to_wasm(gc_ref) }; Ok(raw.get()) } @@ -501,7 +501,7 @@ impl ExnRef { let store = AutoAssertNoGc::new(store); let gc_ref = self.inner.try_gc_ref(&store)?; - let header = store.gc_store()?.header(gc_ref); + let header = store.require_gc_store()?.header(gc_ref); debug_assert!(header.kind().matches(VMGcKind::ExnRef)); let index = header.ty().expect("exnrefs should have concrete types"); @@ -554,7 +554,7 @@ impl ExnRef { fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> { assert!(self.comes_from_same_store(&store)); let gc_ref = self.inner.try_gc_ref(store)?; - Ok(store.gc_store()?.header(gc_ref)) + Ok(store.require_gc_store()?.header(gc_ref)) } fn exnref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMExnRef> { diff --git a/crates/wasmtime/src/runtime/gc/enabled/externref.rs b/crates/wasmtime/src/runtime/gc/enabled/externref.rs index 90698f65f0..1f74a2c196 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/externref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/externref.rs @@ -229,7 +229,7 @@ impl ExternRef { let gc_ref = store .retry_after_gc(value, |store, value| { store - .gc_store_mut()? + .require_gc_store_mut()? .alloc_externref(value) .context("unrecoverable error when allocating new `externref`")? .map_err(|(x, n)| GcHeapOutOfMemory::new(x, n).into()) @@ -346,7 +346,7 @@ impl ExternRef { let gc_ref = store .retry_after_gc_async(value, |store, value| { store - .gc_store_mut()? + .require_gc_store_mut()? .alloc_externref(value) .context("unrecoverable error when allocating new `externref`")? .map_err(|(x, n)| GcHeapOutOfMemory::new(x, n).into()) @@ -435,11 +435,13 @@ impl ExternRef { store: &mut AutoAssertNoGc<'_>, gc_ref: VMGcRef, ) -> Rooted { - assert!( - gc_ref.is_extern_ref(&*store.unwrap_gc_store().gc_heap) - || gc_ref.is_any_ref(&*store.unwrap_gc_store().gc_heap), - "GC reference {gc_ref:#p} should be an externref or anyref" - ); + if !gc_ref.is_i31() { + assert!( + gc_ref.is_extern_ref(&*store.unwrap_gc_store().gc_heap) + || gc_ref.is_any_ref(&*store.unwrap_gc_store().gc_heap), + "GC reference {gc_ref:#p} should be an externref or anyref" + ); + } Rooted::new(store, gc_ref) } @@ -481,7 +483,10 @@ impl ExternRef { { let store = store.into().0; let gc_ref = self.inner.try_gc_ref(&store)?; - let gc_store = store.gc_store()?; + if gc_ref.is_i31() { + return Ok(None); + } + let gc_store = store.require_gc_store()?; if let Some(externref) = gc_ref.as_externref(&*gc_store.gc_heap) { Ok(Some(gc_store.externref_host_data(externref))) } else { @@ -532,7 +537,10 @@ impl ExternRef { // so that we can get the store's GC store. But importantly we cannot // trigger a GC while we are working with `gc_ref` here. let gc_ref = self.inner.try_gc_ref(store)?.unchecked_copy(); - let gc_store = store.gc_store_mut()?; + if gc_ref.is_i31() { + return Ok(None); + } + let gc_store = store.require_gc_store_mut()?; if let Some(externref) = gc_ref.as_externref(&*gc_store.gc_heap) { Ok(Some(gc_store.externref_host_data_mut(externref))) } else { @@ -579,7 +587,7 @@ impl ExternRef { // (Not actually memory unsafe since we have indexed GC heaps.) pub(crate) fn _from_raw(store: &mut AutoAssertNoGc, raw: u32) -> Option> { let gc_ref = VMGcRef::from_raw_u32(raw)?; - let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref); + let gc_ref = store.clone_gc_ref(&gc_ref); Some(Self::from_cloned_gc_ref(store, gc_ref)) } diff --git a/crates/wasmtime/src/runtime/gc/enabled/rooting.rs b/crates/wasmtime/src/runtime/gc/enabled/rooting.rs index 91cc444dbd..a352c64936 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/rooting.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/rooting.rs @@ -168,14 +168,14 @@ mod sealed { /// Panics if this root is not associated with the given store. fn clone_gc_ref(&self, store: &mut AutoAssertNoGc<'_>) -> Option { let gc_ref = self.get_gc_ref(store)?.unchecked_copy(); - Some(store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref)) + Some(store.clone_gc_ref(&gc_ref)) } /// Same as `clone_gc_ref` but returns an error instead of `None` for /// objects that have been unrooted. fn try_clone_gc_ref(&self, store: &mut AutoAssertNoGc<'_>) -> Result { let gc_ref = self.try_gc_ref(store)?.unchecked_copy(); - Ok(store.gc_store_mut()?.clone_gc_ref(&gc_ref)) + Ok(store.clone_gc_ref(&gc_ref)) } } } @@ -263,14 +263,14 @@ impl GcRootIndex { /// particular `T: GcRef`. pub(crate) fn try_clone_gc_ref(&self, store: &mut AutoAssertNoGc<'_>) -> Result { let gc_ref = self.try_gc_ref(store)?.unchecked_copy(); - Ok(store.gc_store_mut()?.clone_gc_ref(&gc_ref)) + Ok(store.clone_gc_ref(&gc_ref)) } } /// This is a bit-packed version of /// /// ```ignore -/// enema { +/// enum { /// Lifo(usize), /// Manual(SlabId), /// } @@ -1031,7 +1031,7 @@ impl Rooted { from_cloned_gc_ref: impl Fn(&mut AutoAssertNoGc<'_>, VMGcRef) -> Self, ) -> Option { let gc_ref = VMGcRef::from_raw_u32(raw_gc_ref)?; - let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref); + let gc_ref = store.clone_gc_ref(&gc_ref); Some(from_cloned_gc_ref(store, gc_ref)) } } @@ -1781,7 +1781,7 @@ where val_raw: impl Fn(u32) -> ValRaw, ) -> Result<()> { let gc_ref = self.try_clone_gc_ref(store)?; - let raw = store.gc_store_mut()?.expose_gc_ref_to_wasm(gc_ref); + let raw = store.require_gc_store_mut()?.expose_gc_ref_to_wasm(gc_ref); ptr.write(val_raw(raw.get())); Ok(()) } @@ -1795,7 +1795,7 @@ where ) -> Self { debug_assert_ne!(raw_gc_ref, 0); let gc_ref = VMGcRef::from_raw_u32(raw_gc_ref).expect("non-null"); - let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref); + let gc_ref = store.clone_gc_ref(&gc_ref); RootSet::with_lifo_scope(store, |store| { let rooted = from_cloned_gc_ref(store, gc_ref); rooted @@ -1829,7 +1829,7 @@ where from_cloned_gc_ref: impl Fn(&mut AutoAssertNoGc<'_>, VMGcRef) -> Rooted, ) -> Option { let gc_ref = VMGcRef::from_raw_u32(raw_gc_ref)?; - let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref); + let gc_ref = store.clone_gc_ref(&gc_ref); RootSet::with_lifo_scope(store, |store| { let rooted = from_cloned_gc_ref(store, gc_ref); Some( diff --git a/crates/wasmtime/src/runtime/gc/enabled/structref.rs b/crates/wasmtime/src/runtime/gc/enabled/structref.rs index 8ae1f0ddb7..d65beef4e5 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/structref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/structref.rs @@ -359,7 +359,7 @@ impl StructRef { // Allocate the struct and write each field value into the appropriate // offset. let structref = store - .gc_store_mut()? + .require_gc_store_mut()? .alloc_uninit_struct(allocator.type_index(), &allocator.layout()) .context("unrecoverable error when allocating new `structref`")? .map_err(|n| GcHeapOutOfMemory::new((), n))?; @@ -383,7 +383,9 @@ impl StructRef { })() { Ok(()) => Ok(Rooted::new(&mut store, structref.into())), Err(e) => { - store.gc_store_mut()?.dealloc_uninit_struct(structref); + store + .require_gc_store_mut()? + .dealloc_uninit_struct(structref); Err(e) } } @@ -473,7 +475,7 @@ impl StructRef { let store = AutoAssertNoGc::new(store); let gc_ref = self.inner.try_gc_ref(&store)?; - let header = store.gc_store()?.header(gc_ref); + let header = store.require_gc_store()?.header(gc_ref); debug_assert!(header.kind().matches(VMGcKind::StructRef)); let index = header.ty().expect("structrefs should have concrete types"); @@ -526,7 +528,7 @@ impl StructRef { fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> { assert!(self.comes_from_same_store(&store)); let gc_ref = self.inner.try_gc_ref(store)?; - Ok(store.gc_store()?.header(gc_ref)) + Ok(store.require_gc_store()?.header(gc_ref)) } fn structref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMStructRef> { @@ -638,7 +640,7 @@ impl StructRef { pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result { let gc_ref = self.inner.try_gc_ref(store)?; - let header = store.gc_store()?.header(gc_ref); + let header = store.require_gc_store()?.header(gc_ref); debug_assert!(header.kind().matches(VMGcKind::StructRef)); Ok(header.ty().expect("structrefs should have concrete types")) } diff --git a/crates/wasmtime/src/runtime/instance.rs b/crates/wasmtime/src/runtime/instance.rs index fef680a35a..da2693c497 100644 --- a/crates/wasmtime/src/runtime/instance.rs +++ b/crates/wasmtime/src/runtime/instance.rs @@ -274,7 +274,7 @@ impl Instance { // Allocate the GC heap, if necessary. if module.env_module().needs_gc_heap { - let _ = store.gc_store_mut()?; + store.ensure_gc_store()?; } let compiled_module = module.compiled_module(); diff --git a/crates/wasmtime/src/runtime/memory.rs b/crates/wasmtime/src/runtime/memory.rs index 2259475082..50e79d91a8 100644 --- a/crates/wasmtime/src/runtime/memory.rs +++ b/crates/wasmtime/src/runtime/memory.rs @@ -796,6 +796,7 @@ pub unsafe trait MemoryCreator: Send + Sync { /// # fn main() -> anyhow::Result<()> { /// let mut config = Config::new(); /// config.wasm_threads(true); +/// # if Engine::new(&config).is_err() { return Ok(()); } /// let engine = Engine::new(&config)?; /// let mut store = Store::new(&engine, ()); /// diff --git a/crates/wasmtime/src/runtime/module.rs b/crates/wasmtime/src/runtime/module.rs index 8ce0161502..b2efaa5fd7 100644 --- a/crates/wasmtime/src/runtime/module.rs +++ b/crates/wasmtime/src/runtime/module.rs @@ -1,7 +1,7 @@ use crate::prelude::*; #[cfg(feature = "std")] use crate::runtime::vm::open_file_for_mmap; -use crate::runtime::vm::{CompiledModuleId, ModuleMemoryImages, VMWasmCallFunction}; +use crate::runtime::vm::{CompiledModuleId, MmapVec, ModuleMemoryImages, VMWasmCallFunction}; use crate::sync::OnceLock; use crate::{ Engine, @@ -1114,7 +1114,7 @@ impl Module { let images = self .inner .memory_images - .get_or_try_init(|| memory_images(&self.inner.engine, &self.inner.module))? + .get_or_try_init(|| memory_images(&self.inner))? .as_ref(); Ok(images) } @@ -1164,21 +1164,30 @@ fn _assert_send_sync() { /// Helper method to construct a `ModuleMemoryImages` for an associated /// `CompiledModule`. -fn memory_images(engine: &Engine, module: &CompiledModule) -> Result> { +fn memory_images(inner: &Arc) -> Result> { // If initialization via copy-on-write is explicitly disabled in // configuration then this path is skipped entirely. - if !engine.tunables().memory_init_cow { + if !inner.engine.tunables().memory_init_cow { return Ok(None); } // ... otherwise logic is delegated to the `ModuleMemoryImages::new` // constructor. - let mmap = if engine.config().force_memory_init_memfd { - None - } else { - Some(module.mmap()) - }; - ModuleMemoryImages::new(module.module(), module.code_memory().wasm_data(), mmap) + ModuleMemoryImages::new( + &inner.engine, + inner.module.module(), + inner.code.code_memory(), + ) +} + +impl crate::vm::ModuleMemoryImageSource for CodeMemory { + fn wasm_data(&self) -> &[u8] { + ::wasm_data(self) + } + + fn mmap(&self) -> Option<&MmapVec> { + Some(::mmap(self)) + } } #[cfg(test)] diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs index 33e4b6dae5..a2a4289cd4 100644 --- a/crates/wasmtime/src/runtime/store.rs +++ b/crates/wasmtime/src/runtime/store.rs @@ -101,7 +101,7 @@ use crate::{Global, Instance, Memory, Table, Uninhabited}; use alloc::sync::Arc; use core::fmt; use core::marker; -use core::mem::{self, ManuallyDrop}; +use core::mem::{self, ManuallyDrop, MaybeUninit}; use core::num::NonZeroU64; use core::ops::{Deref, DerefMut}; use core::pin::Pin; @@ -1419,8 +1419,23 @@ impl StoreOpaque { &mut self.vm_store_context } + /// Performs a lazy allocation of the `GcStore` within this store, returning + /// the previous allocation if it's already present. + /// + /// This method will, if necessary, allocate a new `GcStore` -- linear + /// memory and all. This is a blocking operation due to + /// `ResourceLimiterAsync` which means that this should only be executed + /// in a fiber context at this time. + #[inline] + pub(crate) fn ensure_gc_store(&mut self) -> Result<&mut GcStore> { + if self.gc_store.is_some() { + return Ok(self.gc_store.as_mut().unwrap()); + } + self.allocate_gc_store() + } + #[inline(never)] - pub(crate) fn allocate_gc_heap(&mut self) -> Result<()> { + fn allocate_gc_store(&mut self) -> Result<&mut GcStore> { log::trace!("allocating GC heap for store {:?}", self.id()); assert!(self.gc_store.is_none()); @@ -1433,8 +1448,7 @@ impl StoreOpaque { let vmstore = self.traitobj(); let gc_store = allocate_gc_store(self.engine(), vmstore, self.get_pkey())?; self.vm_store_context.gc_heap = gc_store.vmmemory_definition(); - self.gc_store = Some(gc_store); - return Ok(()); + return Ok(self.gc_store.insert(gc_store)); #[cfg(feature = "gc")] fn allocate_gc_store( @@ -1493,24 +1507,45 @@ impl StoreOpaque { } } + /// Helper method to require that a `GcStore` was previously allocated for + /// this store, failing if it has not yet been allocated. + /// + /// Note that this should only be used in a context where allocation of a + /// `GcStore` is sure to have already happened prior, otherwise this may + /// return a confusing error to embedders which is a bug in Wasmtime. + /// + /// Some situations where it's safe to call this method: + /// + /// * There's already a non-null and non-i31 `VMGcRef` in scope. By existing + /// this shows proof that the `GcStore` was previously allocated. + /// * During instantiation and instance's `needs_gc_heap` flag will be + /// handled and instantiation will automatically create a GC store. #[inline] - pub(crate) fn gc_store(&self) -> Result<&GcStore> { + #[cfg(feature = "gc")] + pub(crate) fn require_gc_store(&self) -> Result<&GcStore> { match &self.gc_store { Some(gc_store) => Ok(gc_store), None => bail!("GC heap not initialized yet"), } } + /// Same as [`Self::require_gc_store`], but mutable. #[inline] - pub(crate) fn gc_store_mut(&mut self) -> Result<&mut GcStore> { - if self.gc_store.is_none() { - self.allocate_gc_heap()?; + #[cfg(feature = "gc")] + pub(crate) fn require_gc_store_mut(&mut self) -> Result<&mut GcStore> { + match &mut self.gc_store { + Some(gc_store) => Ok(gc_store), + None => bail!("GC heap not initialized yet"), } - Ok(self.unwrap_gc_store_mut()) } - /// If this store is configured with a GC heap, return a mutable reference - /// to it. Otherwise, return `None`. + /// Attempts to access the GC store that has been previously allocated. + /// + /// This method will return `Some` if the GC store was previously allocated. + /// A `None` return value means either that the GC heap hasn't yet been + /// allocated or that it does not need to be allocated for this store. Note + /// that to require a GC store in a particular situation it's recommended to + /// use [`Self::require_gc_store_mut`] instead. #[inline] pub(crate) fn optional_gc_store_mut(&mut self) -> Option<&mut GcStore> { if cfg!(not(feature = "gc")) || !self.engine.features().gc_types() { @@ -1521,15 +1556,23 @@ impl StoreOpaque { } } + /// Helper to assert that a GC store was previously allocated and is + /// present. + /// + /// # Panics + /// + /// This method will panic if the GC store has not yet been allocated. This + /// should only be used in a context where there's an existing GC reference, + /// for example, or if `ensure_gc_store` has already been called. #[inline] #[track_caller] - #[cfg(feature = "gc")] pub(crate) fn unwrap_gc_store(&self) -> &GcStore { self.gc_store .as_ref() .expect("attempted to access the store's GC heap before it has been allocated") } + /// Same as [`Self::unwrap_gc_store`], but mutable. #[inline] #[track_caller] pub(crate) fn unwrap_gc_store_mut(&mut self) -> &mut GcStore { @@ -1638,7 +1681,7 @@ impl StoreOpaque { let raw: u32 = unsafe { core::ptr::read(stack_slot) }; log::trace!("Stack slot @ {stack_slot:p} = {raw:#x}"); - let gc_ref = VMGcRef::from_raw_u32(raw); + let gc_ref = vm::VMGcRef::from_raw_u32(raw); if gc_ref.is_some() { unsafe { gc_roots_list @@ -1726,6 +1769,38 @@ impl StoreOpaque { self.gc_host_alloc_types.insert(ty); } + /// Helper function execute a `init_gc_ref` when placing `gc_ref` in `dest`. + /// + /// This avoids allocating `GcStore` where possible. + pub(crate) fn init_gc_ref( + &mut self, + dest: &mut MaybeUninit>, + gc_ref: Option<&VMGcRef>, + ) { + if GcStore::needs_init_barrier(gc_ref) { + self.unwrap_gc_store_mut().init_gc_ref(dest, gc_ref) + } else { + dest.write(gc_ref.map(|r| r.copy_i31())); + } + } + + /// Helper function execute a write barrier when placing `gc_ref` in `dest`. + /// + /// This avoids allocating `GcStore` where possible. + pub(crate) fn write_gc_ref(&mut self, dest: &mut Option, gc_ref: Option<&VMGcRef>) { + GcStore::write_gc_ref_optional_store(self.optional_gc_store_mut(), dest, gc_ref) + } + + /// Helper function to clone `gc_ref` notably avoiding allocating a + /// `GcStore` where possible. + pub(crate) fn clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef { + if gc_ref.is_i31() { + gc_ref.copy_i31() + } else { + self.unwrap_gc_store_mut().clone_gc_ref(gc_ref) + } + } + pub fn get_fuel(&self) -> Result { anyhow::ensure!( self.engine().tunables().consume_fuel, @@ -2298,24 +2373,6 @@ unsafe impl vm::VMStore for StoreInner { delta_result } - #[cfg(feature = "gc")] - unsafe fn maybe_async_grow_or_collect_gc_heap( - &mut self, - root: Option, - bytes_needed: Option, - ) -> Result> { - unsafe { self.inner.maybe_async_gc(root, bytes_needed) } - } - - #[cfg(not(feature = "gc"))] - unsafe fn maybe_async_grow_or_collect_gc_heap( - &mut self, - root: Option, - _bytes_needed: Option, - ) -> Result> { - Ok(root) - } - #[cfg(feature = "component-model")] fn component_calls(&mut self) -> &mut vm::component::CallContexts { &mut self.component_calls diff --git a/crates/wasmtime/src/runtime/store/gc.rs b/crates/wasmtime/src/runtime/store/gc.rs index 43eaf6e590..3bf52ad393 100644 --- a/crates/wasmtime/src/runtime/store/gc.rs +++ b/crates/wasmtime/src/runtime/store/gc.rs @@ -2,6 +2,7 @@ use super::*; use crate::GcHeapOutOfMemory; +use crate::runtime::vm::VMGcRef; impl StoreOpaque { /// Collect garbage, potentially growing the GC heap. @@ -51,7 +52,7 @@ impl StoreOpaque { .get_gc_ref(&scope) .expect("still in scope") .unchecked_copy(); - Some(scope.gc_store_mut()?.clone_gc_ref(&r)) + Some(scope.clone_gc_ref(&r)) } }; @@ -82,7 +83,7 @@ impl StoreOpaque { // Take the GC heap's underlying memory out of the GC heap, attempt to // grow it, then replace it. - let mut memory = unsafe { self.unwrap_gc_store_mut().gc_heap.take_memory() }; + let mut memory = self.unwrap_gc_store_mut().gc_heap.take_memory(); let mut delta_bytes_grown = 0; let grow_result: Result<()> = (|| { let page_size = self.engine().tunables().gc_heap_memory_type().page_size(); @@ -162,6 +163,7 @@ impl StoreOpaque { !self.async_support(), "use the `*_async` versions of methods when async is configured" ); + self.ensure_gc_store()?; match alloc_func(self, value) { Ok(x) => Ok(x), Err(e) => match e.downcast::>() { @@ -189,6 +191,7 @@ impl StoreOpaque { where T: Send + Sync + 'static, { + self.ensure_gc_store()?; match alloc_func(self, value) { Ok(x) => Ok(x), Err(e) => match e.downcast::>() { @@ -244,6 +247,7 @@ impl StoreOpaque { self.async_support(), "you must configure async to use the `*_async` versions of methods" ); + self.ensure_gc_store()?; match alloc_func(self, value) { Ok(x) => Ok(x), Err(e) => match e.downcast::>() { diff --git a/crates/wasmtime/src/runtime/trampoline/global.rs b/crates/wasmtime/src/runtime/trampoline/global.rs index 3907f4c7eb..2fad1d81aa 100644 --- a/crates/wasmtime/src/runtime/trampoline/global.rs +++ b/crates/wasmtime/src/runtime/trampoline/global.rs @@ -52,7 +52,7 @@ pub fn generate_global_export( Some(x) => Some(x.try_gc_ref(&store)?.unchecked_copy()), }; let new = new.as_ref(); - global.write_gc_ref(store.gc_store_mut()?, new); + global.write_gc_ref(&mut store, new); } Val::AnyRef(a) => { let new = match a { @@ -60,7 +60,7 @@ pub fn generate_global_export( Some(a) => Some(a.try_gc_ref(&store)?.unchecked_copy()), }; let new = new.as_ref(); - global.write_gc_ref(store.gc_store_mut()?, new); + global.write_gc_ref(&mut store, new); } Val::ExnRef(e) => { let new = match e { @@ -68,7 +68,7 @@ pub fn generate_global_export( Some(e) => Some(e.try_gc_ref(&store)?.unchecked_copy()), }; let new = new.as_ref(); - global.write_gc_ref(store.gc_store_mut()?, new); + global.write_gc_ref(&mut store, new); } } } diff --git a/crates/wasmtime/src/runtime/vm.rs b/crates/wasmtime/src/runtime/vm.rs index b5185ddfc8..13e0567bac 100644 --- a/crates/wasmtime/src/runtime/vm.rs +++ b/crates/wasmtime/src/runtime/vm.rs @@ -60,6 +60,8 @@ mod imports; mod instance; mod memory; mod mmap_vec; +#[cfg(has_virtual_memory)] +mod pagemap_disabled; mod provenance; mod send_sync_ptr; mod stack_switching; @@ -161,6 +163,17 @@ cfg_if::cfg_if! { } } +/// Source of data used for [`MemoryImage`] +pub trait ModuleMemoryImageSource: Send + Sync + 'static { + /// Returns this image's slice of all wasm data for a module which is then + /// further sub-sliced for a particular initialization segment. + fn wasm_data(&self) -> &[u8]; + + /// Optionally returns the backing mmap. Used for using the backing mmap's + /// file to perform other mmaps, for example. + fn mmap(&self) -> Option<&MmapVec>; +} + /// Dynamic runtime functionality needed by this crate throughout the execution /// of a wasm instance. /// @@ -229,26 +242,6 @@ pub unsafe trait VMStore: 'static { #[cfg(target_has_atomic = "64")] fn new_epoch(&mut self) -> Result; - /// Callback invoked whenever an instance needs to grow-or-collect the GC - /// heap. - /// - /// Optionally given a GC reference that is rooted for the collection, and - /// then whose updated GC reference is returned. - /// - /// Optionally given a number of bytes that are needed for an upcoming - /// allocation. - /// - /// Cooperative, async-yielding (if configured) is completely transparent, - /// but must be called from a fiber stack in that case. - /// - /// If the async GC was cancelled, returns an error. This should be raised - /// as a trap to clean up Wasm execution. - unsafe fn maybe_async_grow_or_collect_gc_heap( - &mut self, - root: Option, - bytes_needed: Option, - ) -> Result>; - /// Metadata required for resources for the component model. #[cfg(feature = "component-model")] fn component_calls(&mut self) -> &mut component::CallContexts; diff --git a/crates/wasmtime/src/runtime/vm/const_expr.rs b/crates/wasmtime/src/runtime/vm/const_expr.rs index 81e28ce2fd..6a4f608210 100644 --- a/crates/wasmtime/src/runtime/vm/const_expr.rs +++ b/crates/wasmtime/src/runtime/vm/const_expr.rs @@ -10,9 +10,7 @@ use crate::{ use smallvec::SmallVec; use wasmtime_environ::{ConstExpr, ConstOp, FuncIndex, GlobalIndex}; #[cfg(feature = "gc")] -use wasmtime_environ::{ - Unsigned, VMSharedTypeIndex, WasmCompositeInnerType, WasmCompositeType, WasmSubType, -}; +use wasmtime_environ::{VMSharedTypeIndex, WasmCompositeInnerType, WasmCompositeType, WasmSubType}; /// An interpreter for const expressions. /// @@ -283,7 +281,7 @@ impl ConstExprEvaluator { .unwrap_engine_type_index(); let ty = ArrayType::from_shared_type_index(store.engine(), ty); - let len = self.pop()?.get_i32().unsigned(); + let len = self.pop()?.get_i32().cast_unsigned(); let elem = unsafe { Val::_from_raw(&mut store, self.pop()?, ty.element_type().unpack()) @@ -302,7 +300,7 @@ impl ConstExprEvaluator { .unwrap_engine_type_index(); let ty = ArrayType::from_shared_type_index(store.engine(), ty); - let len = self.pop()?.get_i32().unsigned(); + let len = self.pop()?.get_i32().cast_unsigned(); let elem = Val::default_for_ty(ty.element_type().unpack()) .expect("type should have a default value"); diff --git a/crates/wasmtime/src/runtime/vm/cow.rs b/crates/wasmtime/src/runtime/vm/cow.rs index 68f2eb7e1f..83c5a04505 100644 --- a/crates/wasmtime/src/runtime/vm/cow.rs +++ b/crates/wasmtime/src/runtime/vm/cow.rs @@ -2,12 +2,15 @@ //! modules, and logic to support mapping these backing images into memory. use super::sys::DecommitBehavior; +use crate::Engine; use crate::prelude::*; -use crate::runtime::vm::sys::vm::{self, MemoryImageSource}; -use crate::runtime::vm::{HostAlignedByteCount, MmapOffset, MmapVec, host_page_size}; +use crate::runtime::vm::sys::vm::{self, MemoryImageSource, PageMap, reset_with_pagemap}; +use crate::runtime::vm::{ + HostAlignedByteCount, MmapOffset, ModuleMemoryImageSource, host_page_size, +}; use alloc::sync::Arc; +use core::fmt; use core::ops::Range; -use core::ptr; use wasmtime_environ::{DefinedMemoryIndex, MemoryInitialization, Module, PrimaryMap, Tunables}; /// Backing images for memories in a module. @@ -26,7 +29,6 @@ impl ModuleMemoryImages { } /// One backing image for one memory. -#[derive(Debug, PartialEq)] pub struct MemoryImage { /// The platform-specific source of this image. /// @@ -60,20 +62,29 @@ pub struct MemoryImage { /// /// Must be a multiple of the system page size. linear_memory_offset: HostAlignedByteCount, + + /// The original source of data that this image is derived from. + module_source: Arc, + + /// The offset, within `module_source.wasm_data()`, that this image starts + /// at. + module_source_offset: usize, } impl MemoryImage { fn new( + engine: &Engine, page_size: u32, linear_memory_offset: HostAlignedByteCount, - data: &[u8], - mmap: Option<&MmapVec>, + module_source: &Arc, + data_range: Range, ) -> Result> { let assert_page_aligned = |val: usize| { assert_eq!(val % (page_size as usize), 0); }; // Sanity-check that various parameters are page-aligned. - let len = HostAlignedByteCount::new(data.len()).expect("memory image data is page-aligned"); + let len = + HostAlignedByteCount::new(data_range.len()).expect("memory image data is page-aligned"); // If a backing `mmap` is present then `data` should be a sub-slice of // the `mmap`. The sanity-checks here double-check that. Additionally @@ -89,25 +100,30 @@ impl MemoryImage { // files, but for now this is still a Linux-specific region of Wasmtime. // Some work will be needed to get this file compiling for macOS and // Windows. - if let Some(mmap) = mmap { - let start = mmap.as_ptr() as usize; - let end = start + mmap.len(); - let data_start = data.as_ptr() as usize; - let data_end = data_start + data.len(); - assert!(start <= data_start && data_end <= end); - assert_page_aligned(start); - assert_page_aligned(data_start); - assert_page_aligned(data_end); - - #[cfg(feature = "std")] - if let Some(file) = mmap.original_file() { - if let Some(source) = MemoryImageSource::from_file(file) { - return Ok(Some(MemoryImage { - source, - source_offset: u64::try_from(data_start - start).unwrap(), - linear_memory_offset, - len, - })); + let data = &module_source.wasm_data()[data_range.clone()]; + if !engine.config().force_memory_init_memfd { + if let Some(mmap) = module_source.mmap() { + let start = mmap.as_ptr() as usize; + let end = start + mmap.len(); + let data_start = data.as_ptr() as usize; + let data_end = data_start + data.len(); + assert!(start <= data_start && data_end <= end); + assert_page_aligned(start); + assert_page_aligned(data_start); + assert_page_aligned(data_end); + + #[cfg(feature = "std")] + if let Some(file) = mmap.original_file() { + if let Some(source) = MemoryImageSource::from_file(file) { + return Ok(Some(MemoryImage { + source, + source_offset: u64::try_from(data_start - start).unwrap(), + linear_memory_offset, + len, + module_source: module_source.clone(), + module_source_offset: data_range.start, + })); + } } } } @@ -120,6 +136,8 @@ impl MemoryImage { source_offset: 0, linear_memory_offset, len, + module_source: module_source.clone(), + module_source_offset: data_range.start, })); } @@ -153,9 +171,9 @@ impl ModuleMemoryImages { /// passed in as part of a `InstanceAllocationRequest` to speed up /// instantiation and execution by using copy-on-write-backed memories. pub fn new( + engine: &Engine, module: &Module, - wasm_data: &[u8], - mmap: Option<&MmapVec>, + source: &Arc, ) -> Result> { let map = match &module.memory_initialization { MemoryInitialization::Static { map } => map, @@ -183,15 +201,11 @@ impl ModuleMemoryImages { } }; - // Get the image for this wasm module as a subslice of `wasm_data`, - // and then use that to try to create the `MemoryImage`. If this - // creation files then we fail creating `ModuleMemoryImages` since this - // memory couldn't be represented. - let data = &wasm_data[init.data.start as usize..init.data.end as usize]; + let data_range = init.data.start as usize..init.data.end as usize; if module.memories[memory_index] .minimum_byte_size() .map_or(false, |mem_initial_len| { - init.offset + u64::try_from(data.len()).unwrap() > mem_initial_len + init.offset + u64::try_from(data_range.len()).unwrap() > mem_initial_len }) { // The image is rounded up to multiples of the host OS page @@ -211,7 +225,10 @@ impl ModuleMemoryImages { }; let offset = HostAlignedByteCount::new(offset_usize) .expect("memory init offset is a multiple of the host page size"); - let image = match MemoryImage::new(page_size, offset, data, mmap)? { + + // If this creation fails then we fail creating + // `ModuleMemoryImages` since this memory couldn't be represented. + let image = match MemoryImage::new(engine, page_size, offset, source, data_range)? { Some(image) => image, None => return Ok(None), }; @@ -280,7 +297,6 @@ impl ModuleMemoryImages { /// original mappings, effectively resetting everything back to its initial /// state. Non-linux platforms will replace all memory below `self.accessible` /// with a fresh zero'd mmap, meaning that reuse is effectively not supported. -#[derive(Debug)] pub struct MemoryImageSlot { /// The mmap and offset within it that contains the linear memory for this /// slot. @@ -325,6 +341,18 @@ pub struct MemoryImageSlot { clear_on_drop: bool, } +impl fmt::Debug for MemoryImageSlot { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MemoryImageSlot") + .field("base", &self.base) + .field("static_size", &self.static_size) + .field("accessible", &self.accessible) + .field("dirty", &self.dirty) + .field("clear_on_drop", &self.clear_on_drop) + .finish_non_exhaustive() + } +} + impl MemoryImageSlot { /// Create a new MemoryImageSlot. Assumes that there is an anonymous /// mmap backing in the given range to start. @@ -423,7 +451,12 @@ impl MemoryImageSlot { // Note that this intentionally a "small mmap" which only covers the // extent of the prior initialization image in order to preserve // resident memory that might come before or after the image. - if self.image.as_ref() != maybe_image { + let images_equal = match (self.image.as_ref(), maybe_image) { + (Some(a), Some(b)) if Arc::ptr_eq(a, b) => true, + (None, None) => true, + _ => false, + }; + if !images_equal { self.remove_image()?; } @@ -454,7 +487,7 @@ impl MemoryImageSlot { // skipped if `self.image` matches `maybe_image`. assert!(initial_size_bytes <= self.accessible.byte_count()); assert!(initial_size_bytes_page_aligned <= self.accessible); - if self.image.as_ref() != maybe_image { + if !images_equal { if let Some(image) = maybe_image.as_ref() { assert!( image @@ -500,13 +533,14 @@ impl MemoryImageSlot { #[allow(dead_code, reason = "only used in some cfgs")] pub(crate) fn clear_and_remain_ready( &mut self, + pagemap: Option<&PageMap>, keep_resident: HostAlignedByteCount, decommit: impl FnMut(*mut u8, usize), ) -> Result<()> { assert!(self.dirty); unsafe { - self.reset_all_memory_contents(keep_resident, decommit)?; + self.reset_all_memory_contents(pagemap, keep_resident, decommit)?; } self.dirty = false; @@ -516,6 +550,7 @@ impl MemoryImageSlot { #[allow(dead_code, reason = "only used in some cfgs")] unsafe fn reset_all_memory_contents( &mut self, + pagemap: Option<&PageMap>, keep_resident: HostAlignedByteCount, decommit: impl FnMut(*mut u8, usize), ) -> Result<()> { @@ -531,7 +566,7 @@ impl MemoryImageSlot { } DecommitBehavior::RestoreOriginalMapping => { unsafe { - self.reset_with_original_mapping(keep_resident, decommit); + self.reset_with_original_mapping(pagemap, keep_resident, decommit); } Ok(()) } @@ -541,174 +576,99 @@ impl MemoryImageSlot { #[allow(dead_code, reason = "only used in some cfgs")] unsafe fn reset_with_original_mapping( &mut self, + pagemap: Option<&PageMap>, keep_resident: HostAlignedByteCount, - mut decommit: impl FnMut(*mut u8, usize), + decommit: impl FnMut(*mut u8, usize), ) { - match &self.image { - Some(image) => { - if image.linear_memory_offset < keep_resident { - // If the image starts below the `keep_resident` then - // memory looks something like this: - // - // up to `keep_resident` bytes - // | - // +--------------------------+ remaining_memset - // | | / - // <--------------> <-------> - // - // image_end - // 0 linear_memory_offset | accessible - // | | | | - // +----------------+--------------+---------+--------+ - // | dirty memory | image | dirty memory | - // +----------------+--------------+---------+--------+ - // - // <------+-------> <-----+-----> <---+---> <--+---> - // | | | | - // | | | | - // memset (1) / | madvise (4) - // mmadvise (2) / - // / - // memset (3) - // - // - // In this situation there are two disjoint regions that are - // `memset` manually to zero. Note that `memset (3)` may be - // zero bytes large. Furthermore `madvise (4)` may also be - // zero bytes large. - - let image_end = image - .linear_memory_offset - .checked_add(image.len) - .expect("image is in bounds"); - let mem_after_image = self - .accessible - .checked_sub(image_end) - .expect("image_end falls before self.accessible"); - let excess = keep_resident - .checked_sub(image.linear_memory_offset) - .expect( - "if statement checks that keep_resident > image.linear_memory_offset", - ); - let remaining_memset = excess.min(mem_after_image); - - // This is memset (1) - unsafe { - ptr::write_bytes( - self.base.as_mut_ptr(), - 0u8, - image.linear_memory_offset.byte_count(), - ); - } - - // This is madvise (2) - unsafe { - self.restore_original_mapping( - image.linear_memory_offset, - image.len, - &mut decommit, - ); - } + assert_eq!( + vm::decommit_behavior(), + DecommitBehavior::RestoreOriginalMapping + ); - // This is memset (3) - unsafe { - ptr::write_bytes( - self.base.as_mut_ptr().add(image_end.byte_count()), - 0u8, - remaining_memset.byte_count(), - ); - } + unsafe { + match &self.image { + // If there's a backing image then manually resetting a region + // is a bit trickier than without an image, so delegate to the + // helper function below. + Some(image) => { + reset_with_pagemap( + pagemap, + self.base.as_mut_ptr(), + self.accessible, + keep_resident, + |region| { + manually_reset_region(self.base.as_mut_ptr().addr(), image, region) + }, + decommit, + ); + } - // This is madvise (4) - unsafe { - self.restore_original_mapping( - image_end - .checked_add(remaining_memset) - .expect("image_end + remaining_memset is in bounds"), - mem_after_image - .checked_sub(remaining_memset) - .expect("remaining_memset defined to be <= mem_after_image"), - &mut decommit, - ); - } - } else { - // If the image starts after the `keep_resident` threshold - // then we memset the start of linear memory and then use - // madvise below for the rest of it, including the image. - // - // 0 keep_resident accessible - // | | | - // +----------------+---+----------+------------------+ - // | dirty memory | image | dirty memory | - // +----------------+---+----------+------------------+ - // - // <------+-------> <-------------+-----------------> - // | | - // | | - // memset (1) madvise (2) - // - // Here only a single memset is necessary since the image - // started after the threshold which we're keeping resident. - // Note that the memset may be zero bytes here. - - // This is memset (1) - unsafe { - ptr::write_bytes(self.base.as_mut_ptr(), 0u8, keep_resident.byte_count()); - } + // If there's no memory image for this slot then pages are always + // manually reset back to zero or given to `decommit`. + None => reset_with_pagemap( + pagemap, + self.base.as_mut_ptr(), + self.accessible, + keep_resident, + |region| region.fill(0), + decommit, + ), + } + } - // This is madvise (2) - unsafe { - self.restore_original_mapping( - keep_resident, - self.accessible - .checked_sub(keep_resident) - .expect("keep_resident is a subset of accessible memory"), - decommit, - ); - } - }; + /// Manually resets `region` back to its original contents as specified + /// in `image`. + /// + /// This assumes that the original mmap starts at `base_addr` and + /// `region` is a subslice within the original mmap. + /// + /// # Panics + /// + /// Panics if `base_addr` is not the right index due to the various + /// indexing calculations below. + fn manually_reset_region(base_addr: usize, image: &MemoryImage, mut region: &mut [u8]) { + let image_start = image.linear_memory_offset.byte_count(); + let image_end = image_start + image.len.byte_count(); + let mut region_start = region.as_ptr().addr() - base_addr; + let region_end = region_start + region.len(); + let image_bytes = image.module_source.wasm_data(); + let image_bytes = &image_bytes[image.module_source_offset..][..image.len.byte_count()]; + + // 1. Zero out the part before the image (if any). + if let Some(len_before_image) = image_start.checked_sub(region_start) { + let len = len_before_image.min(region.len()); + let (a, b) = region.split_at_mut(len); + a.fill(0); + region = b; + region_start += len; + + if region.is_empty() { + return; + } } - // If there's no memory image for this slot then memset the first - // bytes in the memory back to zero while using `madvise` to purge - // the rest. - None => { - let size_to_memset = keep_resident.min(self.accessible); - unsafe { - ptr::write_bytes(self.base.as_mut_ptr(), 0u8, size_to_memset.byte_count()); - self.restore_original_mapping( - size_to_memset, - self.accessible - .checked_sub(size_to_memset) - .expect("size_to_memset is defined to be <= self.accessible"), - decommit, - ); + debug_assert_eq!(region_end - region_start, region.len()); + debug_assert!(region_start >= image_start); + + // 2. Copy the original bytes from the image for the part that + // overlaps with the image. + if let Some(len_in_image) = image_end.checked_sub(region_start) { + let len = len_in_image.min(region.len()); + let (a, b) = region.split_at_mut(len); + a.copy_from_slice(&image_bytes[region_start - image_start..][..len]); + region = b; + region_start += len; + + if region.is_empty() { + return; } } - } - } - #[allow(dead_code, reason = "only used in some cfgs")] - unsafe fn restore_original_mapping( - &self, - base: HostAlignedByteCount, - len: HostAlignedByteCount, - mut decommit: impl FnMut(*mut u8, usize), - ) { - assert!(base.checked_add(len).unwrap() <= self.accessible); - if len == 0 { - return; - } + debug_assert_eq!(region_end - region_start, region.len()); + debug_assert!(region_start >= image_end); - assert_eq!( - vm::decommit_behavior(), - DecommitBehavior::RestoreOriginalMapping - ); - unsafe { - decommit( - self.base.as_mut_ptr().add(base.byte_count()), - len.byte_count(), - ); + // 3. Zero out the part after the image. + region.fill(0); } } @@ -809,7 +769,7 @@ mod test { use super::*; use crate::runtime::vm::mmap::{AlignedLength, Mmap}; use crate::runtime::vm::sys::vm::decommit_pages; - use crate::runtime::vm::{HostAlignedByteCount, host_page_size}; + use crate::runtime::vm::{HostAlignedByteCount, MmapVec, host_page_size}; use std::sync::Arc; use wasmtime_environ::{IndexType, Limits, Memory}; @@ -820,12 +780,32 @@ mod test { // The image length is rounded up to the nearest page size let image_len = HostAlignedByteCount::new_rounded_up(data.len()).unwrap(); - Ok(MemoryImage { + let mut source = TestDataSource { + data: vec![0; image_len.byte_count()], + }; + source.data[..data.len()].copy_from_slice(data); + + return Ok(MemoryImage { source: MemoryImageSource::from_data(data)?.unwrap(), len: image_len, source_offset: 0, linear_memory_offset, - }) + module_source: Arc::new(source), + module_source_offset: 0, + }); + + struct TestDataSource { + data: Vec, + } + + impl ModuleMemoryImageSource for TestDataSource { + fn wasm_data(&self) -> &[u8] { + &self.data + } + fn mmap(&self) -> Option<&MmapVec> { + None + } + } } fn dummy_memory() -> Memory { @@ -903,7 +883,7 @@ mod test { // instantiate again; we should see zeroes, even as the // reuse-anon-mmap-opt kicks in memfd - .clear_and_remain_ready(HostAlignedByteCount::ZERO, |ptr, len| unsafe { + .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -944,7 +924,7 @@ mod test { // Clear and re-instantiate same image memfd - .clear_and_remain_ready(HostAlignedByteCount::ZERO, |ptr, len| unsafe { + .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -956,7 +936,7 @@ mod test { // Clear and re-instantiate no image memfd - .clear_and_remain_ready(HostAlignedByteCount::ZERO, |ptr, len| unsafe { + .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -967,7 +947,7 @@ mod test { // Clear and re-instantiate image again memfd - .clear_and_remain_ready(HostAlignedByteCount::ZERO, |ptr, len| unsafe { + .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -980,7 +960,7 @@ mod test { // Create another image with different data. let image2 = Arc::new(create_memfd_with_data(page_size, &[10, 11, 12, 13]).unwrap()); memfd - .clear_and_remain_ready(HostAlignedByteCount::ZERO, |ptr, len| unsafe { + .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -993,7 +973,7 @@ mod test { // Instantiate the original image again; we should notice it's // a different image and not reuse the mappings. memfd - .clear_and_remain_ready(HostAlignedByteCount::ZERO, |ptr, len| unsafe { + .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -1041,7 +1021,7 @@ mod test { }; memfd - .clear_and_remain_ready(amt_to_memset, |ptr, len| unsafe { + .clear_and_remain_ready(None, amt_to_memset, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -1062,7 +1042,7 @@ mod test { }); } memfd - .clear_and_remain_ready(amt_to_memset, |ptr, len| unsafe { + .clear_and_remain_ready(None, amt_to_memset, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -1103,7 +1083,7 @@ mod test { } memfd - .clear_and_remain_ready(HostAlignedByteCount::ZERO, |ptr, len| unsafe { + .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -1128,7 +1108,7 @@ mod test { } memfd - .clear_and_remain_ready(HostAlignedByteCount::ZERO, |ptr, len| unsafe { + .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -1153,7 +1133,7 @@ mod test { } memfd - .clear_and_remain_ready(HostAlignedByteCount::ZERO, |ptr, len| unsafe { + .clear_and_remain_ready(None, HostAlignedByteCount::ZERO, |ptr, len| unsafe { decommit_pages(ptr, len).unwrap() }) .unwrap(); @@ -1164,4 +1144,130 @@ mod test { assert_eq!(&[0, 0, 0, 0], &slice[page_size..][..4]); assert_eq!(&[0, 0], &slice[initial..initial + 2]); } + + #[test] + fn reset_with_pagemap() { + let page_size = host_page_size(); + let ty = dummy_memory(); + let tunables = Tunables { + memory_reservation: 100 << 16, + ..Tunables::default_miri() + }; + let mmap = mmap_4mib_inaccessible(); + let mmap_len = page_size * 9; + let mut memfd = + MemoryImageSlot::create(mmap.zero_offset(), HostAlignedByteCount::ZERO, mmap_len); + memfd.no_clear_on_drop(); + let pagemap = PageMap::new(); + let pagemap = pagemap.as_ref(); + + let mut data = vec![0; 3 * page_size]; + for (i, chunk) in data.chunks_mut(page_size).enumerate() { + for slot in chunk { + *slot = u8::try_from(i + 1).unwrap(); + } + } + let image = Arc::new(create_memfd_with_data(3 * page_size, &data).unwrap()); + + memfd + .instantiate(mmap_len, Some(&image), &ty, &tunables) + .unwrap(); + + let keep_resident = HostAlignedByteCount::new(mmap_len).unwrap(); + let assert_pristine_after_reset = |memfd: &mut MemoryImageSlot| unsafe { + // Wipe the image, keeping some bytes resident. + memfd + .clear_and_remain_ready(pagemap, keep_resident, |ptr, len| { + decommit_pages(ptr, len).unwrap() + }) + .unwrap(); + + // Double check that the contents of memory are as expected after + // reset. + with_slice_mut(&mmap, 0..mmap_len, move |slice| { + for (i, chunk) in slice.chunks(page_size).enumerate() { + let expected = match i { + 0..3 => 0, + 3..6 => u8::try_from(i).unwrap() - 2, + 6..9 => 0, + _ => unreachable!(), + }; + for slot in chunk { + assert_eq!(*slot, expected); + } + } + }); + + // Re-instantiate, but then wipe the image entirely by keeping + // nothing resident. + memfd + .instantiate(mmap_len, Some(&image), &ty, &tunables) + .unwrap(); + memfd + .clear_and_remain_ready(pagemap, HostAlignedByteCount::ZERO, |ptr, len| { + decommit_pages(ptr, len).unwrap() + }) + .unwrap(); + + // Next re-instantiate a final time to get used for the next test. + memfd + .instantiate(mmap_len, Some(&image), &ty, &tunables) + .unwrap(); + }; + + let write_page = |_memfd: &mut MemoryImageSlot, page: usize| unsafe { + with_slice_mut( + &mmap, + page * page_size..(page + 1) * page_size, + move |slice| slice.fill(0xff), + ); + }; + + // Test various combinations of dirty pages and regions. For example + // test a dirty region of memory entirely in the zero-initialized zone + // before/after the image and also test when the dirty region straddles + // just the start of the image, just the end of the image, both ends, + // and is entirely contained in just the image. + assert_pristine_after_reset(&mut memfd); + + for i in 0..9 { + write_page(&mut memfd, i); + assert_pristine_after_reset(&mut memfd); + } + write_page(&mut memfd, 0); + write_page(&mut memfd, 1); + assert_pristine_after_reset(&mut memfd); + write_page(&mut memfd, 1); + assert_pristine_after_reset(&mut memfd); + write_page(&mut memfd, 2); + write_page(&mut memfd, 3); + assert_pristine_after_reset(&mut memfd); + write_page(&mut memfd, 3); + write_page(&mut memfd, 4); + write_page(&mut memfd, 5); + assert_pristine_after_reset(&mut memfd); + write_page(&mut memfd, 0); + write_page(&mut memfd, 1); + write_page(&mut memfd, 2); + assert_pristine_after_reset(&mut memfd); + write_page(&mut memfd, 0); + write_page(&mut memfd, 3); + write_page(&mut memfd, 6); + assert_pristine_after_reset(&mut memfd); + write_page(&mut memfd, 2); + write_page(&mut memfd, 3); + write_page(&mut memfd, 4); + write_page(&mut memfd, 5); + write_page(&mut memfd, 6); + assert_pristine_after_reset(&mut memfd); + write_page(&mut memfd, 4); + write_page(&mut memfd, 5); + write_page(&mut memfd, 6); + write_page(&mut memfd, 7); + assert_pristine_after_reset(&mut memfd); + write_page(&mut memfd, 4); + write_page(&mut memfd, 5); + write_page(&mut memfd, 8); + assert_pristine_after_reset(&mut memfd); + } } diff --git a/crates/wasmtime/src/runtime/vm/cow_disabled.rs b/crates/wasmtime/src/runtime/vm/cow_disabled.rs index 8789380deb..90a92e63f8 100644 --- a/crates/wasmtime/src/runtime/vm/cow_disabled.rs +++ b/crates/wasmtime/src/runtime/vm/cow_disabled.rs @@ -3,8 +3,9 @@ #![warn(dead_code, unused_imports)] +use crate::Engine; use crate::prelude::*; -use crate::runtime::vm::MmapVec; +use crate::vm::ModuleMemoryImageSource; use alloc::sync::Arc; use wasmtime_environ::{DefinedMemoryIndex, Module}; @@ -24,9 +25,9 @@ pub enum MemoryImage {} impl ModuleMemoryImages { pub fn new( + _engine: &Engine, _module: &Module, - _wasm_data: &[u8], - _mmap: Option<&MmapVec>, + _source: &Arc, ) -> Result> { Ok(None) } diff --git a/crates/wasmtime/src/runtime/vm/gc.rs b/crates/wasmtime/src/runtime/vm/gc.rs index 1f9e7349ba..9eb361b74d 100644 --- a/crates/wasmtime/src/runtime/vm/gc.rs +++ b/crates/wasmtime/src/runtime/vm/gc.rs @@ -97,7 +97,7 @@ impl GcStore { /// Clone a GC reference, calling GC write barriers as necessary. pub fn clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef { if gc_ref.is_i31() { - gc_ref.unchecked_copy() + gc_ref.copy_i31() } else { self.gc_heap.clone_gc_ref(gc_ref) } @@ -116,21 +116,55 @@ impl GcStore { self.write_gc_ref(destination, source); } + /// Dynamically tests whether a `init_gc_ref` is needed to write `gc_ref` + /// into an uninitialized destination. + pub(crate) fn needs_init_barrier(gc_ref: Option<&VMGcRef>) -> bool { + assert!(cfg!(feature = "gc") || gc_ref.is_none()); + gc_ref.is_some_and(|r| !r.is_i31()) + } + + /// Dynamically tests whether a `write_gc_ref` is needed to write `gc_ref` + /// into `dest`. + pub(crate) fn needs_write_barrier( + dest: &mut Option, + gc_ref: Option<&VMGcRef>, + ) -> bool { + assert!(cfg!(feature = "gc") || gc_ref.is_none()); + assert!(cfg!(feature = "gc") || dest.is_none()); + dest.as_ref().is_some_and(|r| !r.is_i31()) || gc_ref.is_some_and(|r| !r.is_i31()) + } + + /// Same as [`Self::write_gc_ref`] but doesn't require a `store` when + /// possible. + /// + /// # Panics + /// + /// Panics if `store` is `None` and one of `dest` or `gc_ref` requires a + /// write barrier. + pub(crate) fn write_gc_ref_optional_store( + store: Option<&mut Self>, + dest: &mut Option, + gc_ref: Option<&VMGcRef>, + ) { + if Self::needs_write_barrier(dest, gc_ref) { + store.unwrap().write_gc_ref(dest, gc_ref) + } else { + *dest = gc_ref.map(|r| r.copy_i31()); + } + } + /// Write the `source` GC reference into the `destination` slot, performing /// write barriers as necessary. pub fn write_gc_ref(&mut self, destination: &mut Option, source: Option<&VMGcRef>) { // If neither the source nor destination actually point to a GC object // (that is, they are both either null or `i31ref`s) then we can skip // the GC barrier. - if destination.as_ref().map_or(true, |d| d.is_i31()) - && source.as_ref().map_or(true, |s| s.is_i31()) - { - *destination = source.map(|s| s.unchecked_copy()); - return; + if Self::needs_write_barrier(destination, source) { + self.gc_heap + .write_gc_ref(&mut self.host_data_table, destination, source); + } else { + *destination = source.map(|s| s.copy_i31()); } - - self.gc_heap - .write_gc_ref(&mut self.host_data_table, destination, source); } /// Drop the given GC reference, performing drop barriers as necessary. diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/arrayref.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/arrayref.rs index ca29e62436..04982d3c49 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/arrayref.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/arrayref.rs @@ -235,8 +235,9 @@ impl VMArrayRef { Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()), None => None, }; - store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref()); - let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); + let store = store.require_gc_store_mut()?; + store.write_gc_ref(&mut gc_ref, e.as_ref()); + let data = store.gc_object_data(self.as_gc_ref()); data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); } Val::AnyRef(a) => { @@ -246,8 +247,9 @@ impl VMArrayRef { Some(a) => Some(a.try_gc_ref(store)?.unchecked_copy()), None => None, }; - store.gc_store_mut()?.write_gc_ref(&mut gc_ref, a.as_ref()); - let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); + let store = store.require_gc_store_mut()?; + store.write_gc_ref(&mut gc_ref, a.as_ref()); + let data = store.gc_object_data(self.as_gc_ref()); data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); } Val::ExnRef(e) => { @@ -257,8 +259,9 @@ impl VMArrayRef { Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()), None => None, }; - store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref()); - let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); + let store = store.require_gc_store_mut()?; + store.write_gc_ref(&mut gc_ref, e.as_ref()); + let data = store.gc_object_data(self.as_gc_ref()); data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); } @@ -267,9 +270,9 @@ impl VMArrayRef { Some(f) => Some(SendSyncPtr::new(f.vm_func_ref(store))), None => None, }; - let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(func_ref) }; + let store = store.require_gc_store_mut()?; + let id = unsafe { store.func_ref_table.intern(func_ref) }; store - .gc_store_mut()? .gc_object_data(self.as_gc_ref()) .write_u32(offset, id.into_raw()); } @@ -311,33 +314,27 @@ impl VMArrayRef { ) -> Result<()> { debug_assert!(val._matches_ty(&store, &ty.unpack())?); let offset = layout.elem_offset(index); + let gcstore = store.require_gc_store_mut()?; match val { - Val::I32(i) if ty.is_i8() => store - .gc_store_mut()? + Val::I32(i) if ty.is_i8() => gcstore .gc_object_data(self.as_gc_ref()) .write_i8(offset, truncate_i32_to_i8(i)), - Val::I32(i) if ty.is_i16() => store - .gc_store_mut()? + Val::I32(i) if ty.is_i16() => gcstore .gc_object_data(self.as_gc_ref()) .write_i16(offset, truncate_i32_to_i16(i)), - Val::I32(i) => store - .gc_store_mut()? + Val::I32(i) => gcstore .gc_object_data(self.as_gc_ref()) .write_i32(offset, i), - Val::I64(i) => store - .gc_store_mut()? + Val::I64(i) => gcstore .gc_object_data(self.as_gc_ref()) .write_i64(offset, i), - Val::F32(f) => store - .gc_store_mut()? + Val::F32(f) => gcstore .gc_object_data(self.as_gc_ref()) .write_u32(offset, f), - Val::F64(f) => store - .gc_store_mut()? + Val::F64(f) => gcstore .gc_object_data(self.as_gc_ref()) .write_u64(offset, f), - Val::V128(v) => store - .gc_store_mut()? + Val::V128(v) => gcstore .gc_object_data(self.as_gc_ref()) .write_v128(offset, v), @@ -350,7 +347,7 @@ impl VMArrayRef { Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), }; store - .gc_store_mut()? + .require_gc_store_mut()? .gc_object_data(self.as_gc_ref()) .write_u32(offset, x); } @@ -360,7 +357,7 @@ impl VMArrayRef { Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), }; store - .gc_store_mut()? + .require_gc_store_mut()? .gc_object_data(self.as_gc_ref()) .write_u32(offset, x); } @@ -370,7 +367,7 @@ impl VMArrayRef { Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), }; store - .gc_store_mut()? + .require_gc_store_mut()? .gc_object_data(self.as_gc_ref()) .write_u32(offset, x); } @@ -380,9 +377,9 @@ impl VMArrayRef { Some(f) => Some(SendSyncPtr::new(f.vm_func_ref(store))), None => None, }; - let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(func_ref) }; - store - .gc_store_mut()? + let gcstore = store.require_gc_store_mut()?; + let id = unsafe { gcstore.func_ref_table.intern(func_ref) }; + gcstore .gc_object_data(self.as_gc_ref()) .write_u32(offset, id.into_raw()); } diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs index 0da49ef9fb..bf53d04d96 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs @@ -935,7 +935,7 @@ unsafe impl GcHeap for DrcHeap { ptr.cast() } - unsafe fn take_memory(&mut self) -> crate::vm::Memory { + fn take_memory(&mut self) -> crate::vm::Memory { debug_assert!(self.is_attached()); self.vmmemory.take(); self.memory.take().unwrap() diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/exnref.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/exnref.rs index 418cbf3621..46f6195a45 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/exnref.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/exnref.rs @@ -179,12 +179,11 @@ impl VMExnRef { instance: InstanceId, tag: DefinedTagIndex, ) -> Result<()> { + let store = store.require_gc_store_mut()?; store - .gc_store_mut()? .gc_object_data(&self.0) .write_u32(layout.tag_offset, instance.as_u32()); store - .gc_store_mut()? .gc_object_data(&self.0) .write_u32(layout.tag_offset + 4, tag.as_u32()); Ok(()) @@ -196,13 +195,10 @@ impl VMExnRef { store: &mut AutoAssertNoGc, layout: &GcExceptionLayout, ) -> Result<(InstanceId, DefinedTagIndex)> { - let instance = store - .gc_store_mut()? - .gc_object_data(&self.0) - .read_u32(layout.tag_offset); + let store = store.require_gc_store_mut()?; + let instance = store.gc_object_data(&self.0).read_u32(layout.tag_offset); let instance = InstanceId::from_u32(instance); let tag = store - .gc_store_mut()? .gc_object_data(&self.0) .read_u32(layout.tag_offset + 4); let tag = DefinedTagIndex::from_u32(tag); diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/null.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/null.rs index 00c6bf0467..0adc79567c 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/null.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/null.rs @@ -219,7 +219,7 @@ unsafe impl GcHeap for NullHeap { self.no_gc_count -= 1; } - unsafe fn take_memory(&mut self) -> crate::vm::Memory { + fn take_memory(&mut self) -> crate::vm::Memory { debug_assert!(self.is_attached()); self.memory.take().unwrap() } diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/structref.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/structref.rs index a2980c4e8b..364faf1c01 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/structref.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/structref.rs @@ -165,7 +165,8 @@ impl VMStructRef { debug_assert!(val._matches_ty(&store, &ty.unpack())?); let offset = layout.fields[field].offset; - let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); + let gcstore = store.require_gc_store_mut()?; + let data = gcstore.gc_object_data(self.as_gc_ref()); match val { Val::I32(i) if ty.is_i8() => data.write_i8(offset, truncate_i32_to_i8(i)), Val::I32(i) if ty.is_i16() => data.write_i16(offset, truncate_i32_to_i16(i)), @@ -194,8 +195,9 @@ impl VMStructRef { Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()), None => None, }; - store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref()); - let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); + let store = store.require_gc_store_mut()?; + store.write_gc_ref(&mut gc_ref, e.as_ref()); + let data = store.gc_object_data(self.as_gc_ref()); data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); } Val::AnyRef(a) => { @@ -205,8 +207,9 @@ impl VMStructRef { Some(a) => Some(a.try_gc_ref(store)?.unchecked_copy()), None => None, }; - store.gc_store_mut()?.write_gc_ref(&mut gc_ref, a.as_ref()); - let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); + let store = store.require_gc_store_mut()?; + store.write_gc_ref(&mut gc_ref, a.as_ref()); + let data = store.gc_object_data(self.as_gc_ref()); data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); } Val::ExnRef(e) => { @@ -216,16 +219,17 @@ impl VMStructRef { Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()), None => None, }; - store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref()); - let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); + let store = store.require_gc_store_mut()?; + store.write_gc_ref(&mut gc_ref, e.as_ref()); + let data = store.gc_object_data(self.as_gc_ref()); data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); } Val::FuncRef(f) => { let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store))); - let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(f) }; - store - .gc_store_mut()? + let gcstore = store.require_gc_store_mut()?; + let id = unsafe { gcstore.func_ref_table.intern(f) }; + gcstore .gc_object_data(self.as_gc_ref()) .write_u32(offset, id.into_raw()); } @@ -326,35 +330,19 @@ pub(crate) fn initialize_field_impl( offset: u32, val: Val, ) -> Result<()> { + let gcstore = store.require_gc_store_mut()?; match val { - Val::I32(i) if ty.is_i8() => store - .gc_store_mut()? + Val::I32(i) if ty.is_i8() => gcstore .gc_object_data(gc_ref) .write_i8(offset, truncate_i32_to_i8(i)), - Val::I32(i) if ty.is_i16() => store - .gc_store_mut()? + Val::I32(i) if ty.is_i16() => gcstore .gc_object_data(gc_ref) .write_i16(offset, truncate_i32_to_i16(i)), - Val::I32(i) => store - .gc_store_mut()? - .gc_object_data(gc_ref) - .write_i32(offset, i), - Val::I64(i) => store - .gc_store_mut()? - .gc_object_data(gc_ref) - .write_i64(offset, i), - Val::F32(f) => store - .gc_store_mut()? - .gc_object_data(gc_ref) - .write_u32(offset, f), - Val::F64(f) => store - .gc_store_mut()? - .gc_object_data(gc_ref) - .write_u64(offset, f), - Val::V128(v) => store - .gc_store_mut()? - .gc_object_data(gc_ref) - .write_v128(offset, v), + Val::I32(i) => gcstore.gc_object_data(gc_ref).write_i32(offset, i), + Val::I64(i) => gcstore.gc_object_data(gc_ref).write_i64(offset, i), + Val::F32(f) => gcstore.gc_object_data(gc_ref).write_u32(offset, f), + Val::F64(f) => gcstore.gc_object_data(gc_ref).write_u64(offset, f), + Val::V128(v) => gcstore.gc_object_data(gc_ref).write_v128(offset, v), // NB: We don't need to do a write barrier when initializing a // field, because there is nothing being overwritten. Therefore, we @@ -365,7 +353,7 @@ pub(crate) fn initialize_field_impl( Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), }; store - .gc_store_mut()? + .require_gc_store_mut()? .gc_object_data(gc_ref) .write_u32(offset, x); } @@ -375,7 +363,7 @@ pub(crate) fn initialize_field_impl( Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), }; store - .gc_store_mut()? + .require_gc_store_mut()? .gc_object_data(gc_ref) .write_u32(offset, x); } @@ -385,16 +373,16 @@ pub(crate) fn initialize_field_impl( Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), }; store - .gc_store_mut()? + .require_gc_store_mut()? .gc_object_data(gc_ref) .write_u32(offset, x); } Val::FuncRef(f) => { let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store))); - let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(f) }; - store - .gc_store_mut()? + let gcstore = store.require_gc_store_mut()?; + let id = unsafe { gcstore.func_ref_table.intern(f) }; + gcstore .gc_object_data(gc_ref) .write_u32(offset, id.into_raw()); } diff --git a/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs b/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs index 84b9d97926..2cde325945 100644 --- a/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs +++ b/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs @@ -423,10 +423,11 @@ pub unsafe trait GcHeap: 'static + Send + Sync { /// Take the underlying memory storage out of this GC heap. /// - /// # Safety + /// # Panics /// - /// You may not use this GC heap again until after you replace the memory. - unsafe fn take_memory(&mut self) -> crate::vm::Memory; + /// If this GC heap is used while the memory is taken then a panic will + /// occur. This will also panic if the memory is already taken. + fn take_memory(&mut self) -> crate::vm::Memory; /// Replace this GC heap's underlying memory storage. /// diff --git a/crates/wasmtime/src/runtime/vm/gc/i31.rs b/crates/wasmtime/src/runtime/vm/gc/i31.rs index 98e3e3c078..34c1601bf8 100644 --- a/crates/wasmtime/src/runtime/vm/gc/i31.rs +++ b/crates/wasmtime/src/runtime/vm/gc/i31.rs @@ -2,7 +2,6 @@ use super::VMGcRef; use core::fmt; -use wasmtime_environ::Unsigned; /// A 31-bit integer for use with `i31ref`. #[derive(Clone, Copy, PartialEq, Eq, Hash)] @@ -70,7 +69,7 @@ impl I31 { /// the wrapped value does. #[inline] pub fn wrapping_i32(value: i32) -> Self { - Self::wrapping_u32(value.unsigned()) + Self::wrapping_u32(value.cast_unsigned()) } /// Get this `I31`'s value as an unsigned integer. diff --git a/crates/wasmtime/src/runtime/vm/instance.rs b/crates/wasmtime/src/runtime/vm/instance.rs index 98199ee2ce..cb0e54e52b 100644 --- a/crates/wasmtime/src/runtime/vm/instance.rs +++ b/crates/wasmtime/src/runtime/vm/instance.rs @@ -581,10 +581,7 @@ impl Instance { } if self.env_module().needs_gc_heap { - self.as_mut().set_gc_heap(Some(store.gc_store().expect( - "if we need a GC heap, then `Instance::new_raw` should have already \ - allocated it for us", - ))); + self.as_mut().set_gc_heap(Some(store.unwrap_gc_store())); } else { self.as_mut().set_gc_heap(None); } diff --git a/crates/wasmtime/src/runtime/vm/instance/allocator.rs b/crates/wasmtime/src/runtime/vm/instance/allocator.rs index 2ad773b3ad..e8d92502d1 100644 --- a/crates/wasmtime/src/runtime/vm/instance/allocator.rs +++ b/crates/wasmtime/src/runtime/vm/instance/allocator.rs @@ -612,7 +612,6 @@ fn initialize_tables( let idx = module.table_index(table); match module.tables[idx].ref_type.heap_type.top() { WasmHeapTopType::Extern => { - store.gc_store_mut()?; let (gc_store, instance) = store.optional_gc_store_and_instance_mut(context.instance); let gc_store = gc_store.unwrap(); @@ -624,7 +623,6 @@ fn initialize_tables( } WasmHeapTopType::Any => { - store.gc_store_mut()?; let (gc_store, instance) = store.optional_gc_store_and_instance_mut(context.instance); let gc_store = gc_store.unwrap(); @@ -636,7 +634,6 @@ fn initialize_tables( } WasmHeapTopType::Exn => { - store.gc_store_mut()?; let (gc_store, instance) = store.optional_gc_store_and_instance_mut(context.instance); let gc_store = gc_store.unwrap(); diff --git a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs index 2e592d9ab4..2127492891 100644 --- a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs +++ b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs @@ -52,6 +52,7 @@ use crate::runtime::vm::{ CompiledModuleId, Memory, Table, instance::Instance, mpk::{self, ProtectionKey, ProtectionMask}, + sys::vm::PageMap, }; use std::borrow::Cow; use std::fmt::Display; @@ -303,6 +304,8 @@ pub struct PoolingInstanceAllocator { #[cfg(feature = "async")] stacks: StackPool, + + pagemap: Option, } impl Drop for PoolingInstanceAllocator { @@ -350,6 +353,7 @@ impl PoolingInstanceAllocator { gc_heaps: GcHeapPool::new(config)?, #[cfg(feature = "async")] stacks: StackPool::new(config)?, + pagemap: PageMap::new(), }) } @@ -645,14 +649,18 @@ unsafe impl InstanceAllocatorImpl for PoolingInstanceAllocator { let mut image = memory.unwrap_static_image(); let mut queue = DecommitQueue::default(); image - .clear_and_remain_ready(self.memories.keep_resident, |ptr, len| { - // SAFETY: the memory in `image` won't be used until this - // decommit queue is flushed, and by definition the memory is - // not in use when calling this function. - unsafe { - queue.push_raw(ptr, len); - } - }) + .clear_and_remain_ready( + self.pagemap.as_ref(), + self.memories.keep_resident, + |ptr, len| { + // SAFETY: the memory in `image` won't be used until this + // decommit queue is flushed, and by definition the memory is + // not in use when calling this function. + unsafe { + queue.push_raw(ptr, len); + } + }, + ) .expect("failed to reset memory image"); // SAFETY: this image is not in use and its memory regions were enqueued @@ -685,10 +693,14 @@ unsafe impl InstanceAllocatorImpl for PoolingInstanceAllocator { // the understanding that the memory won't get used until the whole // queue is flushed. unsafe { - self.tables - .reset_table_pages_to_zero(allocation_index, &mut table, |ptr, len| { + self.tables.reset_table_pages_to_zero( + self.pagemap.as_ref(), + allocation_index, + &mut table, + |ptr, len| { queue.push_raw(ptr, len); - }); + }, + ); } // SAFETY: the table has had all its memory regions enqueued above. diff --git a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling/table_pool.rs b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling/table_pool.rs index 13e2629c6e..4b30d27001 100644 --- a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling/table_pool.rs +++ b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling/table_pool.rs @@ -2,7 +2,7 @@ use super::{ TableAllocationIndex, index_allocator::{SimpleIndexAllocator, SlotId}, }; -use crate::runtime::vm::sys::vm::commit_pages; +use crate::runtime::vm::sys::vm::{PageMap, commit_pages, reset_with_pagemap}; use crate::runtime::vm::{ InstanceAllocationRequest, Mmap, PoolingInstanceAllocatorConfig, SendSyncPtr, Table, mmap::AlignedLength, @@ -198,9 +198,10 @@ impl TablePool { /// table pool once it is zeroed and decommitted. pub unsafe fn reset_table_pages_to_zero( &self, + pagemap: Option<&PageMap>, allocation_index: TableAllocationIndex, table: &mut Table, - mut decommit: impl FnMut(*mut u8, usize), + decommit: impl FnMut(*mut u8, usize), ) { assert!(table.is_static()); let base = self.get(allocation_index); @@ -208,26 +209,18 @@ impl TablePool { let table_byte_size_page_aligned = HostAlignedByteCount::new_rounded_up(table_byte_size) .expect("table entry size doesn't overflow"); - // `memset` the first `keep_resident` bytes. - let size_to_memset = table_byte_size_page_aligned.min(self.keep_resident); - - // SAFETY: the contract of this function requires that the table is not - // actively in use so it's safe to pave over its allocation with zero - // bytes. + // SAFETY: The `base` pointer is valid for `size` bytes and is safe to + // mutate here given the contract of our own function. unsafe { - std::ptr::write_bytes(base, 0, size_to_memset.byte_count()); + reset_with_pagemap( + pagemap, + base, + table_byte_size_page_aligned, + self.keep_resident, + |slice| slice.fill(0), + decommit, + ) } - - // And decommit the rest of it. - decommit( - // SAFETY: `size_to_memset` is less than the size of the allocation, - // so it's safe to use the `add` intrinsic. - unsafe { base.add(size_to_memset.byte_count()) }, - table_byte_size_page_aligned - .checked_sub(size_to_memset) - .expect("size_to_memset <= size") - .byte_count(), - ); } } diff --git a/crates/wasmtime/src/runtime/vm/libcalls.rs b/crates/wasmtime/src/runtime/vm/libcalls.rs index 5eb70acdc6..16dd012c04 100644 --- a/crates/wasmtime/src/runtime/vm/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/libcalls.rs @@ -533,7 +533,14 @@ unsafe fn grow_gc_heap( _instance: Pin<&mut Instance>, bytes_needed: u64, ) -> Result<()> { - let orig_len = u64::try_from(store.gc_store()?.gc_heap.vmmemory().current_length()).unwrap(); + let orig_len = u64::try_from( + store + .require_gc_store()? + .gc_heap + .vmmemory() + .current_length(), + ) + .unwrap(); unsafe { store @@ -544,7 +551,14 @@ unsafe fn grow_gc_heap( // JIT code relies on the memory having grown by `bytes_needed` bytes if // this libcall returns successfully, so trap if we didn't grow that much. - let new_len = u64::try_from(store.gc_store()?.gc_heap.vmmemory().current_length()).unwrap(); + let new_len = u64::try_from( + store + .require_gc_store()? + .gc_heap + .vmmemory() + .current_length(), + ) + .unwrap(); if orig_len .checked_add(bytes_needed) .is_none_or(|expected_len| new_len < expected_len) @@ -626,7 +640,12 @@ unsafe fn intern_func_ref_for_gc_heap( let func_ref = func_ref.cast::(); let func_ref = NonNull::new(func_ref).map(SendSyncPtr::new); - let func_ref_id = unsafe { store.gc_store_mut()?.func_ref_table.intern(func_ref) }; + let func_ref_id = unsafe { + store + .require_gc_store_mut()? + .func_ref_table + .intern(func_ref) + }; Ok(func_ref_id.into_raw()) } diff --git a/crates/wasmtime/src/runtime/vm/pagemap_disabled.rs b/crates/wasmtime/src/runtime/vm/pagemap_disabled.rs new file mode 100644 index 0000000000..32f24ec08f --- /dev/null +++ b/crates/wasmtime/src/runtime/vm/pagemap_disabled.rs @@ -0,0 +1,46 @@ +use crate::runtime::vm::HostAlignedByteCount; +use core::slice; + +#[derive(Debug)] +pub enum PageMap {} + +impl PageMap { + #[allow(dead_code, reason = "not used on linux64")] + pub fn new() -> Option { + None + } +} + +/// Resets `ptr` for `len` bytes. +/// +/// # Safety +/// +/// Requires that `ptr` is valid to read and write for `len` bytes. +pub unsafe fn reset_with_pagemap( + _pagemap: Option<&PageMap>, + mut ptr: *mut u8, + mut len: HostAlignedByteCount, + mut keep_resident: HostAlignedByteCount, + mut reset_manually: impl FnMut(&mut [u8]), + mut decommit: impl FnMut(*mut u8, usize), +) { + keep_resident = keep_resident.min(len); + + // `memset` the first `keep_resident` bytes. + // + // SAFETY: it's a contract of this function that `ptr` is valid to write for + // `len` bytes, and `keep_resident` is less than `len` here. + unsafe { + reset_manually(slice::from_raw_parts_mut(ptr, keep_resident.byte_count())); + } + + // SAFETY: It's a contract of this function that the parameters are valid to + // always produce an in-bounds pointer. + unsafe { + ptr = ptr.add(keep_resident.byte_count()); + } + len = len.checked_sub(keep_resident).unwrap(); + + // decommit the rest of it. + decommit(ptr, len.byte_count()) +} diff --git a/crates/wasmtime/src/runtime/vm/sys/custom/vm.rs b/crates/wasmtime/src/runtime/vm/sys/custom/vm.rs index f2303388ea..23a98c6e7b 100644 --- a/crates/wasmtime/src/runtime/vm/sys/custom/vm.rs +++ b/crates/wasmtime/src/runtime/vm/sys/custom/vm.rs @@ -7,6 +7,8 @@ use core::ptr::{self, NonNull}; #[cfg(feature = "std")] use std::{fs::File, sync::Arc}; +pub use crate::runtime::vm::pagemap_disabled::{PageMap, reset_with_pagemap}; + pub unsafe fn expose_existing_mapping(ptr: *mut u8, len: usize) -> Result<()> { unsafe { cvt(capi::wasmtime_mprotect( diff --git a/crates/wasmtime/src/runtime/vm/sys/miri/vm.rs b/crates/wasmtime/src/runtime/vm/sys/miri/vm.rs index d2e066b7f9..9a800573d0 100644 --- a/crates/wasmtime/src/runtime/vm/sys/miri/vm.rs +++ b/crates/wasmtime/src/runtime/vm/sys/miri/vm.rs @@ -3,6 +3,8 @@ use std::fs::File; use std::io; use std::sync::Arc; +pub use crate::runtime::vm::pagemap_disabled::{PageMap, reset_with_pagemap}; + pub unsafe fn expose_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> { unsafe { std::ptr::write_bytes(ptr, 0u8, len); diff --git a/crates/wasmtime/src/runtime/vm/sys/unix/mod.rs b/crates/wasmtime/src/runtime/vm/sys/unix/mod.rs index 39ffaadcc8..24137b7f5c 100644 --- a/crates/wasmtime/src/runtime/vm/sys/unix/mod.rs +++ b/crates/wasmtime/src/runtime/vm/sys/unix/mod.rs @@ -18,6 +18,11 @@ pub mod machports; #[cfg(has_native_signals)] pub mod signals; +#[cfg(all(target_os = "linux", target_pointer_width = "64", feature = "std"))] +mod pagemap; +#[cfg(not(all(target_os = "linux", target_pointer_width = "64", feature = "std")))] +use crate::vm::pagemap_disabled as pagemap; + std::thread_local!(static TLS: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) }); #[inline] diff --git a/crates/wasmtime/src/runtime/vm/sys/unix/pagemap.rs b/crates/wasmtime/src/runtime/vm/sys/unix/pagemap.rs new file mode 100644 index 0000000000..db647d9366 --- /dev/null +++ b/crates/wasmtime/src/runtime/vm/sys/unix/pagemap.rs @@ -0,0 +1,994 @@ +//! Module for Linux pagemap based tracking of dirty pages. +//! +//! For other platforms, a no-op implementation is provided. + +#[cfg(feature = "pooling-allocator")] +use crate::prelude::*; + +use self::ioctl::{Categories, PageMapScanBuilder}; +use crate::runtime::vm::{HostAlignedByteCount, host_page_size}; +use rustix::ioctl::ioctl; +use std::fs::File; +use std::mem::MaybeUninit; +use std::ptr; + +/// A static file-per-process which represents this process's page map file. +/// +/// Note that this is required to be updated on a fork because otherwise this'll +/// refer to the parent process's page map instead of the child process's page +/// map. Thus when first initializing this file the `pthread_atfork` function is +/// used to hook the child process to update this. +/// +/// Also note that updating this is not done via mutation but rather it's done +/// with `dup2` to replace the file descriptor that `File` points to in-place. +/// The local copy of of `File` is then closed in the atfork handler. +#[cfg(feature = "pooling-allocator")] +static PROCESS_PAGEMAP: std::sync::LazyLock> = std::sync::LazyLock::new(|| { + use rustix::fd::AsRawFd; + + let pagemap = File::open("/proc/self/pagemap").ok()?; + + // SAFETY: all libc functions are unsafe by default, and we're basically + // going to do our damndest to make sure this invocation of `pthread_atfork` + // is safe, namely the handler registered here is intentionally quite + // minimal and only accesses the `PROCESS_PAGEMAP`. + let rc = unsafe { libc::pthread_atfork(None, None, Some(after_fork_in_child)) }; + if rc != 0 { + return None; + } + + return Some(pagemap); + + /// Hook executed as part of `pthread_atfork` in the child process after a + /// fork. + /// + /// # Safety + /// + /// This function is not safe to call in general and additionally has its + /// own stringent safety requirements. This is after a fork but before exec + /// so all the safety requirements of `Command::pre_exec` in the standard + /// library apply here. Effectively the standard library primitives are + /// avoided here as they aren't necessarily safe to execute in this context. + unsafe extern "C" fn after_fork_in_child() { + let Some(parent_pagemap) = PROCESS_PAGEMAP.as_ref() else { + // This should not be reachable, but to avoid panic infrastructure + // here this is just skipped instead. + return; + }; + + // SAFETY: see function documentation. + // + // Here `/proc/self/pagemap` is opened in the child. If that fails for + // whatever reason then the pagemap is replaced with `/dev/null` which + // means that all future ioctls for `PAGEMAP_SCAN` will fail. If that + // fails then that's left to abort the process for now. If that's + // problematic we may want to consider opening a local pipe and then + // installing that here? Unsure. + // + // Once a fd is opened the `dup2` syscall is used to replace the + // previous file descriptor stored in `parent_pagemap`. That'll update + // the pagemap in-place in this child for all future use in case this is + // further used in the child. + // + // And finally once that's all done the `child_pagemap` is itself + // closed since we have no more need for it. + unsafe { + let flags = libc::O_CLOEXEC | libc::O_RDONLY; + let mut child_pagemap = libc::open(c"/proc/self/pagemap".as_ptr(), flags); + if child_pagemap == -1 { + child_pagemap = libc::open(c"/dev/null".as_ptr(), flags); + } + if child_pagemap == -1 { + libc::abort(); + } + + let rc = libc::dup2(child_pagemap, parent_pagemap.as_raw_fd()); + if rc == -1 { + libc::abort(); + } + let rc = libc::close(child_pagemap); + if rc == -1 { + libc::abort(); + } + } + } +}); + +#[derive(Debug)] +pub struct PageMap(&'static File); + +impl PageMap { + #[cfg(feature = "pooling-allocator")] + pub fn new() -> Option { + let file = PROCESS_PAGEMAP.as_ref()?; + + // Check if the `pagemap_scan` ioctl is supported. + let mut regions = vec![MaybeUninit::uninit(); 1]; + let pm_scan = PageMapScanBuilder::new(ptr::slice_from_raw_parts(ptr::null_mut(), 0)) + .max_pages(1) + .return_mask(Categories::empty()) + .category_mask(Categories::all()) + .build(&mut regions); + + // SAFETY: we did our best in the `ioctl` code below to model this ioctl + // safely, and it's safe to issue the ioctl on `/proc/self/pagemap`. + unsafe { + ioctl(&file, pm_scan).ok()?; + } + Some(PageMap(file)) + } +} + +/// Resets `ptr` for `len` bytes. +/// +/// This function is a dual implementation of this function in the +/// `pagemap_disabled` module except it uses the `PAGEMAP_SCAN` [ioctl] on +/// Linux to be more clever about calling the `reset_manually` closure. +/// Semantically though this still has the same meaning where all of `ptr` for +/// `len` bytes will be reset, either through `reset_manually` or `decommit`. +/// The optimization here is that `reset_manually` will only be called on +/// regions as-necessary and `decommit` can be skipped entirely in some +/// situations. +/// +/// The `PAGEMAP_SCAN` [ioctl] scans a region of memory and reports back +/// "regions of interest" as configured by the scan. It also does things with +/// uffd and write-protected pages, but that's not leveraged here. Specifically +/// this function will perform a scan of `ptr` for `len` bytes which will search +/// for pages that: +/// +/// * Are present. +/// * Have been written. +/// * Are NOT backed by the "zero" page. +/// * Are NOT backed by a "file" page. +/// +/// By default WebAssembly memories/tables are all accessible virtual memory, +/// but paging optimizations on Linux means they don't actually have a backing +/// page. For example when an instance starts for the first time its entire +/// linear memory will be mapped as anonymous memory where page-table-entries +/// don't even exist for the new memory. Most modules will then have an initial +/// image mapped in, but that still won't have any page table entries. When +/// memory is accessed for the first time a page fault will be generated and +/// handled by the kernel. +/// +/// If memory is read then the page fault will force a PTE to be allocated to +/// either zero-backed pages (e.g. ZFOD behavior) or a file-backed page if the +/// memory is in the initial image mapping. For ZFOD the kernel uses a single +/// page for the entire system of zeros and for files it uses the page map cache +/// in the kernel to share the same page across many mappings (as it's all +/// read-only anyway). Note that in this situation the PTE allocated will have +/// the write permission disabled meaning that a write will later generate a +/// page fault. +/// +/// If memory is written then that will allocate a fresh page from the kernel. +/// If the PTE was not previously present then the fresh page is initialized +/// either with zeros or a copy of the contents of the file-backed mapping. If +/// the PTE was previously present then its previous contents are copied into +/// the new page. In all of these cases the final PTE allocate will be a private +/// page to just this process which will be reflected nowhere else on the +/// system. +/// +/// Putting this all together this helps explain the search criteria for +/// `PAGEMAP_SCAN`, notably: +/// +/// * `Categories::PRESENT` - we're only interested in present pages, anything +/// unmapped wasn't touched by the guest so no need for the host to touch it +/// either. +/// +/// * `Categories::WRITTEN` - if a page was only read by the guest no need to +/// take a look at it as the contents aren't changed from the initial image. +/// +/// * `!Categories::PFNZERO` - if a page is mapped to the zero page then it's +/// guaranteed to be readonly and it means that wasm read the memory but +/// didn't write to it, additionally meaning it doesn't need to be reset. +/// +/// * `!Categories::FILE` - similar to `!PFNZERO` if a page is mapped to a file +/// then for us that means it's readonly meaning wasm only read the memory, +/// didn't write to it, so the page can be skipped. +/// +/// The `PAGEMAP_SCAN` will report back a set of contiguous regions of memory +/// which match our scan flags that we're looking for. Each of these regions is +/// then passed to `reset_manually` as-is. The ioctl will additionally then +/// report a "walk_end" address which is the last address it considered before +/// the scan was halted. A scan can stop for 3 reasons: +/// +/// * The end of the region of memory being scanned was reached. In this case +/// the entire region was scanned meaning that all dirty memory was reported +/// through `reset_manually`. This means that `decommit` can be skipped +/// entirely (or invoked with a 0 length here which will also end up with it +/// being skipped). +/// +/// * The scan's `max_pages` setting was reached. The `keep_resident` argument +/// indicates the maximal amount of memory to pass to `reset_manually` and +/// this translates to the `max_pages` configuration option to the ioctl. The +/// sum total of the size of all regions reported from the ioctl is guaranteed +/// to be less than `max_pages`. This means that if a scan reaches the +/// `keep_resident` limit before reaching the end then the ioctl will bail out +/// early. That means that the wasm module's working set of memory was larger +/// than `keep_resident` and then the rest of it will be `decommit`'d away. +/// +/// * The scan's returned set of regions exceeds the capacity passed into the +/// ioctl. The `pm_scan_arg` of the ioctl takes a `vec` and `vec_len` which is +/// a region of memory to store a list of `page_region` structures. Below this +/// is always `MAX_REGIONS`. If there are more than this number of disjoint +/// regions of memory that need to be reported then the ioctl will also return +/// early without reaching the end of memory. Note that this means that all +/// further memory will be `decommit`'d with reported regions still going to +/// `reset_manually`. This is arguably something we should detect and improve +/// in Wasmtime, but for now `MAX_REGIONS` is hardcoded. +/// +/// In the end this ends up being a "more clever" version of this function than +/// the one in the `pagemap_disabled` module. By using `PAGEMAP_SCAN` we can +/// search for the first `keep_resident` bytes of dirty memory written to by a +/// wasm guest instead of assuming the first `keep_resident` bytes of the region +/// were modified by the guest. This crucially enables the `decommit` operation +/// to a noop if the wasm guest's set of working memory is less than +/// `keep_resident` which means that `memcpy` is sufficient to reset a linear +/// memory or table. This directly translates to higher throughput as it avoids +/// IPIs and synchronization updating page tables and additionally avoids page +/// faults on future executions of the same module. +/// +/// # Safety +/// +/// Requires that `ptr` is valid to read and write for `len` bytes. +/// +/// [ioctl]: https://www.man7.org/linux/man-pages/man2/PAGEMAP_SCAN.2const.html +pub unsafe fn reset_with_pagemap( + mut pagemap: Option<&PageMap>, + ptr: *mut u8, + len: HostAlignedByteCount, + mut keep_resident: HostAlignedByteCount, + mut reset_manually: impl FnMut(&mut [u8]), + mut decommit: impl FnMut(*mut u8, usize), +) { + keep_resident = keep_resident.min(len); + let host_page_size = host_page_size(); + + if pagemap.is_some() { + // Nothing to keep resident? fall back to the default behavior. + if keep_resident.byte_count() == 0 { + pagemap = None; + } + + // Keeping less than one page of memory resident when the original + // mapping itself is also less than a page? Also fall back to the + // default behavior as this'll just be a simple memcpy. + if keep_resident.byte_count() <= host_page_size && len.byte_count() <= host_page_size { + pagemap = None; + } + } + + let pagemap = match pagemap { + Some(pagemap) => pagemap, + + // Fall back to the default behavior. + // + // SAFETY: the safety requirement of + // `pagemap_disabled::reset_with_pagemap` is the same as this function. + _ => unsafe { + return crate::runtime::vm::pagemap_disabled::reset_with_pagemap( + None, + ptr, + len, + keep_resident, + reset_manually, + decommit, + ); + }, + }; + + // For now use a fixed set of regions on the stack, but in the future this + // may want to use a dynamically allocated vector for more regions for + // example. + const MAX_REGIONS: usize = 32; + let mut storage = [MaybeUninit::uninit(); MAX_REGIONS]; + + let scan_arg = PageMapScanBuilder::new(ptr::slice_from_raw_parts(ptr, len.byte_count())) + .max_pages(keep_resident.byte_count() / host_page_size) + // We specifically want pages that are NOT backed by the zero page or + // backed by files. Such pages mean that they haven't changed from their + // original contents, so they're inverted. + .category_inverted(Categories::PFNZERO | Categories::FILE) + // Search for pages that are written and present as those are the dirty + // pages. Additionally search for the zero page/file page as those are + // inverted above meaning we're searching for pages that specifically + // don't have those flags. + .category_mask( + Categories::WRITTEN | Categories::PRESENT | Categories::PFNZERO | Categories::FILE, + ) + // Don't return any categories back. This helps group regions together + // since the reported set of categories is always empty and we otherwise + // aren't looking for anything in particular. + .return_mask(Categories::empty()) + .build(&mut storage); + + // SAFETY: this should be a safe ioctl as we control the fd we're operating + // on plus all of `scan_arg`, but this relies on `Ioctl` below being the + // correct implementation and such. + let result = match unsafe { ioctl(&pagemap.0, scan_arg) } { + Ok(result) => result, + + // If the ioctl fails for whatever reason, we at least tried, so fall + // back to the default behavior. + // + // SAFETY: the safety requirement of + // `pagemap_disabled::reset_with_pagemap` is the same as this function. + Err(err) => unsafe { + log::warn!("failed pagemap scan {err}"); + return crate::runtime::vm::pagemap_disabled::reset_with_pagemap( + None, + ptr, + len, + keep_resident, + reset_manually, + decommit, + ); + }, + }; + + // For all regions that were written in the scan reset them manually, then + // afterwards decommit everything else. + for region in result.regions() { + // SAFETY: we're relying on Linux to pass in valid region ranges within + // the `ptr/len` we specified to the original syscall. + unsafe { + reset_manually(&mut *region.region().cast_mut()); + } + } + + // Report everything after `walk_end` to the end of memory as memory that + // must be decommitted as the scan didn't reach it. Note that if `walk_end` + // is already at the end of memory then the byte size of the decommitted + // memory here will be 0 meaning that this is a noop. + let scan_size = result.walk_end().addr() - ptr.addr(); + decommit(result.walk_end().cast_mut(), len.byte_count() - scan_size); +} + +mod ioctl { + use rustix::ioctl::*; + use std::ffi::c_void; + use std::fmt; + use std::marker; + use std::mem::MaybeUninit; + use std::ptr; + + bitflags::bitflags! { + /// Categories that can be filtered with [`PageMapScan`] + #[derive(Copy, Clone, PartialEq, Eq)] + #[repr(transparent)] + pub struct Categories: u64 { + /// The page has asynchronous write-protection enabled. + const WPALLOWED = 1 << 0; + /// The page has been written to from the time it was write protected. + const WRITTEN = 1 << 1; + /// The page is file backed. + const FILE = 1 << 2; + /// The page is present in the memory. + const PRESENT = 1 << 3; + /// The page is swapped. + const SWAPPED = 1 << 4; + /// The page has zero PFN. + const PFNZERO = 1 << 5; + /// The page is THP or Hugetlb backed. + const HUGE = 1 << 6; + // NB: I don't know what this is and I can't find documentation for + // it, it's just included here for complete-ness with the API that + // `PAGEMAP_SCAN` provides. + const SOFT_DIRTY = 1 << 7; + } + } + + impl fmt::Debug for Categories { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + bitflags::parser::to_writer(self, f) + } + } + + impl fmt::Display for Categories { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + bitflags::parser::to_writer(self, f) + } + } + + /// Builder-style structure for building up a [`PageMapScan`] `ioctl` call. + pub struct PageMapScanBuilder { + pm_scan_arg: pm_scan_arg, + } + + impl PageMapScanBuilder { + /// Creates a new page map scan that will scan the provided range of memory. + pub fn new(region: *const [u8]) -> PageMapScanBuilder { + PageMapScanBuilder { + pm_scan_arg: pm_scan_arg { + size: size_of::() as u64, + flags: 0, + start: region.cast::().addr() as u64, + end: region.cast::().addr().wrapping_add(region.len()) as u64, + walk_end: 0, + vec: 0, + vec_len: 0, + max_pages: 0, + category_inverted: Categories::empty(), + category_anyof_mask: Categories::empty(), + category_mask: Categories::empty(), + return_mask: Categories::empty(), + }, + } + } + + /// Configures the maximum number of returned pages in the output regions. + /// + /// Setting this to 0 disables this maximum. + pub fn max_pages(&mut self, max: usize) -> &mut PageMapScanBuilder { + self.pm_scan_arg.max_pages = max.try_into().unwrap(); + self + } + + /// Configures categories which values must match if 0 instead of 1. + /// + /// Note that this is a mask which is xor'd to the page's true + /// categories before testing for `category_mask`. That means that if a + /// bit needs to be zero then it additionally must be specified in one + /// of `category_mask` or `category_anyof_mask`. + /// + /// For more detail see the `pagemap_scan_is_interesting_page` function + /// in the Linux kernel source. + pub fn category_inverted(&mut self, flags: Categories) -> &mut PageMapScanBuilder { + self.pm_scan_arg.category_inverted = flags; + self + } + + /// Only consider pages for which all `flags` match. + /// + /// This mask is applied after `category_inverted` is used to flip bits + /// in a page's categories. Only pages which match all bits in `flags` + /// will be considered. + /// + /// For more detail see the `pagemap_scan_is_interesting_page` function + /// in the Linux kernel source. + pub fn category_mask(&mut self, flags: Categories) -> &mut PageMapScanBuilder { + self.pm_scan_arg.category_mask = flags; + self + } + + /// Only consider pages for which any bit of `flags` matches. + /// + /// After `category_inverted` and `category_mask` have been applied, if + /// this option is specified to a non-empty value, then at least one of + /// `flags` must be in a page's flags to be considered. That means that + /// flags specified in `category_inverted` will already be inverted for + /// consideration here. The page categories are and'd with `flags` and + /// some bit must be set for the page to be considered. + /// + /// For more detail see the `pagemap_scan_is_interesting_page` function + /// in the Linux kernel source. + #[expect(dead_code, reason = "bindings for the future if we need them")] + pub fn category_anyof_mask(&mut self, flags: Categories) -> &mut PageMapScanBuilder { + self.pm_scan_arg.category_anyof_mask = flags; + self + } + + /// Categories that are to be reported in the regions returned + pub fn return_mask(&mut self, flags: Categories) -> &mut PageMapScanBuilder { + self.pm_scan_arg.return_mask = flags; + self + } + + /// Finishes this configuration and flags that the scan results will be + /// placed within `dst`. The returned object can be used to perform the + /// pagemap scan ioctl. + pub fn build<'a>(&self, dst: &'a mut [MaybeUninit]) -> PageMapScan<'a> { + let mut ret = PageMapScan { + pm_scan_arg: self.pm_scan_arg, + _marker: marker::PhantomData, + }; + ret.pm_scan_arg.vec = dst.as_ptr() as u64; + ret.pm_scan_arg.vec_len = dst.len() as u64; + return ret; + } + } + + /// Return result of [`PageMapScanBuilder::build`] used to perform an `ioctl`. + #[repr(transparent)] + pub struct PageMapScan<'a> { + pm_scan_arg: pm_scan_arg, + _marker: marker::PhantomData<&'a mut [MaybeUninit]>, + } + + #[derive(Copy, Clone)] + #[repr(C)] + struct pm_scan_arg { + size: u64, + flags: u64, + start: u64, + end: u64, + walk_end: u64, + vec: u64, + vec_len: u64, + max_pages: u64, + category_inverted: Categories, + category_mask: Categories, + category_anyof_mask: Categories, + return_mask: Categories, + } + + /// Return result of a [`PageMapScan`] `ioctl`. + /// + /// This reports where the kernel stopped walking with + /// [`PageMapScanResult::walk_end`] and the description of regions found in + /// [`PageMapScanResult::regions`]. + #[derive(Debug)] + pub struct PageMapScanResult<'a> { + walk_end: *const u8, + regions: &'a mut [PageRegion], + } + + impl PageMapScanResult<'_> { + /// Where the kernel stopped walking pages, which may be earlier than the + /// end of the requested region + pub fn walk_end(&self) -> *const u8 { + self.walk_end + } + + /// Regions the kernel reported back with categories and such. + pub fn regions(&self) -> &[PageRegion] { + self.regions + } + } + + /// Return value of [`PageMapScan`], description of regions in the original scan + /// with the categories queried. + #[repr(transparent)] + #[derive(Copy, Clone)] + pub struct PageRegion(page_region); + + #[repr(C)] + #[derive(Debug, Copy, Clone)] + struct page_region { + start: u64, + end: u64, + categories: Categories, + } + + impl PageRegion { + /// Returns the region of memory this represents as `*const [u8]` + #[inline] + pub fn region(&self) -> *const [u8] { + ptr::slice_from_raw_parts(self.start(), self.len()) + } + + /// Returns the base pointer into memory this region represents. + #[inline] + pub fn start(&self) -> *const u8 { + self.0.start as *const u8 + } + + /// Returns the byte length that this region represents. + #[inline] + pub fn len(&self) -> usize { + usize::try_from(self.0.end - self.0.start).unwrap() + } + + /// Returns the category flags associated with this region. + /// + /// Note that this will only contain categories specified in + /// [`PageMapScanBuilder::return_mask`]. + #[inline] + #[cfg_attr( + not(test), + expect(dead_code, reason = "bindings for the future if we need them") + )] + pub fn categories(&self) -> Categories { + self.0.categories + } + } + + impl fmt::Debug for PageRegion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PageRegion") + .field("start", &self.start()) + .field("len", &self.len()) + .field("categories", &self.0.categories) + .finish() + } + } + + // SAFETY: this implementation should uphold the various requirements that + // this trait has, such as `IS_MUTATING` is right, it's only used on the + // right platform with the right files, etc. + unsafe impl<'a> Ioctl for PageMapScan<'a> { + type Output = PageMapScanResult<'a>; + + const IS_MUTATING: bool = true; + + fn opcode(&self) -> Opcode { + opcode::read_write::(b'f', 16) + } + + fn as_ptr(&mut self) -> *mut c_void { + (&raw mut self.pm_scan_arg).cast() + } + + unsafe fn output_from_ptr( + out: IoctlOutput, + extract_output: *mut c_void, + ) -> rustix::io::Result { + let extract_output = extract_output.cast::(); + let len = usize::try_from(out).unwrap(); + // SAFETY: it's a requirement of this method that + // `extract_output` is safe to read and indeed a `pm_scan_arg`. + // Additionally the slice returned here originated from a slice + // provided to `PageMapScanBuilder::build` threaded through the + // `vec` field and it should be safe to thread that back out through + // to the result. + let regions = unsafe { + assert!((len as u64) <= (*extract_output).vec_len); + std::slice::from_raw_parts_mut((*extract_output).vec as *mut PageRegion, len) + }; + Ok(PageMapScanResult { + regions, + // SAFETY: it's a requirement of this method that + // `extract_output` is safe to read and indeed a `pm_scan_arg`. + walk_end: unsafe { (*extract_output).walk_end as *const u8 }, + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::ioctl::*; + use crate::prelude::*; + use rustix::ioctl::*; + use rustix::mm::*; + use std::fs::File; + use std::ptr; + + struct MmapAnonymous { + ptr: *mut std::ffi::c_void, + len: usize, + } + + impl MmapAnonymous { + fn new(pages: usize) -> MmapAnonymous { + let len = pages * rustix::param::page_size(); + let ptr = unsafe { + mmap_anonymous( + ptr::null_mut(), + len, + ProtFlags::READ | ProtFlags::WRITE, + MapFlags::PRIVATE, + ) + .unwrap() + }; + MmapAnonymous { ptr, len } + } + + fn read(&self, page: usize) { + unsafe { + let offset = page * rustix::param::page_size(); + assert!(offset < self.len); + std::ptr::read_volatile(self.ptr.cast::().add(offset)); + } + } + + fn write(&self, page: usize) { + unsafe { + let offset = page * rustix::param::page_size(); + assert!(offset < self.len); + std::ptr::write_volatile(self.ptr.cast::().add(offset), 1); + } + } + + fn region(&self) -> *const [u8] { + ptr::slice_from_raw_parts(self.ptr.cast(), self.len) + } + + fn page_region(&self, pages: std::ops::Range) -> *const [u8] { + ptr::slice_from_raw_parts( + self.ptr + .cast::() + .wrapping_add(pages.start * rustix::param::page_size()), + (pages.end - pages.start) * rustix::param::page_size(), + ) + } + + fn end(&self) -> *const u8 { + self.ptr.cast::().wrapping_add(self.len) + } + + fn page_end(&self, page: usize) -> *const u8 { + self.ptr + .cast::() + .wrapping_add((page + 1) * rustix::param::page_size()) + } + } + + impl Drop for MmapAnonymous { + fn drop(&mut self) { + unsafe { + munmap(self.ptr, self.len).unwrap(); + } + } + } + + fn ioctl_supported() -> bool { + let mmap = MmapAnonymous::new(1); + let mut results = Vec::with_capacity(1); + let fd = File::open("/proc/self/pagemap").unwrap(); + unsafe { + ioctl( + &fd, + PageMapScanBuilder::new(mmap.region()) + .category_mask(Categories::WRITTEN) + .return_mask(Categories::all()) + .build(results.spare_capacity_mut()), + ) + .is_ok() + } + } + + #[test] + fn no_pages_returned() { + if !ioctl_supported() { + return; + } + let mmap = MmapAnonymous::new(10); + let mut results = Vec::with_capacity(10); + let fd = File::open("/proc/self/pagemap").unwrap(); + + let result = unsafe { + ioctl( + &fd, + PageMapScanBuilder::new(mmap.region()) + .category_mask(Categories::WRITTEN) + .return_mask(Categories::all()) + .build(results.spare_capacity_mut()), + ) + .unwrap() + }; + assert!(result.regions().is_empty()); + assert_eq!(result.walk_end(), mmap.end()); + } + + #[test] + fn empty_region() { + if !ioctl_supported() { + return; + } + let mut results = Vec::with_capacity(10); + let fd = File::open("/proc/self/pagemap").unwrap(); + + let empty_region = ptr::slice_from_raw_parts(rustix::param::page_size() as *const u8, 0); + let result = unsafe { + ioctl( + &fd, + PageMapScanBuilder::new(empty_region) + .return_mask(Categories::all()) + .build(results.spare_capacity_mut()), + ) + .unwrap() + }; + assert!(result.regions().is_empty()); + } + + #[test] + fn basic_page_flags() { + if !ioctl_supported() { + return; + } + let mmap = MmapAnonymous::new(10); + let mut results = Vec::with_capacity(10); + let fd = File::open("/proc/self/pagemap").unwrap(); + + mmap.read(0); + mmap.write(1); + mmap.write(2); + mmap.read(3); + + mmap.read(5); + mmap.read(6); + + let result = unsafe { + ioctl( + &fd, + PageMapScanBuilder::new(mmap.region()) + .category_mask(Categories::WRITTEN) + .return_mask(Categories::WRITTEN | Categories::PRESENT | Categories::PFNZERO) + .build(results.spare_capacity_mut()), + ) + .unwrap() + }; + assert_eq!(result.regions().len(), 4); + assert_eq!(result.walk_end(), mmap.end()); + assert_eq!(result.regions()[0].region(), mmap.page_region(0..1)); + assert_eq!( + result.regions()[0].categories(), + Categories::WRITTEN | Categories::PRESENT | Categories::PFNZERO + ); + + assert_eq!(result.regions()[1].region(), mmap.page_region(1..3)); + assert_eq!( + result.regions()[1].categories(), + Categories::WRITTEN | Categories::PRESENT + ); + + assert_eq!(result.regions()[2].region(), mmap.page_region(3..4)); + assert_eq!( + result.regions()[2].categories(), + Categories::WRITTEN | Categories::PRESENT | Categories::PFNZERO + ); + + assert_eq!(result.regions()[3].region(), mmap.page_region(5..7)); + assert_eq!( + result.regions()[3].categories(), + Categories::WRITTEN | Categories::PRESENT | Categories::PFNZERO + ); + } + + #[test] + fn only_written_pages() { + if !ioctl_supported() { + return; + } + let mmap = MmapAnonymous::new(10); + let mut results = Vec::with_capacity(10); + let fd = File::open("/proc/self/pagemap").unwrap(); + + mmap.read(0); + mmap.write(1); + mmap.write(2); + mmap.read(3); + + mmap.read(5); + mmap.read(6); + + let result = unsafe { + ioctl( + &fd, + PageMapScanBuilder::new(mmap.region()) + .category_inverted(Categories::PFNZERO) + .category_mask(Categories::WRITTEN | Categories::PFNZERO) + .return_mask(Categories::WRITTEN | Categories::PRESENT | Categories::PFNZERO) + .build(results.spare_capacity_mut()), + ) + .unwrap() + }; + assert_eq!(result.regions().len(), 1); + assert_eq!(result.walk_end(), mmap.end()); + + assert_eq!(result.regions()[0].region(), mmap.page_region(1..3)); + assert_eq!( + result.regions()[0].categories(), + Categories::WRITTEN | Categories::PRESENT + ); + } + + #[test] + fn region_limit() { + if !ioctl_supported() { + return; + } + let mmap = MmapAnonymous::new(10); + let mut results = Vec::with_capacity(1); + let fd = File::open("/proc/self/pagemap").unwrap(); + + mmap.read(0); + mmap.write(1); + mmap.read(2); + mmap.write(3); + + // Ask for written|pfnzero meaning only-read pages. This should return only + // a single region of the first page. + let result = unsafe { + ioctl( + &fd, + PageMapScanBuilder::new(mmap.region()) + .return_mask(Categories::WRITTEN | Categories::PFNZERO) + .build(results.spare_capacity_mut()), + ) + .unwrap() + }; + assert_eq!(result.regions().len(), 1); + assert_eq!(result.walk_end(), mmap.page_end(0)); + + assert_eq!(result.regions()[0].region(), mmap.page_region(0..1)); + assert_eq!( + result.regions()[0].categories(), + Categories::WRITTEN | Categories::PFNZERO + ); + + // If we ask for written pages though (which seems synonymous with + // present?) then everything should be in one region. + let result = unsafe { + ioctl( + &fd, + PageMapScanBuilder::new(mmap.region()) + .return_mask(Categories::WRITTEN) + .build(results.spare_capacity_mut()), + ) + .unwrap() + }; + assert_eq!(result.regions().len(), 1); + assert_eq!(result.walk_end(), mmap.page_end(3)); + + assert_eq!(result.regions()[0].region(), mmap.page_region(0..4)); + assert_eq!(result.regions()[0].categories(), Categories::WRITTEN); + } + + #[test] + fn page_limit() { + if !ioctl_supported() { + return; + } + let mmap = MmapAnonymous::new(10); + let mut results = Vec::with_capacity(10); + let fd = File::open("/proc/self/pagemap").unwrap(); + + mmap.read(0); + mmap.read(1); + mmap.read(2); + mmap.read(3); + + // Ask for written|pfnzero meaning only-read pages. This should return only + // a single region of the first page. + let result = unsafe { + ioctl( + &fd, + PageMapScanBuilder::new(mmap.region()) + .return_mask(Categories::WRITTEN | Categories::PFNZERO) + .max_pages(2) + .build(results.spare_capacity_mut()), + ) + .unwrap() + }; + assert_eq!(result.regions().len(), 1); + assert_eq!(result.walk_end(), mmap.page_end(1)); + + assert_eq!(result.regions()[0].region(), mmap.page_region(0..2)); + assert_eq!( + result.regions()[0].categories(), + Categories::WRITTEN | Categories::PFNZERO + ); + } + + #[test] + fn page_limit_with_hole() { + if !ioctl_supported() { + return; + } + let mmap = MmapAnonymous::new(10); + let mut results = Vec::with_capacity(10); + let fd = File::open("/proc/self/pagemap").unwrap(); + + mmap.read(0); + mmap.read(2); + mmap.read(3); + + // Ask for written|pfnzero meaning only-read pages. This should return only + // a single region of the first page. + let result = unsafe { + ioctl( + &fd, + PageMapScanBuilder::new(mmap.region()) + .category_mask(Categories::WRITTEN) + .return_mask(Categories::WRITTEN | Categories::PFNZERO) + .max_pages(2) + .build(results.spare_capacity_mut()), + ) + .unwrap() + }; + assert_eq!(result.regions().len(), 2); + assert_eq!(result.walk_end(), mmap.page_end(2)); + + assert_eq!(result.regions()[0].region(), mmap.page_region(0..1)); + assert_eq!( + result.regions()[0].categories(), + Categories::WRITTEN | Categories::PFNZERO + ); + assert_eq!(result.regions()[1].region(), mmap.page_region(2..3)); + assert_eq!( + result.regions()[1].categories(), + Categories::WRITTEN | Categories::PFNZERO + ); + } +} diff --git a/crates/wasmtime/src/runtime/vm/sys/unix/vm.rs b/crates/wasmtime/src/runtime/vm/sys/unix/vm.rs index f11af94c4c..237b8d9b28 100644 --- a/crates/wasmtime/src/runtime/vm/sys/unix/vm.rs +++ b/crates/wasmtime/src/runtime/vm/sys/unix/vm.rs @@ -6,6 +6,8 @@ use std::io; #[cfg(feature = "std")] use std::sync::Arc; +pub use super::pagemap::{PageMap, reset_with_pagemap}; + pub unsafe fn expose_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> { unsafe { mprotect(ptr.cast(), len, MprotectFlags::READ | MprotectFlags::WRITE)?; diff --git a/crates/wasmtime/src/runtime/vm/sys/windows/vm.rs b/crates/wasmtime/src/runtime/vm/sys/windows/vm.rs index af69e15b56..eb367c9029 100644 --- a/crates/wasmtime/src/runtime/vm/sys/windows/vm.rs +++ b/crates/wasmtime/src/runtime/vm/sys/windows/vm.rs @@ -6,6 +6,8 @@ use std::sync::Arc; use windows_sys::Win32::System::Memory::*; use windows_sys::Win32::System::SystemInformation::*; +pub use crate::runtime::vm::pagemap_disabled::{PageMap, reset_with_pagemap}; + pub unsafe fn expose_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> { if len == 0 { return Ok(()); diff --git a/crates/wasmtime/src/runtime/vm/table.rs b/crates/wasmtime/src/runtime/vm/table.rs index 50d01a1b4d..117dbb30ef 100644 --- a/crates/wasmtime/src/runtime/vm/table.rs +++ b/crates/wasmtime/src/runtime/vm/table.rs @@ -1126,7 +1126,7 @@ impl Table { } fn copy_elements( - gc_store: Option<&mut GcStore>, + mut gc_store: Option<&mut GcStore>, dst_table: &mut Self, src_table: &Self, dst_range: Range, @@ -1151,9 +1151,9 @@ impl Table { ); assert!(dst_range.end <= dst_table.gc_refs().len()); assert!(src_range.end <= src_table.gc_refs().len()); - let gc_store = gc_store.unwrap(); for (dst, src) in dst_range.zip(src_range) { - gc_store.write_gc_ref( + GcStore::write_gc_ref_optional_store( + gc_store.as_deref_mut(), &mut dst_table.gc_refs_mut()[dst], src_table.gc_refs()[src].as_ref(), ); @@ -1169,7 +1169,7 @@ impl Table { fn copy_elements_within( &mut self, - gc_store: Option<&mut GcStore>, + mut gc_store: Option<&mut GcStore>, dst_range: Range, src_range: Range, ) { @@ -1191,8 +1191,6 @@ impl Table { funcrefs.copy_within(src_range, dst_range.start); } TableElementType::GcRef => { - let gc_store = gc_store.unwrap(); - // We need to clone each `externref` while handling overlapping // ranges let elements = self.gc_refs_mut(); @@ -1201,14 +1199,14 @@ impl Table { let (ds, ss) = elements.split_at_mut(s); let dst = &mut ds[d]; let src = ss[0].as_ref(); - gc_store.write_gc_ref(dst, src); + GcStore::write_gc_ref_optional_store(gc_store.as_deref_mut(), dst, src); } } else { for (s, d) in src_range.rev().zip(dst_range.rev()) { let (ss, ds) = elements.split_at_mut(d); let dst = &mut ds[0]; let src = ss[s].as_ref(); - gc_store.write_gc_ref(dst, src); + GcStore::write_gc_ref_optional_store(gc_store.as_deref_mut(), dst, src); } } } diff --git a/crates/wasmtime/src/runtime/vm/vmcontext.rs b/crates/wasmtime/src/runtime/vm/vmcontext.rs index ff2ba0141a..e089543da3 100644 --- a/crates/wasmtime/src/runtime/vm/vmcontext.rs +++ b/crates/wasmtime/src/runtime/vm/vmcontext.rs @@ -5,7 +5,7 @@ mod vm_host_func_context; pub use self::vm_host_func_context::VMArrayCallHostFuncContext; use crate::prelude::*; -use crate::runtime::vm::{GcStore, InterpreterRef, VMGcRef, VmPtr, VmSafe, f32x4, f64x2, i8x16}; +use crate::runtime::vm::{InterpreterRef, VMGcRef, VmPtr, VmSafe, f32x4, f64x2, i8x16}; use crate::store::StoreOpaque; use crate::vm::stack_switching::VMStackChain; use core::cell::UnsafeCell; @@ -18,7 +18,7 @@ use core::ptr::{self, NonNull}; use core::sync::atomic::{AtomicUsize, Ordering}; use wasmtime_environ::{ BuiltinFunctionIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, - DefinedTagIndex, Unsigned, VMCONTEXT_MAGIC, VMSharedTypeIndex, WasmHeapTopType, WasmValType, + DefinedTagIndex, VMCONTEXT_MAGIC, VMSharedTypeIndex, WasmHeapTopType, WasmValType, }; /// A function pointer that exposes the array calling convention. @@ -560,17 +560,17 @@ impl VMGlobalDefinition { WasmValType::Ref(r) => match r.heap_type.top() { WasmHeapTopType::Extern => { let r = VMGcRef::from_raw_u32(raw.get_externref()); - global.init_gc_ref(store.gc_store_mut()?, r.as_ref()) + global.init_gc_ref(store, r.as_ref()) } WasmHeapTopType::Any => { let r = VMGcRef::from_raw_u32(raw.get_anyref()); - global.init_gc_ref(store.gc_store_mut()?, r.as_ref()) + global.init_gc_ref(store, r.as_ref()) } WasmHeapTopType::Func => *global.as_func_ref_mut() = raw.get_funcref().cast(), WasmHeapTopType::Cont => *global.as_func_ref_mut() = raw.get_funcref().cast(), // TODO(#10248): temporary hack. WasmHeapTopType::Exn => { let r = VMGcRef::from_raw_u32(raw.get_exnref()); - global.init_gc_ref(store.gc_store_mut()?, r.as_ref()) + global.init_gc_ref(store, r.as_ref()) } }, } @@ -597,18 +597,18 @@ impl VMGlobalDefinition { WasmValType::V128 => ValRaw::v128(self.get_u128()), WasmValType::Ref(r) => match r.heap_type.top() { WasmHeapTopType::Extern => ValRaw::externref(match self.as_gc_ref() { - Some(r) => store.gc_store_mut()?.clone_gc_ref(r).as_raw_u32(), + Some(r) => store.clone_gc_ref(r).as_raw_u32(), None => 0, }), WasmHeapTopType::Any => ValRaw::anyref({ match self.as_gc_ref() { - Some(r) => store.gc_store_mut()?.clone_gc_ref(r).as_raw_u32(), + Some(r) => store.clone_gc_ref(r).as_raw_u32(), None => 0, } }), WasmHeapTopType::Exn => ValRaw::exnref({ match self.as_gc_ref() { - Some(r) => store.gc_store_mut()?.clone_gc_ref(r).as_raw_u32(), + Some(r) => store.clone_gc_ref(r).as_raw_u32(), None => 0, } }), @@ -736,9 +736,7 @@ impl VMGlobalDefinition { } /// Initialize a global to the given GC reference. - pub unsafe fn init_gc_ref(&mut self, gc_store: &mut GcStore, gc_ref: Option<&VMGcRef>) { - assert!(cfg!(feature = "gc") || gc_ref.is_none()); - + pub unsafe fn init_gc_ref(&mut self, store: &mut StoreOpaque, gc_ref: Option<&VMGcRef>) { let dest = unsafe { &mut *(self .storage @@ -747,17 +745,13 @@ impl VMGlobalDefinition { .cast::>>()) }; - gc_store.init_gc_ref(dest, gc_ref) + store.init_gc_ref(dest, gc_ref) } /// Write a GC reference into this global value. - pub unsafe fn write_gc_ref(&mut self, gc_store: &mut GcStore, gc_ref: Option<&VMGcRef>) { - assert!(cfg!(feature = "gc") || gc_ref.is_none()); - + pub unsafe fn write_gc_ref(&mut self, store: &mut StoreOpaque, gc_ref: Option<&VMGcRef>) { let dest = unsafe { &mut *(self.storage.as_mut().as_mut_ptr().cast::>()) }; - assert!(cfg!(feature = "gc") || dest.is_none()); - - gc_store.write_gc_ref(dest, gc_ref) + store.write_gc_ref(dest, gc_ref) } /// Return a reference to the value as a `VMFuncRef`. @@ -1458,7 +1452,7 @@ impl ValRaw { // `wasmtime` crate. Otherwise though all `ValRaw` constructors are // otherwise constrained to guarantee that the initial 64-bits are // always initialized. - ValRaw::u64(i.unsigned().into()) + ValRaw::u64(i.cast_unsigned().into()) } /// Creates a WebAssembly `i64` value @@ -1549,13 +1543,13 @@ impl ValRaw { /// Gets the WebAssembly `i32` value #[inline] pub fn get_u32(&self) -> u32 { - self.get_i32().unsigned() + self.get_i32().cast_unsigned() } /// Gets the WebAssembly `i64` value #[inline] pub fn get_u64(&self) -> u64 { - self.get_i64().unsigned() + self.get_i64().cast_unsigned() } /// Gets the WebAssembly `f32` value diff --git a/crates/wasmtime/tests/engine_across_forks.rs b/crates/wasmtime/tests/engine_across_forks.rs new file mode 100644 index 0000000000..c5d779ac30 --- /dev/null +++ b/crates/wasmtime/tests/engine_across_forks.rs @@ -0,0 +1,135 @@ +use libtest_mimic::Arguments; +use wasmtime::Result; + +fn main() -> Result<()> { + let mut trials = Vec::new(); + + #[cfg(unix)] + if !cfg!(miri) && !cfg!(asan) { + for (name, test) in unix::TESTS { + trials.push(libtest_mimic::Trial::test(*name, || { + test().map_err(|e| format!("{e:?}").into()) + })); + } + } + let _ = &mut trials; + + let mut args = Arguments::from_args(); + // I'll be honest, I'm scared of threads + fork, so I'm just + // preemptively disabling threads here. + args.test_threads = Some(1); + libtest_mimic::run(&args, trials).exit() +} + +#[cfg(unix)] +mod unix { + use rustix::fd::AsRawFd; + use rustix::process::{Pid, WaitOptions, waitpid}; + use std::io::{self, BufRead, BufReader}; + use wasmtime::*; + + pub const TESTS: &[(&str, fn() -> Result<()>)] = &[ + ("smoke", smoke), + ("pooling_allocator_reset", pooling_allocator_reset), + ]; + + fn smoke() -> Result<()> { + let mut config = Config::new(); + config.macos_use_mach_ports(false); + let engine = Engine::new(&config)?; + let module = Module::new(&engine, r#"(module (func (export "")))"#)?; + run_in_child(|| { + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &module, &[])?; + let export = instance.get_typed_func::<(), ()>(&mut store, "")?; + export.call(&mut store, ())?; + Ok(()) + })?; + Ok(()) + } + + fn pooling_allocator_reset() -> Result<()> { + let mut pooling = PoolingAllocationConfig::new(); + pooling.linear_memory_keep_resident(4096); + let mut config = Config::new(); + config.allocation_strategy(pooling); + config.macos_use_mach_ports(false); + let engine = Engine::new(&config)?; + let module = Module::new( + &engine, + r#" + (module + (memory (export "") 1 1) + (data (i32.const 0) "\0a") + ) + "#, + )?; + + let assert_pristine = || { + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &module, &[])?; + let memory = instance.get_memory(&mut store, "").unwrap(); + let data = memory.data(&store); + assert_eq!(data[0], 0x0a); + anyhow::Ok((store, memory)) + }; + run_in_child(|| { + // Allocate a memory, and then mutate it. + let (mut store, memory) = assert_pristine()?; + let data = memory.data_mut(&mut store); + data[0] = 0; + drop(store); + + // Allocating the memory again should reuse the same pooling + // allocator slot but it should be reset correctly. + assert_pristine()?; + assert_pristine()?; + Ok(()) + })?; + Ok(()) + } + + fn run_in_child(closure: impl FnOnce() -> Result<()>) -> Result<()> { + let (read, write) = io::pipe()?; + let child = match unsafe { libc::fork() } { + -1 => return Err(io::Error::last_os_error().into()), + + 0 => { + // If a panic happens, don't let it go above this stack frame. + let _bomb = Bomb; + + drop(read); + unsafe { + assert!(libc::dup2(write.as_raw_fd(), 1) == 1); + assert!(libc::dup2(write.as_raw_fd(), 2) == 2); + } + drop(write); + + closure().unwrap(); + std::process::exit(0); + } + + pid => pid, + }; + + drop(write); + + for line in BufReader::new(read).lines() { + println!("CHILD: {}", line?); + } + + let (_pid, status) = + waitpid(Some(Pid::from_raw(child).unwrap()), WaitOptions::empty())?.unwrap(); + assert_eq!(status.as_raw(), 0); + + Ok(()) + } + + struct Bomb; + + impl Drop for Bomb { + fn drop(&mut self) { + std::process::exit(1); + } + } +} diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs index b654c21485..5356032980 100644 --- a/crates/wit-bindgen/src/lib.rs +++ b/crates/wit-bindgen/src/lib.rs @@ -2559,11 +2559,16 @@ impl<'a> InterfaceGenerator<'a> { None => format!("Host"), }; let convert = format!("{}::convert_{}", convert_trait, err_name.to_snake_case()); + let convert = if flags.contains(FunctionFlags::STORE) { + format!("accessor.with(|mut host| {convert}(&mut host.get(), e))?") + } else { + format!("{convert}(host, e)?") + }; uwrite!( self.src, "Ok((match r {{ Ok(a) => Ok(a), - Err(e) => Err({convert}(host, e)?), + Err(e) => Err({convert}), }},))" ); } else if func.result.is_some() { diff --git a/docs/contributing-fuzzing.md b/docs/contributing-fuzzing.md index e5cb20199f..782aa1d61a 100644 --- a/docs/contributing-fuzzing.md +++ b/docs/contributing-fuzzing.md @@ -1,6 +1,160 @@ # Fuzzing -## Test Case Generators and Oracles +## External Fuzzing Campaigns + +The Wasmtime maintainers appreciate bug reports from external fuzzing campaigns +— when done thoughtfully and responsibly. Triaging and diagnosing bug reports, +particularly reports for bugs found by fuzzers, takes a lot of time and effort +on the part of Wasmtime maintainers. We ask that you match that effort by +following the guidelines below. + +### Talk To Us First + +We would love to collaborate and help you find bugs in Wasmtime! We do lots of +fuzzing already (see the docs below about our internal fuzzing infrastructure) +but we always have ideas for new kinds of generators for directed fuzzing, new +oracles that we wish we had, areas of code that we wish were better fuzzed, and +etc... We can share which of Wasmtime's properties are most important to us and +what kinds of bugs we value discovering the most. It is also good for us to know +that an external fuzzing campaign is spinning up and that we should be on the +look out for new issues being filed. + +[Come say hello on our Zulip and introduce yourself +:)](https://bytecodealliance.zulipchat.com/#narrow/stream/217126-wasmtime) + +### If a Bug Might be a Security Vulnerability, Do Not File a Public Issue + +When you find a new bug, first evaluate it against our +[guidelines](./security-what-is-considered-a-security-vulnerability.md) for what +constitutes a security vulnerability. + +If you determine that the bug is not a security vulnerability, then [file an +issue on our public +tracker](https://github.com/bytecodealliance/wasmtime/issues/new/choose). + +If you think the bug might be considered a security vulnerability, **do not open +a public issue detailing the bug!** Instead, follow the vulnerability reporting +process documented [here](https://bytecodealliance.org/security). + +### Write Good Bug Reports + +The Wasmtime maintainers appreciate bug reports with the following: + +1. **A minimal test case:** You should integrate [automatic test-case + reduction](./contributing-reducing-test-cases.md) into your fuzzing campaign. + +* **Steps to reproduce:** Simple, unambiguous steps that can be performed to + reproduce the buggy behavior with the test case. These steps should include + all options you configured Wasmtime with, such as CLI flags and + `wasmtime::Config` method calls. Ideally these steps are as simple for + maintainers to execute as running `wasmtime [OPTIONS] testcase.wasm` or a Rust + `#[test]` that can be run via `cargo test`. + +* **Expected behavior:** A description of the expected, non-buggy behavior, as + well as your rationale for *why* that behavior is expected. For example, just + because another Wasm engine or an alternative Wasmtime execution strategy + produces a different result from default Wasmtime, that is not necessarily a + bug. See the [documentation + below](#divergent-webassembly-behavior-across-runtimes) for examples of known divergent + behavior of one module in two runtimes. If applicable, make sure to account + for this in your rationale and analysis of the bug. + +* **Actual behavior:** A description of the actual, buggy behavior. This should + include things various things like incorrect computation results, assertion + failure messages, stack traces, signals raised, and etc... when applicable. + +* **Wasmtime version and system information:** Include the version of Wasmtime + you are using (either the exact release or the git commit hash) and your ISA, + operating system, distribution, and kernel version in the bug report. + +Including the above information is extra important for bugs discovered +mechanically, whether by fuzzing or other means, since the associated test cases +will often be pseudo-random or otherwise unintuitive to debug. + +### Divergent WebAssembly behavior across runtimes + +WebAssembly has a variety of sources of [non-determinism] which means that the +exact same module is allowed to behave differently under the same inputs +across multiple runtimes. These specifics don't often arise in "real world" +modules but can quickly arise during fuzzing. Some example behaviors are: + +* **NaN bit patterns** - floating-point operations which produce NaN as a result + are allowed to produce any one of a set of patterns of NaN. This means that + the exact bit-representation of the result of a floating-point operation may + diverge across engines. When fuzzing you can update your source-generation to + automatically canonicalize NaN values after all floating point operations. + Wasmtime has built-in options to curb this [non-determinism] as well. + +* **Relaxed SIMD** - the `relaxed-simd` proposal to WebAssembly explicitly has + multiple allowed results for instructions given particular inputs. These + instructions are inherently non-deterministic across implementations. When + fuzzing you can avoid these instructions entirely, canonicalize the results, + or use Wasmtime's built-in options to curb the [non-determinism]. + +* **Call stack exhaustion** - the WebAssembly specification requires that all + function calls consume a nonzero-amount of some resource which can eventually + be exhausted. This means that infinite recursion is not allowed in any + WebAssembly engine. Bounded, but very large, recursion is allowed in + WebAssembly but is not guaranteed to work across WebAssembly engines. One + engine may have different stack settings than another engine and/or runtime + parameters may tune how much stack space is taken (e.g. optimizations on/off). + If one engine stack overflows and another doesn't then that's not necessarily + a bug in either engine. Short of banning recursion there's no known great way + to handle this apart from throwing out fuzz test cases that stack overflow. + +* **Memory exhaustion** - the `memory.grow` and `table.grow` instructions in + WebAssembly are not always guaranteed to either fail or succeed. This means + that growth may succeed in one engine but fail in another depending on various + settings. To handle this in fuzzing it's recommended to generate memories with + a maximum size and ensure that each engine being fuzzed can grow memory all + the way to the maximum size. + +* **WASIp1 API behavior** - the initial specification of WASI, WASIp1 or + `wasi_snapshot_preview1`, effectively is not suitable for differential fuzzing + across engines. The APIs are not thoroughly specified enough nor is there a + rigorous enough test suite to codify what exactly should happen in all + situations on all platforms. This means that exactly what kind of error arises + or various other edge cases may behave differently across engines. The lack of + specificity of WASIp1 means that there is no great oracle as to whether an + engine is right or wrong. Development of WASIp1 has ceased and the Component + Model is being worked on instead (e.g. WASIp2 and beyond) which is more + suitable for differential fuzzing. + +[non-determinism]: ./examples-deterministic-wasm-execution.md + +### Do Not Report the Same Bug Multiple Times + +Fuzzers will often trigger the same bug multiple times in multiple different +ways. Do not just file an issue for every single test case where the fuzzer +triggers an assertion failure. Many, or even most, of those test cases will +trigger the exact same assertion failure, but perhaps with a slightly different +stack trace. Spend some amount of effort deduplicating bugs before reporting +them. + +### Do Not Report Too Many Issues At Once + +Please do not clog up our issue tracker by filing dozens and dozens of bug +reports all at the same time. Choose a handful of the bugs your fuzzer has +discovered, prioritizing the ones that seem most serious, and file issues for +those bugs first. As those issues are resolved, then file a few more issues, and +so on. + +### Further Reading + +Here are some more helpful resources to help your external fuzzing efforts +succeed: + +* Blog post: [Responsible and Effective Bugfinding by John + Regehr](https://blog.regehr.org/archives/2037) + +## Wasmtime's Internal Fuzzing Infrastructure + +The Wasmtime project leverages extensive fuzzing for its safety and correctness +assurances, and therefore already has a fairly large amount of fuzzing +infrastructure. [Our fuzzers run continuously on +OSS-Fuzz.](https://github.com/google/oss-fuzz/tree/master/projects/wasmtime) + +### Test Case Generators and Oracles Test case generators and oracles live in the `wasmtime-fuzzing` crate, located in the `crates/fuzzing` directory. @@ -21,7 +175,7 @@ the two executions are observably identical. Our test case generators and oracles strive to be fuzzer-agnostic: they can be reused with libFuzzer or AFL or any other fuzzing engine or driver. -## libFuzzer and `cargo fuzz` Fuzz Targets +### libFuzzer and `cargo fuzz` Fuzz Targets We combine a test case generator and one more oracles into a *fuzz target*. Because the target needs to pipe the raw input from a fuzzer into the diff --git a/docs/contributing-reducing-test-cases.md b/docs/contributing-reducing-test-cases.md index ce81b1be35..ee029b9be5 100644 --- a/docs/contributing-reducing-test-cases.md +++ b/docs/contributing-reducing-test-cases.md @@ -20,6 +20,20 @@ another Wasm engine, the script can run both engines and compare their results. It is also often useful to `grep` through the candidate's WAT disassembly to make sure that relevant features and instructions are present. +Note that there are also a few other test-case reducers that can operate on +Wasm. All of them, including `wasm-shrink`, work fairly similarly at a high +level, but often if one reducer gets stuck in a local minimum, another reducer +can pick up from there and reduce the test case further due to differences in +the details of their implementations. Therefore, if you find that `wasm-shrink` +isn't very effective on a particular test case, you can try continuing reduction +with one of the following: + +* [Binaryen's `wasm-reduce` + tool](https://github.com/WebAssembly/binaryen?tab=readme-ov-file#tools) +* [`creduce`, which can be effective at reducing Wasm test cases when + disassembled into their `.wat` text + format](https://github.com/csmith-project/creduce) + ## Case Study: [Issue #7779](https://github.com/bytecodealliance/wasmtime/issues/7779) A bug was reported involving the `memory.init` instruction. The attached test diff --git a/docs/examples-deterministic-wasm-execution.md b/docs/examples-deterministic-wasm-execution.md index 2f7272c479..2e20ae1bdd 100644 --- a/docs/examples-deterministic-wasm-execution.md +++ b/docs/examples-deterministic-wasm-execution.md @@ -32,7 +32,7 @@ for more details. The relaxed SIMD proposal gives Wasm programs access to SIMD operations that cannot be made to execute both identically and performantly across different -architectures. The proposal gave up determinism across different achitectures in +architectures. The proposal gave up determinism across different architectures in order to maintain portable performance. At the cost of worse runtime performance, Wasmtime can deterministically execute diff --git a/examples/linking.rs b/examples/linking.rs index d547667018..ee13df98a2 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -2,8 +2,8 @@ // You can execute this example with `cargo run --example linking` -use wasi_common::sync::WasiCtxBuilder; use wasmtime::*; +use wasmtime_wasi::WasiCtx; fn main() -> Result<()> { let engine = Engine::default(); @@ -11,17 +11,14 @@ fn main() -> Result<()> { // First set up our linker which is going to be linking modules together. We // want our linker to have wasi available, so we set that up here as well. let mut linker = Linker::new(&engine); - wasi_common::sync::add_to_linker(&mut linker, |s| s)?; + wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |s| s)?; // Load and compile our two modules let linking1 = Module::from_file(&engine, "examples/linking1.wat")?; let linking2 = Module::from_file(&engine, "examples/linking2.wat")?; // Configure WASI and insert it into a `Store` - let wasi = WasiCtxBuilder::new() - .inherit_stdio() - .inherit_args()? - .build(); + let wasi = WasiCtx::builder().inherit_stdio().inherit_args().build_p1(); let mut store = Store::new(&engine, wasi); // Instantiate our first module which only uses WASI, then register that diff --git a/examples/resource-component/main.rs b/examples/resource-component/main.rs index 96aa5cf54f..2b6b73bd3f 100644 --- a/examples/resource-component/main.rs +++ b/examples/resource-component/main.rs @@ -13,7 +13,7 @@ use wasmtime::component::{Component, Linker, ResourceTable}; use wasmtime::component::{HasSelf, Resource}; use wasmtime::{Config, Engine, Result, Store}; use wasmtime_wasi::p2::add_to_linker_async; -use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView}; +use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; pub struct ComponentRunStates { // These two are required basically as a standard way to enable the impl of IoView and @@ -24,24 +24,22 @@ pub struct ComponentRunStates { // You can add other custom host states if needed } -impl IoView for ComponentRunStates { - fn table(&mut self) -> &mut ResourceTable { - &mut self.resource_table - } -} impl WasiView for ComponentRunStates { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi_ctx + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi_ctx, + table: &mut self.resource_table, + } } } impl ComponentRunStates { pub fn new() -> Self { // Create a WASI context and put it in a Store; all instances in the store - // share this context. `WasiCtxBuilder` provides a number of ways to + // share this context. `WasiCtx` provides a number of ways to // configure what the target program will have access to. ComponentRunStates { - wasi_ctx: WasiCtxBuilder::new().build(), + wasi_ctx: WasiCtx::builder().build(), resource_table: ResourceTable::new(), } } diff --git a/examples/tokio/main.rs b/examples/tokio/main.rs index 7e9d548c82..fa36624c84 100644 --- a/examples/tokio/main.rs +++ b/examples/tokio/main.rs @@ -2,10 +2,7 @@ use anyhow::Error; use std::sync::Arc; use tokio::time::Duration; use wasmtime::{Config, Engine, Linker, Module, Store}; -// For this example we want to use the async version of wasi_common. -// Notably, this version of wasi uses a scheduler that will async yield -// when sleeping in `poll_oneoff`. -use wasi_common::{WasiCtx, tokio::WasiCtxBuilder}; +use wasmtime_wasi::{WasiCtx, p1::WasiP1Ctx}; #[tokio::main] async fn main() -> Result<(), Error> { @@ -42,7 +39,7 @@ async fn main() -> Result<(), Error> { struct Environment { engine: Engine, module: Module, - linker: Arc>, + linker: Arc>, } impl Environment { @@ -61,7 +58,7 @@ impl Environment { // adds WASI functions to the linker, notably the async versions built // on tokio. let mut linker = Linker::new(&engine); - wasi_common::tokio::add_to_linker(&mut linker, |cx| cx)?; + wasmtime_wasi::p1::add_to_linker_async(&mut linker, |cx| cx)?; Ok(Self { engine, @@ -86,12 +83,12 @@ impl Inputs { } async fn run_wasm(inputs: Inputs) -> Result<(), Error> { - let wasi = WasiCtxBuilder::new() + let wasi = WasiCtx::builder() // Let wasi print to this process's stdout. .inherit_stdout() // Set an environment variable so the wasm knows its name. - .env("NAME", &inputs.name)? - .build(); + .env("NAME", &inputs.name) + .build_p1(); let mut store = Store::new(&inputs.env.engine, wasi); // Put effectively unlimited fuel so it can run forever. diff --git a/examples/wasip1-async/main.rs b/examples/wasip1-async/main.rs index 0f7495b71f..4be30ec1c2 100644 --- a/examples/wasip1-async/main.rs +++ b/examples/wasip1-async/main.rs @@ -9,8 +9,8 @@ You can execute this example with: use anyhow::Result; use wasmtime::{Config, Engine, Linker, Module, Store}; -use wasmtime_wasi::p2::WasiCtxBuilder; -use wasmtime_wasi::preview1::{self, WasiP1Ctx}; +use wasmtime_wasi::WasiCtx; +use wasmtime_wasi::p1::{self, WasiP1Ctx}; #[tokio::main] async fn main() -> Result<()> { @@ -19,15 +19,14 @@ async fn main() -> Result<()> { config.async_support(true); let engine = Engine::new(&config)?; - // Add the WASI preview1 API to the linker (will be implemented in terms of - // the preview2 API) + // Add the WASIp1 APIs to the linker let mut linker: Linker = Linker::new(&engine); - preview1::add_to_linker_async(&mut linker, |t| t)?; + p1::add_to_linker_async(&mut linker, |t| t)?; // Add capabilities (e.g. filesystem access) to the WASI preview2 context - // here. Here only stdio is inherited, but see docs of `WasiCtxBuilder` for + // here. Here only stdio is inherited, but see docs of `WasiCtx` for // more. - let wasi_ctx = WasiCtxBuilder::new().inherit_stdio().build_p1(); + let wasi_ctx = WasiCtx::builder().inherit_stdio().build_p1(); let mut store = Store::new(&engine, wasi_ctx); diff --git a/examples/wasip1/main.rs b/examples/wasip1/main.rs index 6bc01bca52..c9e381a985 100644 --- a/examples/wasip1/main.rs +++ b/examples/wasip1/main.rs @@ -6,22 +6,19 @@ You can execute this example with: cargo run --example wasip1 */ -use wasi_common::sync::WasiCtxBuilder; use wasmtime::*; +use wasmtime_wasi::WasiCtx; fn main() -> Result<()> { // Define the WASI functions globally on the `Config`. let engine = Engine::default(); let mut linker = Linker::new(&engine); - wasi_common::sync::add_to_linker(&mut linker, |s| s)?; + wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |s| s)?; // Create a WASI context and put it in a Store; all instances in the store // share this context. `WasiCtxBuilder` provides a number of ways to // configure what the target program will have access to. - let wasi = WasiCtxBuilder::new() - .inherit_stdio() - .inherit_args()? - .build(); + let wasi = WasiCtx::builder().inherit_stdio().inherit_args().build_p1(); let mut store = Store::new(&engine, wasi); // Instantiate our module with the imports we've created, and run it. diff --git a/examples/wasip2-async/main.rs b/examples/wasip2-async/main.rs index f7fa357883..db44473d55 100644 --- a/examples/wasip2-async/main.rs +++ b/examples/wasip2-async/main.rs @@ -10,7 +10,7 @@ You can execute this example with: use wasmtime::component::{Component, Linker, ResourceTable}; use wasmtime::*; use wasmtime_wasi::p2::bindings::Command; -use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView}; +use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; pub struct ComponentRunStates { // These two are required basically as a standard way to enable the impl of IoView and @@ -21,14 +21,12 @@ pub struct ComponentRunStates { // You can add other custom host states if needed } -impl IoView for ComponentRunStates { - fn table(&mut self) -> &mut ResourceTable { - &mut self.resource_table - } -} impl WasiView for ComponentRunStates { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi_ctx + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi_ctx, + table: &mut self.resource_table, + } } } @@ -42,9 +40,9 @@ async fn main() -> Result<()> { wasmtime_wasi::p2::add_to_linker_async(&mut linker)?; // Create a WASI context and put it in a Store; all instances in the store - // share this context. `WasiCtxBuilder` provides a number of ways to + // share this context. `WasiCtx` provides a number of ways to // configure what the target program will have access to. - let wasi = WasiCtxBuilder::new().inherit_stdio().inherit_args().build(); + let wasi = WasiCtx::builder().inherit_stdio().inherit_args().build(); let state = ComponentRunStates { wasi_ctx: wasi, resource_table: ResourceTable::new(), diff --git a/examples/wasip2/main.rs b/examples/wasip2/main.rs index 7b3fdc9c8d..06739b0103 100644 --- a/examples/wasip2/main.rs +++ b/examples/wasip2/main.rs @@ -9,7 +9,7 @@ You can execute this example with: use wasmtime::component::{Component, Linker, ResourceTable}; use wasmtime::*; use wasmtime_wasi::p2::bindings::sync::Command; -use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView}; +use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; pub struct ComponentRunStates { // These two are required basically as a standard way to enable the impl of IoView and @@ -20,14 +20,12 @@ pub struct ComponentRunStates { // You can add other custom host states if needed } -impl IoView for ComponentRunStates { - fn table(&mut self) -> &mut ResourceTable { - &mut self.resource_table - } -} impl WasiView for ComponentRunStates { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.wasi_ctx + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi_ctx, + table: &mut self.resource_table, + } } } @@ -38,9 +36,9 @@ fn main() -> Result<()> { wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?; // Create a WASI context and put it in a Store; all instances in the store - // share this context. `WasiCtxBuilder` provides a number of ways to + // share this context. `WasiCtx` provides a number of ways to // configure what the target program will have access to. - let wasi = WasiCtxBuilder::new().inherit_stdio().inherit_args().build(); + let wasi = WasiCtx::builder().inherit_stdio().inherit_args().build(); let state = ComponentRunStates { wasi_ctx: wasi, resource_table: ResourceTable::new(), @@ -57,7 +55,7 @@ fn main() -> Result<()> { // Alternatively, instead of using `Command`, just instantiate it as a normal component // New states - let wasi = WasiCtxBuilder::new().inherit_stdio().inherit_args().build(); + let wasi = WasiCtx::builder().inherit_stdio().inherit_args().build(); let state = ComponentRunStates { wasi_ctx: wasi, resource_table: ResourceTable::new(), diff --git a/pulley/src/interp/tail_loop.rs b/pulley/src/interp/tail_loop.rs index 6fb6a25c67..55c5b68430 100644 --- a/pulley/src/interp/tail_loop.rs +++ b/pulley/src/interp/tail_loop.rs @@ -7,9 +7,9 @@ //! tail-calls. //! //! At this time this module is more performant but disabled by default. Rust -//! does not have guaranteed tail call elimination at this time so this is not -//! a suitable means of writing an interpreter loop. That being said this is -//! included nonetheless for us to experiment and analyze with. +//! does not have guaranteed tail call elimination on stable at this time so +//! this is not a suitable means of writing an interpreter loop. That being said +//! this is included nonetheless for us to experiment and analyze with. //! //! There are two methods of using this module: //! @@ -23,8 +23,8 @@ //! * `RUSTFLAGS=--cfg=pulley_tail_calls` - this compilation flag indicates that //! Rust's nightly-only support for guaranteed tail calls should be used. This //! uses the `become` keyword, for example. At this time this feature of Rust -//! is highly experimental and not even complete. It only passes `cargo check` -//! at this time but doesn't actually run anywhere. +//! is highly experimental and may not be complete. This is only lightly +//! tested in CI. use super::*; use crate::ExtendedOpcode; diff --git a/src/commands/run.rs b/src/commands/run.rs index 44e1ea8bf5..a1b80d362d 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -6,7 +6,6 @@ )] use crate::common::{Profile, RunCommon, RunTarget}; - use anyhow::{Context as _, Error, Result, anyhow, bail}; use clap::Parser; use std::ffi::OsString; @@ -15,14 +14,7 @@ use std::sync::{Arc, Mutex}; use std::thread; use wasi_common::sync::{Dir, TcpListener, WasiCtxBuilder, ambient_authority}; use wasmtime::{Engine, Func, Module, Store, StoreLimits, Val, ValType}; -use wasmtime_wasi::p2::{self, IoView as _}; -use wasmtime_wasi::p3; - -#[cfg(feature = "wasi-nn")] -use wasmtime_wasi_nn::wit::WasiNnView; - -#[cfg(feature = "wasi-threads")] -use wasmtime_wasi_threads::WasiThreadsCtx; +use wasmtime_wasi::{WasiCtxView, WasiView}; #[cfg(feature = "wasi-config")] use wasmtime_wasi_config::{WasiConfig, WasiConfigVariables}; @@ -32,7 +24,10 @@ use wasmtime_wasi_http::{ }; #[cfg(feature = "wasi-keyvalue")] use wasmtime_wasi_keyvalue::{WasiKeyValue, WasiKeyValueCtx, WasiKeyValueCtxBuilder}; - +#[cfg(feature = "wasi-nn")] +use wasmtime_wasi_nn::wit::WasiNnView; +#[cfg(feature = "wasi-threads")] +use wasmtime_wasi_threads::WasiThreadsCtx; #[cfg(feature = "wasi-tls")] use wasmtime_wasi_tls::{WasiTls, WasiTlsCtx}; @@ -246,9 +241,9 @@ impl RunCommand { // Exit the process if Wasmtime understands the error; // otherwise, fall back on Rust's default error printing/return // code. - if store.data().preview1_ctx.is_some() { + if store.data().legacy_p1_ctx.is_some() { return Err(wasi_common::maybe_exit_on_error(e)); - } else if store.data().preview2_ctx.is_some() { + } else if store.data().wasip1_ctx.is_some() { if let Some(exit) = e.downcast_ref::() { std::process::exit(exit.0); } @@ -790,27 +785,22 @@ impl RunCommand { // implementation. (Some(false), _) | (None, Some(true)) => { wasi_common::tokio::add_to_linker(linker, |host| { - host.preview1_ctx.as_mut().unwrap() + host.legacy_p1_ctx.as_mut().unwrap() })?; - self.set_preview1_ctx(store)?; + self.set_legacy_p1_ctx(store)?; } // If preview2 was explicitly requested, always use it. // Otherwise use it so long as threads are disabled. // - // Note that for now `preview0` is currently + // Note that for now `p0` is currently // default-enabled but this may turn into // default-disabled in the future. (Some(true), _) | (None, Some(false) | None) => { if self.run.common.wasi.preview0 != Some(false) { - wasmtime_wasi::preview0::add_to_linker_async(linker, |t| { - t.preview2_ctx() - })?; + wasmtime_wasi::p0::add_to_linker_async(linker, |t| t.wasip1_ctx())?; } - wasmtime_wasi::preview1::add_to_linker_async(linker, |t| { - t.preview2_ctx() - })?; - self.set_preview2_ctx(store)?; - self.set_p3_ctx(store)?; + wasmtime_wasi::p1::add_to_linker_async(linker, |t| t.wasip1_ctx())?; + self.set_wasi_ctx(store)?; } } } @@ -820,8 +810,7 @@ impl RunCommand { wasmtime_wasi::p2::add_to_linker_with_options_async(linker, &link_options)?; wasmtime_wasi::p3::add_to_linker(linker) .context("failed to link `wasi:cli@0.3.x`")?; - self.set_preview2_ctx(store)?; - self.set_p3_ctx(store)?; + self.set_wasi_ctx(store)?; } } } @@ -847,15 +836,14 @@ impl RunCommand { #[cfg(feature = "component-model")] CliLinker::Component(linker) => { wasmtime_wasi_nn::wit::add_to_linker(linker, |h: &mut Host| { - let preview2_ctx = - h.preview2_ctx.as_mut().expect("wasip2 is not configured"); - let preview2_ctx = Arc::get_mut(preview2_ctx) + let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured"); + let ctx = Arc::get_mut(ctx) .expect("wasmtime_wasi is not compatible with threads") .get_mut() .unwrap(); let nn_ctx = Arc::get_mut(h.wasi_nn_wit.as_mut().unwrap()) .expect("wasi-nn is not implemented with multi-threading support"); - WasiNnView::new(preview2_ctx.table(), nn_ctx) + WasiNnView::new(ctx.ctx().table, nn_ctx) })?; store.data_mut().wasi_nn_wit = Some(Arc::new( wasmtime_wasi_nn::wit::WasiNnCtx::new(backends, registry), @@ -923,13 +911,11 @@ impl RunCommand { .build(); wasmtime_wasi_keyvalue::add_to_linker(linker, |h| { - let preview2_ctx = - h.preview2_ctx.as_mut().expect("wasip2 is not configured"); - let preview2_ctx = - Arc::get_mut(preview2_ctx).unwrap().get_mut().unwrap(); + let ctx = h.wasip1_ctx.as_mut().expect("wasip2 is not configured"); + let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap(); WasiKeyValue::new( Arc::get_mut(h.wasi_keyvalue.as_mut().unwrap()).unwrap(), - preview2_ctx.table(), + ctx.ctx().table, ) })?; store.data_mut().wasi_keyvalue = Some(Arc::new(ctx)); @@ -1003,13 +989,11 @@ impl RunCommand { let mut opts = wasmtime_wasi_tls::LinkOptions::default(); opts.tls(true); wasmtime_wasi_tls::add_to_linker(linker, &mut opts, |h| { - let preview2_ctx = - h.preview2_ctx.as_mut().expect("wasip2 is not configured"); - let preview2_ctx = - Arc::get_mut(preview2_ctx).unwrap().get_mut().unwrap(); + let ctx = h.wasip1_ctx.as_mut().expect("wasi is not configured"); + let ctx = Arc::get_mut(ctx).unwrap().get_mut().unwrap(); WasiTls::new( Arc::get_mut(h.wasi_tls.as_mut().unwrap()).unwrap(), - preview2_ctx.table(), + ctx.ctx().table, ) })?; @@ -1023,7 +1007,7 @@ impl RunCommand { Ok(()) } - fn set_preview1_ctx(&self, store: &mut Store) -> Result<()> { + fn set_legacy_p1_ctx(&self, store: &mut Store) -> Result<()> { let mut builder = WasiCtxBuilder::new(); builder.inherit_stdio().args(&self.compute_argv()?)?; @@ -1066,25 +1050,23 @@ impl RunCommand { builder.preopened_dir(dir, guest)?; } - store.data_mut().preview1_ctx = Some(builder.build()); + store.data_mut().legacy_p1_ctx = Some(builder.build()); Ok(()) } - fn set_preview2_ctx(&self, store: &mut Store) -> Result<()> { - let mut builder = wasmtime_wasi::p2::WasiCtxBuilder::new(); + /// Note the naming here is subtle, but this is effectively setting up a + /// `wasmtime_wasi::WasiCtx` structure. + /// + /// This is stored in `Host` as `WasiP1Ctx` which internally contains the + /// `WasiCtx` and `ResourceTable` used for WASI implementations. Exactly + /// which "p" for WASIpN is more a reference to + /// `wasmtime-wasi`-vs-`wasi-common` here more than anything else. + fn set_wasi_ctx(&self, store: &mut Store) -> Result<()> { + let mut builder = wasmtime_wasi::WasiCtxBuilder::new(); builder.inherit_stdio().args(&self.compute_argv()?); self.run.configure_wasip2(&mut builder)?; let ctx = builder.build_p1(); - store.data_mut().preview2_ctx = Some(Arc::new(Mutex::new(ctx))); - Ok(()) - } - - fn set_p3_ctx(&self, store: &mut Store) -> Result<()> { - let mut builder = wasmtime_wasi::p3::WasiCtxBuilder::new(); - builder.inherit_stdio().args(&self.compute_argv()?); - self.run.configure_wasip3(&mut builder)?; - store.data_mut().p3_ctx = Some(Arc::new(Mutex::new(builder.build()))); - + store.data_mut().wasip1_ctx = Some(Arc::new(Mutex::new(ctx))); Ok(()) } @@ -1104,17 +1086,36 @@ impl RunCommand { } } +/// The `T` in `Store` for what the CLI is running. +/// +/// This structures has a number of contexts used for various WASI proposals. +/// Note that all of them are optional meaning that they're `None` by default +/// and enabled with various CLI flags (some CLI flags are on-by-default). Note +/// additionally that this structure is `Clone` to implement the `wasi-threads` +/// proposal. Many WASI proposals are not compatible with `wasi-threads` so to +/// model this `Arc` and `Arc>` is used for many configurations. If a +/// WASI proposal is inherently threadsafe it's protected with just an `Arc` to +/// share its configuration across many threads. +/// +/// If mutation is required then `Mutex` is used. Note though that the mutex is +/// not actually locked as access always goes through `Arc::get_mut` which +/// effectively asserts that there's only one thread. In short much of this is +/// not compatible with `wasi-threads`. #[derive(Default, Clone)] struct Host { - preview1_ctx: Option, - + // Legacy wasip1 context using `wasi_common`, not set unless opted-in-to + // with the CLI. + legacy_p1_ctx: Option, + + // Context for both WASIp1 and WASIp2 (and beyond) for the `wasmtime_wasi` + // crate. This has both `wasmtime_wasi::WasiCtx` as well as a + // `ResourceTable` internally to be used. + // // The Mutex is only needed to satisfy the Sync constraint but we never // actually perform any locking on it as we use Mutex::get_mut for every // access. - preview2_ctx: Option>>, + wasip1_ctx: Option>>, - p3_filesystem: Option, - p3_ctx: Option>>, #[cfg(feature = "wasi-http")] p3_http: Option, @@ -1144,11 +1145,8 @@ struct Host { } impl Host { - fn preview2_ctx(&mut self) -> &mut wasmtime_wasi::preview1::WasiP1Ctx { - let ctx = self - .preview2_ctx - .as_mut() - .expect("wasip2 is not configured"); + fn wasip1_ctx(&mut self) -> &mut wasmtime_wasi::p1::WasiP1Ctx { + let ctx = self.wasip1_ctx.as_mut().expect("wasi is not configured"); Arc::get_mut(ctx) .expect("wasmtime_wasi is not compatible with threads") .get_mut() @@ -1156,51 +1154,9 @@ impl Host { } } -impl p2::IoView for Host { - fn table(&mut self) -> &mut wasmtime::component::ResourceTable { - self.preview2_ctx().table() - } -} -impl p2::WasiView for Host { - fn ctx(&mut self) -> &mut p2::WasiCtx { - self.preview2_ctx().ctx() - } -} -impl p3::WasiView for Host { - fn ctx(&mut self) -> p3::WasiCtxView<'_> { - p3::WasiCtxView { - ctx: { - let ctx = self.p3_ctx.as_mut().expect("wasip3 is not configured"); - Arc::get_mut(ctx) - .expect("wasmtime_wasi is not compatible with threads") - .get_mut() - .unwrap() - }, - table: { - let ctx = self - .preview2_ctx - .as_mut() - .expect("wasip2 is not configured"); - Arc::get_mut(ctx) - .expect("wasmtime_wasi is not compatible with threads") - .get_mut() - .unwrap() - .table() - }, - } - } -} - -impl p3::ResourceView for Host { - fn table(&mut self) -> &mut wasmtime::component::ResourceTable { - self.preview2_ctx().table() - } -} -impl p3::filesystem::WasiFilesystemView for Host { - fn filesystem(&self) -> &wasmtime_wasi::p3::filesystem::WasiFilesystemCtx { - self.p3_filesystem - .as_ref() - .expect("`wasi:filesystem@0.3` not configured") +impl WasiView for Host { + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiView::ctx(self.wasip1_ctx()) } } @@ -1211,6 +1167,10 @@ impl wasmtime_wasi_http::types::WasiHttpView for Host { Arc::get_mut(ctx).expect("wasmtime_wasi is not compatible with threads") } + fn table(&mut self) -> &mut wasmtime::component::ResourceTable { + WasiView::ctx(self).table + } + fn outgoing_body_buffer_chunks(&mut self) -> usize { self.wasi_http_outgoing_body_buffer_chunks .unwrap_or_else(|| DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS) @@ -1222,6 +1182,13 @@ impl wasmtime_wasi_http::types::WasiHttpView for Host { } } +#[cfg(feature = "wasi-http")] +impl wasmtime_wasi::p3::ResourceView for Host { + fn table(&mut self) -> &mut wasmtime::component::ResourceTable { + self.ctx().table + } +} + #[cfg(feature = "wasi-http")] impl wasmtime_wasi_http::p3::WasiHttpView for Host { type Client = wasmtime_wasi_http::p3::DefaultClient; diff --git a/src/commands/serve.rs b/src/commands/serve.rs index 15a707add7..185a77a24d 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -1,5 +1,5 @@ use crate::common::{Profile, RunCommon, RunTarget}; -use anyhow::{Result, anyhow, bail}; +use anyhow::{Result, bail}; use bytes::Bytes; use clap::Parser; use http::{Response, StatusCode}; @@ -7,15 +7,23 @@ use http_body_util::BodyExt as _; use http_body_util::combinators::BoxBody; use std::convert::Infallible; use std::net::SocketAddr; -use std::path::PathBuf; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant}; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::Instant; +use std::{ + path::PathBuf, + sync::{ + Arc, Mutex, + atomic::{AtomicBool, AtomicU64, Ordering}, + }, + time::Duration, +}; +use tokio::io::{self, AsyncWrite}; use tokio::sync::Notify; -use wasmtime::component::{Component, Instance, InstancePre, Linker}; +use wasmtime::component::{Component, Instance, InstancePre, Linker, ResourceTable}; use wasmtime::{Engine, Store, StoreLimits, UpdateDeadline}; -use wasmtime_wasi::p2::{IoView, StreamError, StreamResult, WasiCtx, WasiCtxBuilder, WasiView}; -use wasmtime_wasi::p3; +use wasmtime_wasi::p2::{StreamError, StreamResult}; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; use wasmtime_wasi_http::bindings as p2; use wasmtime_wasi_http::io::TokioIo; use wasmtime_wasi_http::{ @@ -37,9 +45,7 @@ struct Host { http_outgoing_body_buffer_chunks: Option, http_outgoing_body_chunk_size: Option, - p3_filesystem: Option, p3_http: wasmtime_wasi_http::p3::WasiHttpCtx, - p3_ctx: wasmtime_wasi::p3::WasiCtx, limits: StoreLimits, @@ -56,14 +62,12 @@ struct Host { guest_profiler: Option>, } -impl IoView for Host { - fn table(&mut self) -> &mut wasmtime::component::ResourceTable { - &mut self.table - } -} impl WasiView for Host { - fn ctx(&mut self) -> &mut WasiCtx { - &mut self.ctx + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.ctx, + table: &mut self.table, + } } } @@ -71,6 +75,9 @@ impl WasiHttpView for Host { fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http } + fn table(&mut self) -> &mut ResourceTable { + &mut self.table + } fn outgoing_body_buffer_chunks(&mut self) -> usize { self.http_outgoing_body_buffer_chunks @@ -89,23 +96,6 @@ impl wasmtime_wasi::p3::ResourceView for Host { } } -impl wasmtime_wasi::p3::WasiView for Host { - fn ctx(&mut self) -> wasmtime_wasi::p3::WasiCtxView<'_> { - wasmtime_wasi::p3::WasiCtxView { - ctx: &mut self.p3_ctx, - table: &mut self.table, - } - } -} - -impl wasmtime_wasi::p3::filesystem::WasiFilesystemView for Host { - fn filesystem(&self) -> &wasmtime_wasi::p3::filesystem::WasiFilesystemCtx { - self.p3_filesystem - .as_ref() - .expect("`wasi:filesystem@0.3` not configured") - } -} - impl wasmtime_wasi_http::p3::WasiHttpView for Host { type Client = wasmtime_wasi_http::p3::DefaultClient; @@ -186,12 +176,9 @@ impl ServeCommand { fn new_store(&self, engine: &Engine, req_id: u64) -> Result> { let mut p2 = WasiCtxBuilder::new(); - let mut p3 = p3::WasiCtxBuilder::new(); self.run.configure_wasip2(&mut p2)?; - self.run.configure_wasip3(&mut p3)?; p2.env("REQUEST_ID", req_id.to_string()); - p3.env("REQUEST_ID", req_id.to_string()); let stdout_prefix: String; let stderr_prefix: String; @@ -205,9 +192,6 @@ impl ServeCommand { p2.stdout(LogStream::new(stdout_prefix, Output::Stdout)); p2.stderr(LogStream::new(stderr_prefix, Output::Stderr)); - // TODO: set stdout/stderr for p3 - p3.inherit_stdout().inherit_stderr(); - let mut host = Host { table: wasmtime::component::ResourceTable::new(), ctx: p2.build(), @@ -226,21 +210,6 @@ impl ServeCommand { #[cfg(feature = "profiling")] guest_profiler: None, - p3_ctx: p3.build(), - p3_filesystem: { - let mut p3_filesystem = p3::filesystem::WasiFilesystemCtx::default(); - p3_filesystem.allow_blocking_current_thread = - self.run.common.wasm.timeout.is_none(); - for (host, guest) in self.run.dirs.iter() { - p3_filesystem.preopened_dir( - host, - guest, - wasmtime_wasi::DirPerms::all(), - wasmtime_wasi::FilePerms::all(), - )?; - } - Some(p3_filesystem) - }, p3_http: wasmtime_wasi_http::p3::WasiHttpCtx::default(), }; @@ -941,14 +910,13 @@ enum Output { } impl Output { - fn write_all(&self, buf: &[u8]) -> anyhow::Result<()> { + fn write_all(&self, buf: &[u8]) -> io::Result<()> { use std::io::Write; match self { Output::Stdout => std::io::stdout().write_all(buf), Output::Stderr => std::io::stderr().write_all(buf), } - .map_err(|e| anyhow!(e)) } } @@ -973,36 +941,15 @@ impl LogStream { }), } } -} - -impl wasmtime_wasi::p2::StdoutStream for LogStream { - fn stream(&self) -> Box { - Box::new(self.clone()) - } -} - -impl wasmtime_wasi::cli::IsTerminal for LogStream { - fn is_terminal(&self) -> bool { - match &self.output { - Output::Stdout => std::io::stdout().is_terminal(), - Output::Stderr => std::io::stderr().is_terminal(), - } - } -} - -impl wasmtime_wasi::p2::OutputStream for LogStream { - fn write(&mut self, bytes: bytes::Bytes) -> StreamResult<()> { - let mut bytes = &bytes[..]; + fn write_all(&mut self, mut bytes: &[u8]) -> io::Result<()> { while !bytes.is_empty() { if self .state .needs_prefix_on_next_write .load(Ordering::Relaxed) { - self.output - .write_all(self.state.prefix.as_bytes()) - .map_err(StreamError::LastOperationFailed)?; + self.output.write_all(self.state.prefix.as_bytes())?; self.state .needs_prefix_on_next_write .store(false, Ordering::Relaxed); @@ -1011,17 +958,13 @@ impl wasmtime_wasi::p2::OutputStream for LogStream { Some(i) => { let (a, b) = bytes.split_at(i + 1); bytes = b; - self.output - .write_all(a) - .map_err(StreamError::LastOperationFailed)?; + self.output.write_all(a)?; self.state .needs_prefix_on_next_write .store(true, Ordering::Relaxed); } None => { - self.output - .write_all(bytes) - .map_err(StreamError::LastOperationFailed)?; + self.output.write_all(bytes)?; break; } } @@ -1029,6 +972,32 @@ impl wasmtime_wasi::p2::OutputStream for LogStream { Ok(()) } +} + +impl wasmtime_wasi::cli::StdoutStream for LogStream { + fn p2_stream(&self) -> Box { + Box::new(self.clone()) + } + fn async_stream(&self) -> Box { + Box::new(self.clone()) + } +} + +impl wasmtime_wasi::cli::IsTerminal for LogStream { + fn is_terminal(&self) -> bool { + match &self.output { + Output::Stdout => std::io::stdout().is_terminal(), + Output::Stderr => std::io::stderr().is_terminal(), + } + } +} + +impl wasmtime_wasi::p2::OutputStream for LogStream { + fn write(&mut self, bytes: bytes::Bytes) -> StreamResult<()> { + self.write_all(&bytes) + .map_err(|e| StreamError::LastOperationFailed(e.into()))?; + Ok(()) + } fn flush(&mut self) -> StreamResult<()> { Ok(()) @@ -1044,6 +1013,22 @@ impl wasmtime_wasi::p2::Pollable for LogStream { async fn ready(&mut self) {} } +impl AsyncWrite for LogStream { + fn poll_write( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Poll::Ready(self.write_all(buf).map(|_| buf.len())) + } + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + /// The pooling allocator is tailor made for the `wasmtime serve` use case, so /// try to use it when we can. The main cost of the pooling allocator, however, /// is the virtual memory required to run it. Not all systems support the same diff --git a/src/common.rs b/src/common.rs index 52c6b9cea2..0a0fd04e25 100644 --- a/src/common.rs +++ b/src/common.rs @@ -6,8 +6,8 @@ use std::net::TcpListener; use std::{fs::File, path::Path, time::Duration}; use wasmtime::{Engine, Module, Precompiled, StoreLimits, StoreLimitsBuilder}; use wasmtime_cli_flags::{CommonOptions, opt::WasmtimeOptionValue}; +use wasmtime_wasi::WasiCtxBuilder; use wasmtime_wasi::p2::bindings::LinkOptions; -use wasmtime_wasi::{p2, p3}; #[cfg(feature = "component-model")] use wasmtime::component::Component; @@ -258,71 +258,7 @@ impl RunCommon { }) } - pub fn configure_wasip2(&self, builder: &mut p2::WasiCtxBuilder) -> Result<()> { - // It's ok to block the current thread since we're the only thread in - // the program as the CLI. This helps improve the performance of some - // blocking operations in WASI, for example, by skipping the - // back-and-forth between sync and async. - // - // However, do not set this if a timeout is configured, as that would - // cause the timeout to be ignored if the guest does, for example, - // something like `sleep(FOREVER)`. - builder.allow_blocking_current_thread(self.common.wasm.timeout.is_none()); - - if self.common.wasi.inherit_env == Some(true) { - for (k, v) in std::env::vars() { - builder.env(&k, &v); - } - } - for (key, value) in self.vars.iter() { - let value = match value { - Some(value) => value.clone(), - None => match std::env::var_os(key) { - Some(val) => val - .into_string() - .map_err(|_| anyhow!("environment variable `{key}` not valid utf-8"))?, - None => { - // leave the env var un-set in the guest - continue; - } - }, - }; - builder.env(key, &value); - } - - for (host, guest) in self.dirs.iter() { - builder.preopened_dir( - host, - guest, - wasmtime_wasi::DirPerms::all(), - wasmtime_wasi::FilePerms::all(), - )?; - } - - if self.common.wasi.listenfd == Some(true) { - bail!("components do not support --listenfd"); - } - for _ in self.compute_preopen_sockets()? { - bail!("components do not support --tcplisten"); - } - - if self.common.wasi.inherit_network == Some(true) { - builder.inherit_network(); - } - if let Some(enable) = self.common.wasi.allow_ip_name_lookup { - builder.allow_ip_name_lookup(enable); - } - if let Some(enable) = self.common.wasi.tcp { - builder.allow_tcp(enable); - } - if let Some(enable) = self.common.wasi.udp { - builder.allow_udp(enable); - } - - Ok(()) - } - - pub fn configure_wasip3(&self, builder: &mut p3::WasiCtxBuilder) -> Result<()> { + pub fn configure_wasip2(&self, builder: &mut WasiCtxBuilder) -> Result<()> { // It's ok to block the current thread since we're the only thread in // the program as the CLI. This helps improve the performance of some // blocking operations in WASI, for example, by skipping the diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 0d162f6865..1fc10c7eac 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -13,6 +13,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-assembler-x64]] version = "0.121.0" audited_as = "0.120.0" @@ -25,6 +29,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-assembler-x64]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-assembler-x64-meta]] version = "0.121.0" audited_as = "0.120.0" @@ -37,6 +45,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-assembler-x64-meta]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-bforest]] version = "0.121.0" audited_as = "0.120.0" @@ -49,6 +61,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-bforest]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-bitset]] version = "0.121.0" audited_as = "0.120.0" @@ -61,6 +77,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-bitset]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-codegen]] version = "0.121.0" audited_as = "0.120.0" @@ -73,6 +93,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-codegen]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-codegen-meta]] version = "0.121.0" audited_as = "0.120.0" @@ -85,6 +109,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-codegen-meta]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-codegen-shared]] version = "0.121.0" audited_as = "0.120.0" @@ -97,6 +125,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-codegen-shared]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-control]] version = "0.121.0" audited_as = "0.120.0" @@ -109,6 +141,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-control]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-entity]] version = "0.121.0" audited_as = "0.120.0" @@ -121,6 +157,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-entity]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-frontend]] version = "0.121.0" audited_as = "0.120.0" @@ -133,6 +173,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-frontend]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-interpreter]] version = "0.121.0" audited_as = "0.120.0" @@ -145,6 +189,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-interpreter]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-isle]] version = "0.121.0" audited_as = "0.120.0" @@ -157,6 +205,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-isle]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-jit]] version = "0.121.0" audited_as = "0.120.0" @@ -169,6 +221,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-jit]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-module]] version = "0.121.0" audited_as = "0.120.0" @@ -181,6 +237,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-module]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-native]] version = "0.121.0" audited_as = "0.120.0" @@ -193,6 +253,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-native]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-object]] version = "0.121.0" audited_as = "0.120.0" @@ -205,6 +269,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-object]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-reader]] version = "0.121.0" audited_as = "0.120.0" @@ -217,6 +285,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-reader]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-serde]] version = "0.121.0" audited_as = "0.120.0" @@ -229,6 +301,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-serde]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.cranelift-srcgen]] version = "0.121.0" audited_as = "0.120.0" @@ -241,6 +317,10 @@ audited_as = "0.120.0" version = "0.123.0" audited_as = "0.121.1" +[[unpublished.cranelift-srcgen]] +version = "0.124.0" +audited_as = "0.122.0" + [[unpublished.pulley-interpreter]] version = "34.0.0" audited_as = "33.0.0" @@ -253,6 +333,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.pulley-interpreter]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.pulley-macros]] version = "35.0.0" audited_as = "34.0.0" @@ -261,6 +345,10 @@ audited_as = "34.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.pulley-macros]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasi-common]] version = "34.0.0" audited_as = "33.0.0" @@ -273,6 +361,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasi-common]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime]] version = "34.0.0" audited_as = "33.0.0" @@ -285,6 +377,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-asm-macros]] version = "34.0.0" audited_as = "33.0.0" @@ -313,6 +409,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-cli]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-cli-flags]] version = "34.0.0" audited_as = "33.0.0" @@ -325,6 +425,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-cli-flags]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-component-macro]] version = "34.0.0" audited_as = "33.0.0" @@ -361,6 +465,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-environ]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-explorer]] version = "34.0.0" audited_as = "33.0.0" @@ -381,70 +489,138 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-asm-macros]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-c-api-macros]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-c-api-macros]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-cache]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-cache]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-component-macro]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-component-macro]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-component-util]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-component-util]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-cranelift]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-cranelift]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-explorer]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-explorer]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-fiber]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-fiber]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-jit-debug]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-jit-debug]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-jit-icache-coherence]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-jit-icache-coherence]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-math]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-math]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-slab]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-slab]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-unwinder]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-unwinder]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-versioned-export-macros]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-versioned-export-macros]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-winch]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-winch]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-wit-bindgen]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-wit-bindgen]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-internal-wmemcheck]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-internal-wmemcheck]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-jit-debug]] version = "34.0.0" audited_as = "33.0.0" @@ -489,6 +665,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-wasi]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-wasi-config]] version = "34.0.0" audited_as = "33.0.0" @@ -501,6 +681,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-wasi-config]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-wasi-http]] version = "34.0.0" audited_as = "33.0.0" @@ -513,6 +697,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-wasi-http]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-wasi-io]] version = "34.0.0" audited_as = "33.0.0" @@ -525,6 +713,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-wasi-io]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-wasi-keyvalue]] version = "34.0.0" audited_as = "33.0.0" @@ -537,6 +729,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-wasi-keyvalue]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-wasi-nn]] version = "34.0.0" audited_as = "33.0.0" @@ -549,6 +745,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-wasi-nn]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-wasi-threads]] version = "34.0.0" audited_as = "33.0.0" @@ -561,6 +761,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-wasi-threads]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-wasi-tls]] version = "34.0.0" audited_as = "33.0.0" @@ -573,10 +777,18 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-wasi-tls]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-wasi-tls-nativetls]] version = "36.0.0" audited_as = "35.0.0" +[[unpublished.wasmtime-wasi-tls-nativetls]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-wast]] version = "34.0.0" audited_as = "33.0.0" @@ -589,6 +801,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wasmtime-wast]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wasmtime-winch]] version = "34.0.0" audited_as = "33.0.0" @@ -625,6 +841,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wiggle]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wiggle-generate]] version = "34.0.0" audited_as = "33.0.0" @@ -637,6 +857,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wiggle-generate]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wiggle-macro]] version = "34.0.0" audited_as = "33.0.0" @@ -649,6 +873,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.wiggle-macro]] +version = "37.0.0" +audited_as = "35.0.0" + [[unpublished.wiggle-test]] version = "0.0.0" audited_as = "0.1.0" @@ -665,6 +893,10 @@ audited_as = "33.0.0" version = "36.0.0" audited_as = "34.0.1" +[[unpublished.winch-codegen]] +version = "37.0.0" +audited_as = "35.0.0" + [[publisher.aho-corasick]] version = "1.0.2" when = "2023-06-04" @@ -868,122 +1100,122 @@ user-login = "jrmuizel" user-name = "Jeff Muizelaar" [[publisher.cranelift]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-assembler-x64]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-assembler-x64-meta]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-bforest]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-bitset]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-codegen]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-codegen-meta]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-codegen-shared]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-control]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-entity]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-frontend]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-interpreter]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-isle]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-jit]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-module]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-native]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-object]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-reader]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-serde]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.cranelift-srcgen]] -version = "0.121.1" -when = "2025-06-24" +version = "0.122.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" @@ -1197,14 +1429,14 @@ user-login = "dtolnay" user-name = "David Tolnay" [[publisher.pulley-interpreter]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.pulley-macros]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" @@ -1475,8 +1707,8 @@ user-login = "sunfishcode" user-name = "Dan Gohman" [[publisher.wasi-common]] -version = "34.0.1" -when = "2025-06-24" +version = "33.0.0" +when = "2025-05-20" user-id = 73222 user-login = "wasmtime-publish" @@ -1570,26 +1802,26 @@ user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime]] -version = "34.0.1" -when = "2025-06-24" +version = "33.0.0" +when = "2025-05-20" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-cli]] -version = "34.0.1" -when = "2025-06-24" +version = "33.0.0" +when = "2025-05-20" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-cli-flags]] -version = "34.0.1" -when = "2025-06-24" +version = "33.0.0" +when = "2025-05-20" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-environ]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" @@ -1701,50 +1933,50 @@ user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-wasi]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-wasi-config]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-wasi-http]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-wasi-io]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-wasi-keyvalue]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-wasi-nn]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-wasi-threads]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wasmtime-wasi-tls]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" @@ -1756,8 +1988,8 @@ user-login = "alexcrichton" user-name = "Alex Crichton" [[publisher.wasmtime-wast]] -version = "34.0.1" -when = "2025-06-24" +version = "33.0.0" +when = "2025-05-20" user-id = 73222 user-login = "wasmtime-publish" @@ -1781,20 +2013,20 @@ user-login = "alexcrichton" user-name = "Alex Crichton" [[publisher.wiggle]] -version = "34.0.1" -when = "2025-06-24" +version = "33.0.0" +when = "2025-05-20" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wiggle-generate]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" [[publisher.wiggle-macro]] -version = "34.0.1" -when = "2025-06-24" +version = "35.0.0" +when = "2025-07-22" user-id = 73222 user-login = "wasmtime-publish" @@ -1813,8 +2045,8 @@ user-login = "BurntSushi" user-name = "Andrew Gallant" [[publisher.winch-codegen]] -version = "34.0.1" -when = "2025-06-24" +version = "33.0.0" +when = "2025-05-20" user-id = 73222 user-login = "wasmtime-publish" diff --git a/tests/all/code_too_large.rs b/tests/all/code_too_large.rs index e0093f907e..fac1ac6e12 100644 --- a/tests/all/code_too_large.rs +++ b/tests/all/code_too_large.rs @@ -5,6 +5,11 @@ use wasmtime::*; #[test] fn code_too_large_without_panic() -> Result<()> { + // This takes 1m+ in ASAN and isn't too useful to test in ASAN. + if cfg!(asan) { + return Ok(()); + } + const N: usize = 80000; // Build a module with a function whose body will allocate too many diff --git a/tests/all/debug/lldb.rs b/tests/all/debug/lldb.rs index 28abbf0674..d64387fe26 100644 --- a/tests/all/debug/lldb.rs +++ b/tests/all/debug/lldb.rs @@ -461,6 +461,7 @@ r fr v c fr v +breakpoint disable 2 c"#, )?; diff --git a/tests/all/host_funcs.rs b/tests/all/host_funcs.rs index 54d629fe5e..8aae8499b0 100644 --- a/tests/all/host_funcs.rs +++ b/tests/all/host_funcs.rs @@ -739,7 +739,7 @@ fn store_with_context() -> Result<()> { fn wasi_imports() -> Result<()> { let engine = Engine::default(); let mut linker = Linker::new(&engine); - wasmtime_wasi::preview1::add_to_linker_sync(&mut linker, |t| t)?; + wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |t| t)?; let wasm = wat::parse_str( r#" @@ -752,7 +752,7 @@ fn wasi_imports() -> Result<()> { )?; let module = Module::new(&engine, wasm)?; - let mut store = Store::new(&engine, wasmtime_wasi::p2::WasiCtxBuilder::new().build_p1()); + let mut store = Store::new(&engine, wasmtime_wasi::WasiCtxBuilder::new().build_p1()); let instance = linker.instantiate(&mut store, &module)?; let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?; diff --git a/tests/all/stack_overflow.rs b/tests/all/stack_overflow.rs index 48a0fdbdf3..40296916c4 100644 --- a/tests/all/stack_overflow.rs +++ b/tests/all/stack_overflow.rs @@ -122,6 +122,11 @@ fn host_always_has_some_stack() -> Result<()> { #[wasmtime_test] fn big_stack_works_ok(config: &mut Config) -> Result<()> { + // This test takes 1m+ in ASAN and isn't too useful, so prune it. + if cfg!(asan) { + return Ok(()); + } + const N: usize = 10000; // Build a module with a function that uses a very large amount of stack space, diff --git a/tests/all/table.rs b/tests/all/table.rs index 8b0781f63f..b4375c479a 100644 --- a/tests/all/table.rs +++ b/tests/all/table.rs @@ -366,3 +366,23 @@ fn host_table_keep_type_registration() -> Result<()> { Ok(()) } + +#[test] +fn gc_store_with_table_initializers() -> Result<()> { + let mut config = Config::new(); + config.wasm_gc(true); + config.wasm_function_references(true); + let engine = Engine::new(&config)?; + + let test = |wat: &str| -> Result<()> { + let module = Module::new(&engine, wat)?; + Instance::new(&mut Store::new(&engine, ()), &module, &[])?; + Ok(()) + }; + + test("(module (table 1 i31ref))")?; + test("(module (table 1 i31ref (ref.i31 (i32.const 1))))")?; + test("(module (table 1 i31ref (ref.null i31)))")?; + + Ok(()) +} diff --git a/tests/all/traps.rs b/tests/all/traps.rs index a7e7d621c4..844cbfcafc 100644 --- a/tests/all/traps.rs +++ b/tests/all/traps.rs @@ -734,8 +734,8 @@ fn parse_dwarf_info() -> Result<()> { let engine = Engine::new(&config)?; let module = Module::new(&engine, &wasm)?; let mut linker = Linker::new(&engine); - wasmtime_wasi::preview1::add_to_linker_sync(&mut linker, |t| t)?; - let mut store = Store::new(&engine, wasmtime_wasi::p2::WasiCtxBuilder::new().build_p1()); + wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |t| t)?; + let mut store = Store::new(&engine, wasmtime_wasi::WasiCtxBuilder::new().build_p1()); linker.module(&mut store, "", &module)?; let run = linker.get_default(&mut store, "")?; let trap = run.call(&mut store, &[], &mut []).unwrap_err(); diff --git a/tests/disas.rs b/tests/disas.rs index c624881afe..8c75058a1d 100644 --- a/tests/disas.rs +++ b/tests/disas.rs @@ -54,7 +54,7 @@ use wasmtime::{Engine, OptLevel, Strategy}; use wasmtime_cli_flags::CommonOptions; fn main() -> Result<()> { - if cfg!(miri) { + if cfg!(miri) || cfg!(asan) { return Ok(()); } diff --git a/tests/disas/call-indirect.wat b/tests/disas/call-indirect.wat new file mode 100644 index 0000000000..794e815cc4 --- /dev/null +++ b/tests/disas/call-indirect.wat @@ -0,0 +1,57 @@ +;;! target = "x86_64" + +(module + (table (export "t") 0 100 funcref) + (func (export "f") (param i32 i32) (result i32) + (call_indirect (param i32) (result i32) (local.get 0) (local.get 1)) + ) +) + +;; function u0:0(i64 vmctx, i64, i32, i32) -> i32 tail { +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly gv0+8 +;; gv2 = load.i64 notrap aligned gv1+16 +;; gv3 = vmctx +;; gv4 = load.i64 notrap aligned gv3+48 +;; gv5 = load.i64 notrap aligned gv3+56 +;; sig0 = (i64 vmctx, i64, i32) -> i32 tail +;; sig1 = (i64 vmctx, i32, i64) -> i64 tail +;; fn0 = colocated u1:9 sig1 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32, v3: i32): +;; @0035 v5 = load.i64 notrap aligned v0+56 +;; @0035 v6 = ireduce.i32 v5 +;; @0035 v7 = icmp uge v3, v6 +;; @0035 v8 = uextend.i64 v3 +;; @0035 v9 = load.i64 notrap aligned v0+48 +;; v30 = iconst.i64 3 +;; @0035 v10 = ishl v8, v30 ; v30 = 3 +;; @0035 v11 = iadd v9, v10 +;; @0035 v12 = iconst.i64 0 +;; @0035 v13 = select_spectre_guard v7, v12, v11 ; v12 = 0 +;; @0035 v14 = load.i64 user5 aligned table v13 +;; v29 = iconst.i64 -2 +;; @0035 v15 = band v14, v29 ; v29 = -2 +;; @0035 brif v14, block3(v15), block2 +;; +;; block2 cold: +;; @0035 v17 = iconst.i32 0 +;; @0035 v19 = uextend.i64 v3 +;; @0035 v20 = call fn0(v0, v17, v19) ; v17 = 0 +;; @0035 jump block3(v20) +;; +;; block3(v16: i64): +;; @0035 v22 = load.i64 notrap aligned readonly can_move v0+40 +;; @0035 v23 = load.i32 notrap aligned readonly can_move v22+4 +;; @0035 v24 = load.i32 user6 aligned readonly v16+16 +;; @0035 v25 = icmp eq v24, v23 +;; @0035 trapz v25, user7 +;; @0035 v26 = load.i64 notrap aligned readonly v16+8 +;; @0035 v27 = load.i64 notrap aligned readonly v16+24 +;; @0035 v28 = call_indirect sig0, v26(v27, v0, v2) +;; @0038 jump block1 +;; +;; block1: +;; @0038 return v28 +;; } diff --git a/tests/disas/component-model/exported-module-makes-adapters-indirect.wat b/tests/disas/component-model/exported-module-makes-adapters-indirect.wat index e2d7400cc0..afc37dd441 100644 --- a/tests/disas/component-model/exported-module-makes-adapters-indirect.wat +++ b/tests/disas/component-model/exported-module-makes-adapters-indirect.wat @@ -64,7 +64,6 @@ ;; gv2 = load.i64 notrap aligned gv1+16 ;; gv3 = vmctx ;; sig0 = (i64 vmctx, i64, i32) -> i32 tail -;; fn0 = u0:0 sig0 ;; stack_limit = gv2 ;; ;; block0(v0: i64, v1: i64): diff --git a/tests/disas/component-model/inlining-fuzz-bug.wat b/tests/disas/component-model/inlining-fuzz-bug.wat new file mode 100644 index 0000000000..b62cd75aaf --- /dev/null +++ b/tests/disas/component-model/inlining-fuzz-bug.wat @@ -0,0 +1,95 @@ +;;! target = "x86_64" +;;! test = "optimize" +;;! filter = "wasm[2]--function" +;;! flags = "-C inlining=y" + +(component + (core module $A + (func (export "f0") (result i32) + (i32.const 100) + ) + (func (export "f1") (result i32) + (i32.const 101) + ) + ) + + (core module $B + (import "a" "f0" (func $f0 (result i32))) + (import "a" "f1" (func $f1 (result i32))) + (func (export "f2") (result i32) + (i32.add (call $f0) (call $f1)) + ) + ) + + (core module $C + (import "b" "f2" (func $f2 (result i32))) + (func (export "f3") (result i32) + (i32.add (i32.const 100) (call $f2)) + ) + ) + + (core instance $a (instantiate $A)) + (core instance $b (instantiate $B (with "a" (instance $a)))) + (core instance $c (instantiate $C (with "b" (instance $b)))) + + (func (export "f") (result u32) + (canon lift (core func $c "f3")) + ) +) + +;; function u0:1(i64 vmctx, i64) -> i32 tail { +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly gv0+8 +;; gv2 = load.i64 notrap aligned gv1+16 +;; gv3 = vmctx +;; gv4 = vmctx +;; gv5 = load.i64 notrap aligned readonly gv4+8 +;; gv6 = load.i64 notrap aligned gv5+16 +;; gv7 = vmctx +;; gv8 = vmctx +;; gv9 = load.i64 notrap aligned readonly gv8+8 +;; gv10 = load.i64 notrap aligned gv9+16 +;; gv11 = vmctx +;; gv12 = load.i64 notrap aligned readonly gv11+8 +;; gv13 = load.i64 notrap aligned gv12+16 +;; sig0 = (i64 vmctx, i64) -> i32 tail +;; sig1 = (i64 vmctx, i64) -> i32 tail +;; fn0 = colocated u0:0 sig0 +;; fn1 = colocated u0:0 sig1 +;; fn2 = colocated u0:1 sig1 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @00c3 jump block2 +;; +;; block2: +;; jump block4 +;; +;; block4: +;; jump block5 +;; +;; block5: +;; jump block6 +;; +;; block6: +;; jump block7 +;; +;; block7: +;; jump block8 +;; +;; block8: +;; jump block9 +;; +;; block9: +;; jump block3 +;; +;; block3: +;; jump block10 +;; +;; block10: +;; @00c6 jump block1 +;; +;; block1: +;; v26 = iconst.i32 301 +;; @00c6 return v26 ; v26 = 301 +;; } diff --git a/tests/disas/component-model/multiple-instantiations-makes-adapters-indirect.wat b/tests/disas/component-model/multiple-instantiations-makes-adapters-indirect.wat index 135a763e9e..4d35c83a2c 100644 --- a/tests/disas/component-model/multiple-instantiations-makes-adapters-indirect.wat +++ b/tests/disas/component-model/multiple-instantiations-makes-adapters-indirect.wat @@ -70,7 +70,6 @@ ;; gv2 = load.i64 notrap aligned gv1+16 ;; gv3 = vmctx ;; sig0 = (i64 vmctx, i64, i32) -> i32 tail -;; fn0 = u0:0 sig0 ;; stack_limit = gv2 ;; ;; block0(v0: i64, v1: i64): diff --git a/tests/disas/gc/drc/br-on-cast-fail.wat b/tests/disas/gc/drc/br-on-cast-fail.wat index fdc54f3955..1368346c7a 100644 --- a/tests/disas/gc/drc/br-on-cast-fail.wat +++ b/tests/disas/gc/drc/br-on-cast-fail.wat @@ -27,10 +27,7 @@ ;; gv6 = load.i64 notrap aligned gv4+32 ;; sig0 = (i64 vmctx, i32, i32) -> i32 tail ;; sig1 = (i64 vmctx, i64) tail -;; sig2 = (i64 vmctx, i64) tail ;; fn0 = colocated u1:35 sig0 -;; fn1 = u0:0 sig1 -;; fn2 = u0:1 sig2 ;; stack_limit = gv2 ;; ;; block0(v0: i64, v1: i64, v2: i32): @@ -81,6 +78,6 @@ ;; block2: ;; @0038 v30 = load.i64 notrap aligned readonly can_move v0+72 ;; @0038 v29 = load.i64 notrap aligned readonly can_move v0+88 -;; @0038 call_indirect sig2, v30(v29, v0) +;; @0038 call_indirect sig1, v30(v29, v0) ;; @003a return ;; } diff --git a/tests/disas/gc/drc/br-on-cast.wat b/tests/disas/gc/drc/br-on-cast.wat index 3ee3a37388..dff0153a74 100644 --- a/tests/disas/gc/drc/br-on-cast.wat +++ b/tests/disas/gc/drc/br-on-cast.wat @@ -27,10 +27,7 @@ ;; gv6 = load.i64 notrap aligned gv4+32 ;; sig0 = (i64 vmctx, i32, i32) -> i32 tail ;; sig1 = (i64 vmctx, i64) tail -;; sig2 = (i64 vmctx, i64) tail ;; fn0 = colocated u1:35 sig0 -;; fn1 = u0:0 sig1 -;; fn2 = u0:1 sig2 ;; stack_limit = gv2 ;; ;; block0(v0: i64, v1: i64, v2: i32): @@ -81,6 +78,6 @@ ;; block2: ;; @0039 v30 = load.i64 notrap aligned readonly can_move v0+72 ;; @0039 v29 = load.i64 notrap aligned readonly can_move v0+88 -;; @0039 call_indirect sig2, v30(v29, v0) +;; @0039 call_indirect sig1, v30(v29, v0) ;; @003b return ;; } diff --git a/tests/disas/gc/null/br-on-cast-fail.wat b/tests/disas/gc/null/br-on-cast-fail.wat index d684eb5574..5ef3653ae0 100644 --- a/tests/disas/gc/null/br-on-cast-fail.wat +++ b/tests/disas/gc/null/br-on-cast-fail.wat @@ -27,10 +27,7 @@ ;; gv6 = load.i64 notrap aligned gv4+32 ;; sig0 = (i64 vmctx, i32, i32) -> i32 tail ;; sig1 = (i64 vmctx, i64) tail -;; sig2 = (i64 vmctx, i64) tail ;; fn0 = colocated u1:35 sig0 -;; fn1 = u0:0 sig1 -;; fn2 = u0:1 sig2 ;; stack_limit = gv2 ;; ;; block0(v0: i64, v1: i64, v2: i32): @@ -81,6 +78,6 @@ ;; block2: ;; @0038 v30 = load.i64 notrap aligned readonly can_move v0+72 ;; @0038 v29 = load.i64 notrap aligned readonly can_move v0+88 -;; @0038 call_indirect sig2, v30(v29, v0) +;; @0038 call_indirect sig1, v30(v29, v0) ;; @003a return ;; } diff --git a/tests/disas/gc/null/br-on-cast.wat b/tests/disas/gc/null/br-on-cast.wat index 6d72018a4d..27bf38f70b 100644 --- a/tests/disas/gc/null/br-on-cast.wat +++ b/tests/disas/gc/null/br-on-cast.wat @@ -27,10 +27,7 @@ ;; gv6 = load.i64 notrap aligned gv4+32 ;; sig0 = (i64 vmctx, i32, i32) -> i32 tail ;; sig1 = (i64 vmctx, i64) tail -;; sig2 = (i64 vmctx, i64) tail ;; fn0 = colocated u1:35 sig0 -;; fn1 = u0:0 sig1 -;; fn2 = u0:1 sig2 ;; stack_limit = gv2 ;; ;; block0(v0: i64, v1: i64, v2: i32): @@ -81,6 +78,6 @@ ;; block2: ;; @0039 v30 = load.i64 notrap aligned readonly can_move v0+72 ;; @0039 v29 = load.i64 notrap aligned readonly can_move v0+88 -;; @0039 call_indirect sig2, v30(v29, v0) +;; @0039 call_indirect sig1, v30(v29, v0) ;; @003b return ;; } diff --git a/tests/disas/memory-min-max-same.wat b/tests/disas/memory-min-max-same.wat index 5214cb5902..92b80e5c57 100644 --- a/tests/disas/memory-min-max-same.wat +++ b/tests/disas/memory-min-max-same.wat @@ -41,7 +41,6 @@ ;; gv4 = load.i64 notrap aligned gv3+64 ;; gv5 = load.i64 notrap aligned readonly can_move checked gv3+56 ;; sig0 = (i64 vmctx, i64) tail -;; fn0 = u0:0 sig0 ;; stack_limit = gv2 ;; ;; block0(v0: i64, v1: i64, v2: i32): diff --git a/tests/wast.rs b/tests/wast.rs index b7066a387a..c67472b3ad 100644 --- a/tests/wast.rs +++ b/tests/wast.rs @@ -48,6 +48,12 @@ fn main() { ]; compilers.retain(|c| c.supports_host()); + // Only test one compiler in ASAN since we're mostly interested in testing + // runtime code, not compiler-generated code. + if cfg!(asan) { + compilers.truncate(1); + } + // Run each wast test in a few interesting configuration combinations, but // leave the full combinatorial matrix and such to fuzz testing which // configures many more settings than those configured here. @@ -70,6 +76,12 @@ fn main() { ); } + // Don't do extra tests in ASAN as it takes awhile and is unlikely to + // reap much benefit. + if cfg!(asan) { + continue; + } + let compiler = compilers[0]; // Run this test with the pooling allocator under the default compiler. diff --git a/winch/codegen/src/codegen/bounds.rs b/winch/codegen/src/codegen/bounds.rs index 53d5a2e532..7e7f84790b 100644 --- a/winch/codegen/src/codegen/bounds.rs +++ b/winch/codegen/src/codegen/bounds.rs @@ -10,7 +10,6 @@ use crate::{ stack::TypedReg, }; use anyhow::Result; -use wasmtime_environ::Signed; /// A newtype to represent an immediate offset argument for a heap access. #[derive(Debug, Copy, Clone)] @@ -94,7 +93,7 @@ where let dst = context.any_gpr(masm)?; match heap.memory.static_heap_size() { // Constant size, no need to perform a load. - Some(size) => masm.mov(writable!(dst), RegImm::i64(size.signed()), ptr_size)?, + Some(size) => masm.mov(writable!(dst), RegImm::i64(size.cast_signed()), ptr_size)?, None => { masm.with_scratch::(|masm, scratch| { diff --git a/winch/codegen/src/isa/x64/asm.rs b/winch/codegen/src/isa/x64/asm.rs index 283a20bf00..fe9c14ae2d 100644 --- a/winch/codegen/src/isa/x64/asm.rs +++ b/winch/codegen/src/isa/x64/asm.rs @@ -30,7 +30,6 @@ use cranelift_codegen::{ use crate::reg::WritableReg; use cranelift_assembler_x64 as asm; -use wasmtime_environ::Unsigned; use super::address::Address; use smallvec::SmallVec; @@ -419,13 +418,13 @@ impl Assembler { let inst = match size { OperandSize::S8 => { let src = i8::try_from(src).unwrap(); - asm::inst::movb_mi::new(dst, src.unsigned()).into() + asm::inst::movb_mi::new(dst, src.cast_unsigned()).into() } OperandSize::S16 => { let src = i16::try_from(src).unwrap(); - asm::inst::movw_mi::new(dst, src.unsigned()).into() + asm::inst::movw_mi::new(dst, src.cast_unsigned()).into() } - OperandSize::S32 => asm::inst::movl_mi::new(dst, src.unsigned()).into(), + OperandSize::S32 => asm::inst::movl_mi::new(dst, src.cast_unsigned()).into(), OperandSize::S64 => asm::inst::movq_mi_sxl::new(dst, src).into(), _ => unreachable!(), }; @@ -1298,17 +1297,18 @@ impl Assembler { let inst = match size { OperandSize::S8 => { let imm = i8::try_from(imm).unwrap(); - asm::inst::cmpb_mi::new(src1, imm.unsigned()).into() + asm::inst::cmpb_mi::new(src1, imm.cast_unsigned()).into() } OperandSize::S16 => match i8::try_from(imm) { Ok(imm8) => asm::inst::cmpw_mi_sxb::new(src1, imm8).into(), Err(_) => { - asm::inst::cmpw_mi::new(src1, i16::try_from(imm).unwrap().unsigned()).into() + asm::inst::cmpw_mi::new(src1, i16::try_from(imm).unwrap().cast_unsigned()) + .into() } }, OperandSize::S32 => match i8::try_from(imm) { Ok(imm8) => asm::inst::cmpl_mi_sxb::new(src1, imm8).into(), - Err(_) => asm::inst::cmpl_mi::new(src1, imm.unsigned()).into(), + Err(_) => asm::inst::cmpl_mi::new(src1, imm.cast_unsigned()).into(), }, OperandSize::S64 => match i8::try_from(imm) { Ok(imm8) => asm::inst::cmpq_mi_sxb::new(src1, imm8).into(), @@ -2022,7 +2022,7 @@ impl Assembler { self.emit(Inst::External { inst }); } - /// Substract unsigned integers with unsigned saturation. + /// Subtract unsigned integers with unsigned saturation. pub fn xmm_vpsubus_rrr(&mut self, dst: WritableReg, src1: Reg, src2: Reg, size: OperandSize) { let dst: WritableXmm = dst.map(|r| r.into()); let inst = match size {