diff --git a/Cargo.lock b/Cargo.lock index 99558354..98f3f524 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2378,7 +2378,7 @@ dependencies = [ "byte-array-literals", "object 0.33.0", "wasi", - "wasm-encoder 0.205.0", + "wasm-encoder 0.208.1", "wit-bindgen-rust-macro", ] @@ -2418,11 +2418,13 @@ dependencies = [ "tracing-futures", "url", "viceroy-artifacts", + "wasm-encoder 0.208.1", "wasmparser 0.208.1", "wasmtime", "wasmtime-wasi", "wasmtime-wasi-nn", "wiggle", + "wit-component", ] [[package]] @@ -2504,15 +2506,6 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" -[[package]] -name = "wasm-encoder" -version = "0.205.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e95b3563d164f33c1cfb0a7efbd5940c37710019be10cd09f800fdec8b0e5c" -dependencies = [ - "leb128", -] - [[package]] name = "wasm-encoder" version = "0.207.0" @@ -2529,6 +2522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6425e84e42f7f558478e40ecc2287912cb319f2ca68e5c0bb93c61d4fc63fa17" dependencies = [ "leb128", + "wasmparser 0.208.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 21f71cbc..d92c9d08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,13 +47,14 @@ wasmtime-wasi = "21.0.0" wasmtime-wasi-nn = "21.0.0" wiggle = "21.0.0" wasmparser = "0.208.0" +wasm-encoder = { version = "0.208.0", features = ["wasmparser"] } +wit-component = "0.208.0" # Adapter dependencies byte-array-literals = { path = "crates/adapter/byte-array-literals" } bitflags = { version = "2.5.0", default-features = false } object = { version = "0.33", default-features = false, features = ["archive"] } wasi = { version = "0.11.0", default-features = false } -wasm-encoder = "0.205.0" wit-bindgen-rust-macro = { version = "0.25.0", default-features = false } [profile.release.package.viceroy-component-adapter] diff --git a/cli/src/main.rs b/cli/src/main.rs index 5e63336f..a1e87d32 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -93,6 +93,34 @@ pub async fn main() -> ExitCode { Err(_) => ExitCode::FAILURE, } } + Commands::Adapt(adapt_args) => { + let input = adapt_args.input(); + let output = adapt_args.output(); + let bytes = match std::fs::read(&input) { + Ok(bytes) => bytes, + Err(_) => { + eprintln!("Failed to read module from: {}", input.display()); + return ExitCode::FAILURE; + } + }; + + let module = match viceroy_lib::adapt::adapt_bytes(&bytes) { + Ok(module) => module, + Err(e) => { + eprintln!("Failed to adapt module: {e}"); + return ExitCode::FAILURE; + } + }; + + println!("Writing component to: {}", output.display()); + match std::fs::write(output, module) { + Ok(_) => ExitCode::SUCCESS, + Err(e) => { + eprintln!("Failed to write component: {e}"); + return ExitCode::FAILURE; + } + } + } } } diff --git a/cli/src/opts.rs b/cli/src/opts.rs index e4295728..e1d8828b 100644 --- a/cli/src/opts.rs +++ b/cli/src/opts.rs @@ -40,6 +40,9 @@ pub enum Commands { /// Run the input wasm once and then exit. Run(RunArgs), + + /// Adapt core wasm to a component. + Adapt(AdaptArgs), } #[derive(Debug, Args, Clone)] @@ -212,6 +215,37 @@ impl SharedArgs { } } +#[derive(Args, Debug, Clone)] +pub struct AdaptArgs { + /// The path to the service's Wasm module. + #[arg(value_parser = check_module, required=true)] + input: Option, + + /// The output name + #[arg(short = 'o', long = "output")] + output: Option, +} + +impl AdaptArgs { + pub(crate) fn input(&self) -> PathBuf { + PathBuf::from(self.input.as_ref().expect("input wasm name")) + } + + pub(crate) fn output(&self) -> PathBuf { + if let Some(output) = self.output.as_ref() { + return output.clone(); + } + + let mut output = PathBuf::from( + PathBuf::from(self.input.as_ref().expect("input wasm name")) + .file_name() + .expect("input filename"), + ); + output.set_extension("component.wasm"); + output + } +} + /// Enum of available (experimental) wasi modules #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Hash)] pub enum ExperimentalModuleArg { diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 5ec1e632..808625a6 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -55,6 +55,8 @@ tracing = { workspace = true } tracing-futures = { workspace = true } url = { workspace = true } wasmparser = { workspace = true } +wasm-encoder = { workspace = true } +wit-component = { workspace = true } wasmtime = { workspace = true } wasmtime-wasi = { workspace = true } wasmtime-wasi-nn = { workspace = true } diff --git a/lib/src/adapt.rs b/lib/src/adapt.rs new file mode 100644 index 00000000..96ea5d9f --- /dev/null +++ b/lib/src/adapt.rs @@ -0,0 +1,66 @@ +fn mangle_imports(bytes: &[u8]) -> anyhow::Result { + let mut module = wasm_encoder::Module::new(); + + for payload in wasmparser::Parser::new(0).parse_all(&bytes) { + let payload = payload?; + match payload { + wasmparser::Payload::Version { + encoding: wasmparser::Encoding::Component, + .. + } => { + anyhow::bail!("Mangling only supports core-wasm modules, not components"); + } + + wasmparser::Payload::ImportSection(section) => { + let mut imports = wasm_encoder::ImportSection::new(); + + for import in section { + let import = import?; + let entity = wasm_encoder::EntityType::try_from(import.ty).map_err(|_| { + anyhow::anyhow!( + "Failed to translate type for import {}:{}", + import.module, + import.name + ) + })?; + + // Leave the existing preview1 imports alone + if import.module == "wasi_snapshot_preview1" { + imports.import(import.module, import.name, entity); + } else { + let module = "wasi_snapshot_preview1"; + let name = format!("{}#{}", import.module, import.name); + imports.import(module, &name, entity); + } + } + + module.section(&imports); + } + + payload => { + if let Some((id, range)) = payload.as_section() { + module.section(&wasm_encoder::RawSection { + id, + data: &bytes[range], + }); + } + } + } + } + + Ok(module) +} + +/// Given bytes that represent a core wasm module, adapt it to a component using the viceroy +/// adapter. +pub fn adapt_bytes(bytes: &[u8]) -> anyhow::Result> { + let module = mangle_imports(bytes)?; + + let component = wit_component::ComponentEncoder::default() + .module(module.as_slice())? + .adapter("wasi_snapshot_preview1", viceroy_artifacts::ADAPTER_BYTES)? + .validate(true) + .encode()?; + + Ok(component) +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index db972744..5f03d7ae 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -13,6 +13,7 @@ #![cfg_attr(not(debug_assertions), doc(test(attr(allow(dead_code)))))] #![cfg_attr(not(debug_assertions), doc(test(attr(allow(unused_variables)))))] +pub mod adapt; pub mod body; pub mod config; pub mod error;