wasmtime_cli/commands/
wizer.rs

1use crate::commands::run::{CliInstance, Preloads, RunCommand};
2use crate::common::{RunCommon, RunTarget};
3use anyhow::{Context, Result};
4use std::fs;
5use std::io::{self, Read, Write};
6use std::path::PathBuf;
7use wasmtime::Module;
8use wasmtime_wizer::Wizer;
9
10#[derive(clap::Parser)]
11#[expect(missing_docs, reason = "inheriting wizer's docs")]
12pub struct WizerCommand {
13    #[command(flatten)]
14    run: RunCommon,
15
16    #[command(flatten)]
17    wizer: Wizer,
18
19    /// The input Wasm module's file path.
20    input: PathBuf,
21
22    #[command(flatten)]
23    preloads: Preloads,
24
25    /// The file path to write the output Wasm module to.
26    ///
27    /// If not specified, then `stdout` is used.
28    #[arg(short = 'o', long)]
29    output: Option<PathBuf>,
30}
31
32enum WizerInfo<'a> {
33    Core(wasmtime_wizer::ModuleContext<'a>),
34    #[cfg(feature = "component-model")]
35    Component(wasmtime_wizer::ComponentContext<'a>),
36}
37
38impl WizerCommand {
39    /// Runs the command.
40    pub fn execute(mut self) -> Result<()> {
41        self.run.common.init_logging()?;
42        let runtime = tokio::runtime::Builder::new_multi_thread()
43            .enable_time()
44            .enable_io()
45            .build()?;
46        runtime.block_on(self.execute_async())
47    }
48
49    async fn execute_async(mut self) -> Result<()> {
50        // By default use deterministic relaxed simd operations to guarantee
51        // that if relaxed simd operations are used in a module that they always
52        // produce the same result.
53        if self.run.common.wasm.relaxed_simd_deterministic.is_none() {
54            self.run.common.wasm.relaxed_simd_deterministic = Some(true);
55        }
56
57        // Don't provide any WASI imports by default to wizened components. The
58        // `run` command provides the "cli" world as a default so turn that off
59        // here if the command line flags don't otherwise say what to do.
60        if self.run.common.wasi.cli.is_none() {
61            self.run.common.wasi.cli = Some(false);
62        }
63
64        // Read the input wasm, possibly from stdin.
65        let mut wasm = Vec::new();
66        if self.input.to_str() == Some("-") {
67            io::stdin()
68                .read_to_end(&mut wasm)
69                .context("failed to read input Wasm module from stdin")?;
70        } else {
71            wasm = fs::read(&self.input).context("failed to read input Wasm module")?;
72        }
73
74        #[cfg(feature = "wat")]
75        let wasm = wat::parse_bytes(&wasm)?;
76        let is_component = wasmparser::Parser::is_component(&wasm);
77
78        let mut run = RunCommand {
79            run: self.run,
80            argv0: None,
81            invoke: Some(if is_component {
82                format!("{}()", self.wizer.get_init_func())
83            } else {
84                self.wizer.get_init_func().to_string()
85            }),
86            module_and_args: vec![self.input.clone().into()],
87            preloads: self.preloads.clone(),
88        };
89        let engine = run.new_engine()?;
90
91        // Instrument the input wasm with wizer.
92        let (cx, main) = if is_component {
93            #[cfg(feature = "component-model")]
94            {
95                let (cx, wasm) = self.wizer.instrument_component(&wasm)?;
96                (
97                    WizerInfo::Component(cx),
98                    RunTarget::Component(wasmtime::component::Component::new(&engine, &wasm)?),
99                )
100            }
101            #[cfg(not(feature = "component-model"))]
102            unreachable!();
103        } else {
104            let (cx, wasm) = self.wizer.instrument(&wasm)?;
105            (
106                WizerInfo::Core(cx),
107                RunTarget::Core(Module::new(&engine, &wasm)?),
108            )
109        };
110
111        // Execute a rough equivalent of
112        // `wasmtime run --invoke <..> <instrumented-wasm>`
113        let (mut store, mut linker) = run.new_store_and_linker(&engine, &main)?;
114        let instance = run
115            .instantiate_and_run(&engine, &mut linker, &main, &mut store)
116            .await?;
117
118        // Use our state to capture a snapshot with Wizer and then serialize
119        // that.
120        let final_wasm = match (cx, instance) {
121            (WizerInfo::Core(cx), CliInstance::Core(instance)) => {
122                self.wizer
123                    .snapshot(
124                        cx,
125                        &mut wasmtime_wizer::WasmtimeWizer {
126                            store: &mut store,
127                            instance,
128                        },
129                    )
130                    .await?
131            }
132
133            #[cfg(feature = "component-model")]
134            (WizerInfo::Component(cx), CliInstance::Component(instance)) => {
135                self.wizer
136                    .snapshot_component(
137                        cx,
138                        &mut wasmtime_wizer::WasmtimeWizerComponent {
139                            store: &mut store,
140                            instance,
141                        },
142                    )
143                    .await?
144            }
145
146            #[cfg(feature = "component-model")]
147            (WizerInfo::Core(_) | WizerInfo::Component(_), _) => unreachable!(),
148        };
149
150        match &self.output {
151            Some(file) => fs::write(file, &final_wasm).context("failed to write output file")?,
152            None => std::io::stdout()
153                .write_all(&final_wasm)
154                .context("failed to write output to stdout")?,
155        }
156        Ok(())
157    }
158}