Skip to content

Invoke wave enhancements#10511

Merged
pchickey merged 19 commits intobytecodealliance:pch/invoke_wavefrom
tpmccallum:invoke_wave_enhancements
Apr 4, 2025
Merged

Invoke wave enhancements#10511
pchickey merged 19 commits intobytecodealliance:pch/invoke_wavefrom
tpmccallum:invoke_wave_enhancements

Conversation

@tpmccallum
Copy link
Copy Markdown
Contributor

@tpmccallum tpmccallum commented Apr 2, 2025

This PR is in relation to the Zulip conversation at #wasmtime > wasmtime-cli: support run --invoke for components using wave.

This PR includes @pchickey's existing PR 10054. (Apologies @pchickey I could not see the pch/invoke_wave in the dropdown when trying to create a PR that would target your pch/invoke_wave branch). I'm not sure why pch/invoke_wave does not show up. When I pushed it generated this new 10511 PR and included all of your previous changes, so I hope we are still on the right path to getting this --invoke feature implemented.

There is an open issue relating to this work.

Along with the changes, the documentation has also been updated accordingly (the changes in thedocs/cli-options.md file of this PR).

Functionality

The invoke function works great:

Compress WIT:

package example:component;

world compress {
    export compress: func(input: string) -> list<u8>;
}

lib.rs:

#[allow(warnings)]
mod bindings;

use bindings::Guest;
use zstd::stream::encode_all;
use std::io::Cursor;

struct Component;

impl Guest for Component {
    fn compress(input: String) -> Vec<u8> {
        encode_all(Cursor::new(input.as_bytes()), 5).unwrap()
    }
}

bindings::export!(Component with_types_in bindings);

Cargo.toml:

[package]
name = "compress"
version = "0.1.0"
edition = "2021"

[dependencies]
zstd = "0.13"
wit-bindgen-rt = { version = "0.39.0", features = ["bitflags"] }

[lib]
crate-type = ["cdylib"]

[package.metadata.component]
target = { path = "wit/compress.wit" }

CLI:

/Users/tpmccallum/wasmtime/target/release/wasmtime run --invoke "compress(\"hello\")" /Users/tpmccallum/testing_components/compress/target/wasm32-wasip1/debug/compress.wasm
[40, 181, 47, 253, 0, 88, 41, 0, 0, 104, 101, 108, 108, 111]

@tpmccallum tpmccallum requested review from a team as code owners April 2, 2025 07:19
@tpmccallum tpmccallum requested review from alexcrichton and removed request for a team April 2, 2025 07:19
@github-actions github-actions bot added wasmtime:api Related to the API of the `wasmtime` crate itself wasmtime:docs Issues related to Wasmtime's documentation labels Apr 2, 2025
@pchickey pchickey changed the base branch from main to pch/invoke_wave April 2, 2025 14:37
@pchickey pchickey requested review from a team as code owners April 2, 2025 14:37
@pchickey pchickey requested review from cfallin and removed request for a team April 2, 2025 14:37
Copy link
Copy Markdown
Contributor

@pchickey pchickey left a comment

Choose a reason for hiding this comment

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

Thanks for working on this.

I rebased my branch on main, and changed the base branch for this PR to be my branch.

I think if you drop the change to wave core i128, we can merge this to my branch, and then I'll have alex review my PR.

let low = v as i64;
let high = (v >> 64) as i64;
let low = i64::try_from(v).expect("128-bit value too large to fit in i64");
let high = i64::try_from(v >> 64).expect("Upper 64 bits of 128-bit value too large for i64");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The original code is correct here - this is making a pair of i64 out of the bits in a i128, so we want those operations to discard the extra bits and not panic when they are present

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, Pat.
Sure thing. The reason I updated this code is that there was a value truncation error in the cargo clippy CI, which read:

error: casting `u128` to `i64` may truncate the value
help: if this is intentional, allow the lint with `#[allow(clippy::cast_possible_truncation)]`
or use `try_from` and handle the error accordingly
let low = i64::try_from(v);

I will change it back and run clippy locally to ensure it truncates silently and suppresses the warning/error.
Will report back here soon.

Copy link
Copy Markdown
Contributor Author

@tpmccallum tpmccallum Apr 2, 2025

Choose a reason for hiding this comment

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

Ok, I did a cargo clean and cargo build --release and then also performed the CI locally i.e.:

export CARGO_INCREMENTAL=0
export CARGO_PROFILE_DEV_DEBUG=0
export CARGO_PROFILE_TEST_DEBUG=0
export RUSTFLAGS="-D warnings"
export WIT_REQUIRE_SEMICOLONS=1
cargo clippy --workspace --all-targets

The following error occurred:

    Checking wiggle v32.0.0 (/Users/tpmccallum/wasmtime/crates/wiggle)
error: casting `u128` to `i64` may truncate the value
   --> crates/wasmtime/src/runtime/wave/core.rs:103:19
    |
103 |         let low = v as i64;
    |                   ^^^^^^^^
    |
    = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation
    = note: `-D clippy::cast-possible-truncation` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::cast_possible_truncation)]`
help: ... or use `try_from` and handle the error accordingly
    |
103 -         let low = v as i64;
103 +         let low = i64::try_from(v);
    |

error: could not compile `wasmtime` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Reference:

This is the initial GitHub CI error that prompted the try_from approaches:

Screenshot 2025-04-03 at 09 38 01
Screenshot 2025-04-03 at 09 38 25

I will revert to the original code and use the following attribute (at the function level) to see if the CI error goes away for that specific function:

    #[allow(clippy::cast_possible_truncation)]
    fn unwrap_tuple(&self) -> Box<dyn Iterator<Item = Cow<Self>> + '_> {
        let v = unwrap_val!(self, Self::V128, "tuple").as_u128();
        let low = v as i64;
        let high = (v >> 64) as i64;
        Box::new(
            [Self::I64(low), Self::I64(high)]
                .into_iter()
                .map(Cow::Owned),
        )
    }
}

Report back soon ... :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, so that attribute (#[allow(clippy::cast_possible_truncation)] at the function level) seemed to do the trick.

Build:

$ cargo build --release
   Compiling proc-macro2 v1.0.92
   Compiling unicode-ident v1.0.8
  -- snip --
   Compiling wasmtime-wasi-tls v32.0.0 (/Users/tpmccallum/wasmtime/crates/wasi-tls)
    Finished `release` profile [optimized] target(s) in 10m 59s

Clippy:

$ export CARGO_INCREMENTAL=0
$ export CARGO_PROFILE_DEV_DEBUG=0
$ export CARGO_PROFILE_TEST_DEBUG=0
$ export RUSTFLAGS="-D warnings"
$ export WIT_REQUIRE_SEMICOLONS=1
$ cargo clippy --workspace --all-targets
   Compiling proc-macro2 v1.0.92
   Compiling unicode-ident v1.0.8
   Compiling serde v1.0.215
  --snip--
    Checking wasmtime-c-api v32.0.0 (/Users/tpmccallum/wasmtime/crates/c-api/artifact)
    Finished `dev` profile [unoptimized] target(s) in 4m 34s

I will commit the changes now.

@pchickey pchickey removed request for a team and alexcrichton April 2, 2025 14:44
@pchickey pchickey removed the request for review from cfallin April 2, 2025 14:44
@tpmccallum
Copy link
Copy Markdown
Contributor Author

tpmccallum commented Apr 3, 2025

Ok, signing off for the day.
cargo build --release and cargo clippy are ✅

The absence of parentheses will give a helpful result:

$ wasmtime run --invoke "compress" compress.wasm 
Error: failed to run main module `compress.wasm`
Caused by:
    0: Failed to parse invoke 'compress': function calls must be enveloped in double quotes and must include parentheses (e.g., "compress()"). String arguments must be enveloped in escaped double quotes (e.g., "compress(\"hello\")").
    1: unexpected end of input at 8..8

Detecting Quotes In Rust

Interestingly, the absence of outer double quotes is a little trickier. The Rust code I added attempts to test for double quotes via starts_with and ends_with, but this does not work. For example:

  let s = "compress(\"hello\")";
  println!("starts_with: {}, ends_with: {}", s.starts_with("\""), s.ends_with("\""));

One would assume the result to be starts_with: true, ends_with: true but in reality the result is actually:

starts_with: false, ends_with: false

Alas, this is not a validator that is good enough to know whether the exported function being invoked by --invoke is surrounded by double quotes. I did try (and succeeded using the format! macro) to see the outer double quotes when they actually do existed. For example:

// This `invoke` that Rust sees here does not show the outer double quotes. Only the following `func()` or `func(42)`.
let invoke: &String = self.invoke.as_ref().unwrap();
// However, I can get the raw data and see double quotes if/when they exist. For example `"func()"` or `"func(42)"`.
let raw_invoke = format!("{:?}", invoke);

The overarching issue is that if a user does not surround the exported function in quotes, it fails in the shell long before the Rust even sees it. And the result is something along these lines:

$ wasmtime run --invoke compress(\"hello\") compress.wasm 

zsh: unknown file attribute: "
wasmtime run --invoke compress("hello") compress.wasm 

zsh: unknown file attribute: h

The following quoting examples do work, which is great.

$ wasmtime run --invoke "compress(\"hello\")" compress.wasm

[40, 181, 47, 253, 0, 88, 41, 0, 0, 104, 101, 108, 108, 111]
$ wasmtime run --invoke 'compress("hello")' compress.wasm

[40, 181, 47, 253, 0, 88, 41, 0, 0, 104, 101, 108, 108, 111]

Assist

I am stumped in finding a way to enforce outer double or single quotes in the wasmtime run CLI.

(I am convinced that even something built into wasm-wave would not catch the missing outer double quotes in time either. The errors with no outer quotes are stopped in their tracks in the CLI)

For this reason, I ended up being a bit verbose on the documentation on this PR

Hope all of that makes sense. Happy to do more - happy reviewing.

@tpmccallum tpmccallum requested a review from pchickey April 3, 2025 10:45
@pchickey pchickey merged commit 1d75e42 into bytecodealliance:pch/invoke_wave Apr 4, 2025
pchickey added a commit that referenced this pull request Apr 11, 2025
* wip

* wasmtime::component::Component: add iterator of exports

* components_rec

* exports_rec gives fully qualified name

* invoke works!!!

* code motion

* more context in errors

* fix test of invoke

* Finalized enhancements for --invoke: error messages

* Testing and documenting --invoke

* Update if else re: invoke

* Updating to fix truncation possibilities in unwrap_tuple

* Add clippy annotation to resolve CI error and leave original code (that makes 2 i64 out of 1 i128 and discards extra bits).

* Format (rustfmt --edition 2021) Rust files in this PR.

* Removing duplicate code missed from previous conflict resolution

* Add more verbose documentation

* Add more verbose documentation

---------

Co-authored-by: Pat Hickey <pat@moreproductive.org>
github-merge-queue bot pushed a commit that referenced this pull request Apr 11, 2025
* wip

* invoke works!!!

* code motion

* more context in errors

* fix test of invoke

* Invoke wave enhancements (#10511)

* wip

* wasmtime::component::Component: add iterator of exports

* components_rec

* exports_rec gives fully qualified name

* invoke works!!!

* code motion

* more context in errors

* fix test of invoke

* Finalized enhancements for --invoke: error messages

* Testing and documenting --invoke

* Update if else re: invoke

* Updating to fix truncation possibilities in unwrap_tuple

* Add clippy annotation to resolve CI error and leave original code (that makes 2 i64 out of 1 i128 and discards extra bits).

* Format (rustfmt --edition 2021) Rust files in this PR.

* Removing duplicate code missed from previous conflict resolution

* Add more verbose documentation

* Add more verbose documentation

---------

Co-authored-by: Pat Hickey <pat@moreproductive.org>

* Convert docs and error trapping to single quote approach (#10533)

* Convert docs and error trapping to single quote approach

* Adjust the error message a little

* Respond to review (#10544)

* cli: search component for export items

---------

Co-authored-by: Timothy McCallum <mistermac2008@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

wasmtime:api Related to the API of the `wasmtime` crate itself wasmtime:docs Issues related to Wasmtime's documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants