xref: /wasmtime-44.0.1/src/commands/wizer.rs (revision dbaaa92f)
1 use crate::commands::run::{CliInstance, Preloads, RunCommand};
2 use crate::common::{RunCommon, RunTarget};
3 use std::fs;
4 use std::io::{self, Read, Write};
5 use std::path::PathBuf;
6 use wasmtime::{Module, Result, error::Context as _};
7 use wasmtime_wizer::Wizer;
8 
9 #[derive(clap::Parser)]
10 #[expect(missing_docs, reason = "inheriting wizer's docs")]
11 pub struct WizerCommand {
12     #[command(flatten)]
13     run: RunCommon,
14 
15     #[command(flatten)]
16     wizer: Wizer,
17 
18     /// The input Wasm module's file path.
19     input: PathBuf,
20 
21     #[command(flatten)]
22     preloads: Preloads,
23 
24     /// The file path to write the output Wasm module to.
25     ///
26     /// If not specified, then `stdout` is used.
27     #[arg(short = 'o', long)]
28     output: Option<PathBuf>,
29 }
30 
31 enum WizerInfo<'a> {
32     Core(wasmtime_wizer::ModuleContext<'a>),
33     #[cfg(feature = "component-model")]
34     Component(wasmtime_wizer::ComponentContext<'a>),
35 }
36 
37 impl WizerCommand {
38     /// Runs the command.
execute(mut self) -> Result<()>39     pub fn execute(mut self) -> Result<()> {
40         self.run.common.init_logging()?;
41         let runtime = tokio::runtime::Builder::new_multi_thread()
42             .enable_time()
43             .enable_io()
44             .build()?;
45         runtime.block_on(self.execute_async())
46     }
47 
execute_async(mut self) -> Result<()>48     async fn execute_async(mut self) -> Result<()> {
49         // By default use deterministic relaxed simd operations to guarantee
50         // that if relaxed simd operations are used in a module that they always
51         // produce the same result.
52         if self.run.common.wasm.relaxed_simd_deterministic.is_none() {
53             self.run.common.wasm.relaxed_simd_deterministic = Some(true);
54         }
55 
56         // Don't provide any WASI imports by default to wizened components. The
57         // `run` command provides the "cli" world as a default so turn that off
58         // here if the command line flags don't otherwise say what to do.
59         if self.run.common.wasi.cli.is_none() {
60             self.run.common.wasi.cli = Some(false);
61         }
62 
63         // Read the input wasm, possibly from stdin.
64         let mut wasm = Vec::new();
65         if self.input.to_str() == Some("-") {
66             io::stdin()
67                 .read_to_end(&mut wasm)
68                 .context("failed to read input Wasm module from stdin")?;
69         } else {
70             wasm = fs::read(&self.input).context("failed to read input Wasm module")?;
71         }
72 
73         #[cfg(feature = "wat")]
74         let wasm = wat::parse_bytes(&wasm)?;
75         let is_component = wasmparser::Parser::is_component(&wasm);
76 
77         let mut run = RunCommand {
78             run: self.run,
79             argv0: None,
80             invoke: Some(if is_component {
81                 format!("{}()", self.wizer.get_init_func())
82             } else {
83                 self.wizer.get_init_func().to_string()
84             }),
85             module_and_args: vec![self.input.clone().into()],
86             preloads: self.preloads.clone(),
87             module_bytes: None,
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 }
159