wasmtime_bench_api/lib.rs
1//! A C API for benchmarking Wasmtime's WebAssembly compilation, instantiation,
2//! and execution.
3//!
4//! The API expects calls that match the following state machine:
5//!
6//! ```text
7//! |
8//! |
9//! V
10//! .---> wasm_bench_create
11//! | | |
12//! | | |
13//! | | V
14//! | | wasm_bench_compile
15//! | | | |
16//! | | | | .----.
17//! | | | | | |
18//! | | | V V |
19//! | | | wasm_bench_instantiate <------.
20//! | | | | | |
21//! | | | | | |
22//! | | | | | |
23//! | | | .------' '-----> wasm_bench_execute
24//! | | | | |
25//! | | | | |
26//! | V V V |
27//! '------ wasm_bench_free <--------------------------'
28//! |
29//! |
30//! V
31//! ```
32//!
33//! All API calls must happen on the same thread.
34//!
35//! Functions which return pointers use null as an error value. Function which
36//! return `int` use `0` as OK and non-zero as an error value.
37//!
38//! # Example
39//!
40//! ```
41//! use std::ptr;
42//! use wasmtime_bench_api::*;
43//!
44//! let working_dir = std::env::current_dir().unwrap().display().to_string();
45//! let stdout_path = "./stdout.log";
46//! let stderr_path = "./stderr.log";
47//!
48//! // Functions to start/end timers for compilation.
49//! //
50//! // The `compilation_timer` pointer configured in the `WasmBenchConfig` is
51//! // passed through.
52//! extern "C" fn compilation_start(timer: *mut u8) {
53//! // Start your compilation timer here.
54//! }
55//! extern "C" fn compilation_end(timer: *mut u8) {
56//! // End your compilation timer here.
57//! }
58//!
59//! // Similar for instantiation.
60//! extern "C" fn instantiation_start(timer: *mut u8) {
61//! // Start your instantiation timer here.
62//! }
63//! extern "C" fn instantiation_end(timer: *mut u8) {
64//! // End your instantiation timer here.
65//! }
66//!
67//! // Similar for execution.
68//! extern "C" fn execution_start(timer: *mut u8) {
69//! // Start your execution timer here.
70//! }
71//! extern "C" fn execution_end(timer: *mut u8) {
72//! // End your execution timer here.
73//! }
74//!
75//! let config = WasmBenchConfig {
76//! version: WASM_BENCH_CONFIG_VERSION,
77//! size: std::mem::size_of::<WasmBenchConfig>(),
78//! working_dir_ptr: working_dir.as_ptr(),
79//! working_dir_len: working_dir.len(),
80//! stdout_path_ptr: stdout_path.as_ptr(),
81//! stdout_path_len: stdout_path.len(),
82//! stderr_path_ptr: stderr_path.as_ptr(),
83//! stderr_path_len: stderr_path.len(),
84//! stdin_path_ptr: ptr::null(),
85//! stdin_path_len: 0,
86//! compilation_timer: ptr::null_mut(),
87//! compilation_start,
88//! compilation_end,
89//! instantiation_timer: ptr::null_mut(),
90//! instantiation_start,
91//! instantiation_end,
92//! execution_timer: ptr::null_mut(),
93//! execution_start,
94//! execution_end,
95//! execution_flags_ptr: ptr::null(),
96//! execution_flags_len: 0,
97//! cache: false,
98//! };
99//!
100//! let mut bench_api = ptr::null_mut();
101//! unsafe {
102//! let code = wasm_bench_create(&config, &mut bench_api);
103//! assert_eq!(code, OK);
104//! assert!(!bench_api.is_null());
105//! };
106//!
107//! let wasm = wat::parse_bytes(br#"
108//! (module
109//! (func $bench_start (import "bench" "start"))
110//! (func $bench_end (import "bench" "end"))
111//! (func $start (export "_start")
112//! call $bench_start
113//! i32.const 1
114//! i32.const 2
115//! i32.add
116//! drop
117//! call $bench_end
118//! )
119//! )
120//! "#).unwrap();
121//!
122//! // This will call the `compilation_{start,end}` timing functions on success.
123//! let code = unsafe { wasm_bench_compile(bench_api, wasm.as_ptr(), wasm.len()) };
124//! assert_eq!(code, OK);
125//!
126//! // This will call the `instantiation_{start,end}` timing functions on success.
127//! let code = unsafe { wasm_bench_instantiate(bench_api) };
128//! assert_eq!(code, OK);
129//!
130//! // This will call the `execution_{start,end}` timing functions on success.
131//! let code = unsafe { wasm_bench_execute(bench_api) };
132//! assert_eq!(code, OK);
133//!
134//! unsafe {
135//! wasm_bench_free(bench_api);
136//! }
137//! ```
138
139mod unsafe_send_sync;
140
141use crate::unsafe_send_sync::UnsafeSendSync;
142use clap::Parser;
143use std::os::raw::{c_int, c_void};
144use std::slice;
145use std::{env, path::PathBuf};
146use wasmtime::{
147 CodeBuilder, CodeHint, Engine, Linker, Module, Result, Store, error::Context as _, format_err,
148};
149use wasmtime_cli_flags::CommonOptions;
150use wasmtime_wasi::cli::{InputFile, OutputFile};
151use wasmtime_wasi::{FsPerms, I32Exit, WasiCtx, WasiCtxView, WasiView, p1::WasiP1Ctx};
152
153pub type ExitCode = c_int;
154pub const OK: ExitCode = 0;
155pub const ERR: ExitCode = -1;
156
157/// The current version of `WasmBenchConfig`.
158///
159/// Bumped once per release that appends a field, and never otherwise, so a
160/// field added in version `N` is present exactly when the caller's `version` is
161/// at least `N`.
162pub const WASM_BENCH_CONFIG_VERSION: u32 = 1;
163
164// Randomize the location of heap objects to avoid accidental locality being an
165// uncontrolled variable that obscures performance evaluation in our
166// experiments.
167#[cfg(feature = "shuffling-allocator")]
168#[global_allocator]
169static ALLOC: shuffling_allocator::ShufflingAllocator<std::alloc::System> =
170 shuffling_allocator::wrap!(&std::alloc::System);
171
172/// Generate `WasmBenchConfig` and a `Config` accessor for each of its fields.
173///
174/// Tag each field with the `#[since_version = N]` that introduced it. Version 0
175/// fields are mandatory, so their accessors return the field directly; later
176/// fields may be missing from an older caller, so theirs return `Option`.
177macro_rules! define_config {
178 (
179 $(#[doc = $struct_doc:expr])*
180 pub struct $name:ident {
181 $(
182 $(#[doc = $field_doc:expr])*
183 #[since_version = $version:tt]
184 pub $field:ident : $ty:ty,
185 )*
186 }
187 ) => {
188 $(#[doc = $struct_doc])*
189 #[repr(C)]
190 pub struct $name {
191 $(
192 $(#[doc = $field_doc])*
193 pub $field : $ty,
194 )*
195 }
196
197 impl Config<'_> {
198 $(
199 define_config!(@accessor $name, $field, $ty, $version);
200 )*
201 }
202 };
203
204 // Version 0 fields are mandatory. `Config::new` rejects any caller too
205 // small to hold them, so they are always in bounds.
206 (@accessor $name:ident, $field:ident, $ty:ty, 0) => {
207 fn $field(&self) -> $ty {
208 unsafe { self.read(std::ptr::addr_of!((*self.ptr).$field)) }
209 }
210 };
211
212 // Fields added later may lie outside an older caller's allocation, so they
213 // are only read once `has_field` confirms this caller provided them.
214 (@accessor $name:ident, $field:ident, $ty:ty, $version:tt) => {
215 fn $field(&self) -> Option<$ty> {
216 let offset = std::mem::offset_of!($name, $field);
217 if self.has_field($version, offset, size_of::<$ty>()) {
218 Some(unsafe { self.read(std::ptr::addr_of!((*self.ptr).$field)) })
219 } else {
220 None
221 }
222 }
223 };
224}
225
226define_config! {
227 /// Configuration options for the benchmark.
228 ///
229 /// This structure is always passed across the bench API by pointer, and new
230 /// fields are only ever appended to the end, so that a caller built against
231 /// an older version of this structure can still drive a newer bench API
232 /// library.
233 ///
234 /// We only ever read one through [`Config`], never as a reference.
235 pub struct WasmBenchConfig {
236 /// The version of this structure that the caller was built against.
237 ///
238 /// Each field's documentation names the version that added it; a field
239 /// is only present when `version` is at least that.
240 #[since_version = 0]
241 pub version: u32,
242
243 /// The size of this structure, in bytes, as the caller knows it.
244 ///
245 /// Fields beyond these first `size` bytes were not allocated by the
246 /// caller and must not be read.
247 ///
248 /// This acts as a secondary backstop to `version`.
249 #[since_version = 0]
250 pub size: usize,
251
252 /// The working directory where benchmarks should be executed.
253 #[since_version = 0]
254 pub working_dir_ptr: *const u8,
255 #[since_version = 0]
256 pub working_dir_len: usize,
257
258 /// The file path that should be created and used as `stdout`.
259 #[since_version = 0]
260 pub stdout_path_ptr: *const u8,
261 #[since_version = 0]
262 pub stdout_path_len: usize,
263
264 /// The file path that should be created and used as `stderr`.
265 #[since_version = 0]
266 pub stderr_path_ptr: *const u8,
267 #[since_version = 0]
268 pub stderr_path_len: usize,
269
270 /// The (optional) file path that should be opened and used as `stdin`.
271 /// If not provided, then the WASI context will not have a `stdin`
272 /// initialized.
273 #[since_version = 0]
274 pub stdin_path_ptr: *const u8,
275 #[since_version = 0]
276 pub stdin_path_len: usize,
277
278 /// The functions to start and stop performance timers/counters during
279 /// Wasm compilation.
280 #[since_version = 0]
281 pub compilation_timer: *mut u8,
282 #[since_version = 0]
283 pub compilation_start: extern "C" fn(*mut u8),
284 #[since_version = 0]
285 pub compilation_end: extern "C" fn(*mut u8),
286
287 /// The functions to start and stop performance timers/counters during
288 /// Wasm instantiation.
289 #[since_version = 0]
290 pub instantiation_timer: *mut u8,
291 #[since_version = 0]
292 pub instantiation_start: extern "C" fn(*mut u8),
293 #[since_version = 0]
294 pub instantiation_end: extern "C" fn(*mut u8),
295
296 /// The functions to start and stop performance timers/counters during
297 /// Wasm execution.
298 #[since_version = 0]
299 pub execution_timer: *mut u8,
300 #[since_version = 0]
301 pub execution_start: extern "C" fn(*mut u8),
302 #[since_version = 0]
303 pub execution_end: extern "C" fn(*mut u8),
304
305 /// The (optional) flags to use when running Wasmtime. These correspond
306 /// to the flags used when running Wasmtime from the command line.
307 #[since_version = 0]
308 pub execution_flags_ptr: *const u8,
309 #[since_version = 0]
310 pub execution_flags_len: usize,
311
312 /// Whether to enable Wasmtime's compilation cache.
313 ///
314 /// Benchmark runs that measure compilation want this off, so that they
315 /// measure compilation rather than a cache lookup. Benchmark runs that
316 /// measure only instantiation or execution want this on, to avoid
317 /// spending time recompiling the Wasm.
318 #[since_version = 1]
319 pub cache: bool,
320 }
321}
322
323/// A caller-provided [`WasmBenchConfig`], read through its pointer.
324///
325/// Holding the pointer rather than a reference is what keeps this sound. A
326/// `&WasmBenchConfig` would assert that the whole struct is initialized, which
327/// is not true of its padding bytes, nor of the fields belonging to versions
328/// newer than the caller's.
329pub struct Config<'a> {
330 ptr: *const WasmBenchConfig,
331 _lifetime: std::marker::PhantomData<&'a WasmBenchConfig>,
332}
333
334impl<'a> Config<'a> {
335 /// The fields every caller must provide, regardless of how old it is.
336 const MIN_SIZE: usize =
337 std::mem::offset_of!(WasmBenchConfig, execution_flags_len) + size_of::<usize>();
338
339 /// Wrap a caller-provided config pointer, after checking it is large enough
340 /// to hold every mandatory field.
341 ///
342 /// # Safety
343 ///
344 /// `ptr` must be non-null, aligned, and point to a `version` and `size`
345 /// that accurately describe the fields that follow, all of which must
346 /// remain valid for `'a`.
347 unsafe fn new(ptr: *const WasmBenchConfig) -> Result<Config<'a>> {
348 assert!(!ptr.is_null());
349 let config = Config {
350 ptr,
351 _lifetime: std::marker::PhantomData,
352 };
353
354 // Reads `size`, which every caller provides, so this is in bounds even
355 // for the oldest one.
356 let size = config.size();
357 if size < Self::MIN_SIZE {
358 return Err(format_err!(
359 "given `WasmBenchConfig` is {} bytes, but at least {} are required",
360 size,
361 Self::MIN_SIZE,
362 ));
363 }
364
365 Ok(config)
366 }
367
368 /// Read one field out of the caller's config.
369 ///
370 /// # Safety
371 ///
372 /// `field` must be a pointer to a field this caller actually provided,
373 /// derived from `self.ptr`.
374 unsafe fn read<T>(&self, field: *const T) -> T {
375 assert!(!field.is_null());
376 assert!(field.is_aligned());
377 unsafe { field.read() }
378 }
379
380 /// Did the caller provide the field that version `version` added, which
381 /// occupies `field_size` bytes at `offset`?
382 ///
383 /// Both checks must pass. `version` alone is the field's real name, but
384 /// trusting it would let a caller that bumped its version without growing
385 /// the struct send us past its allocation; `size` alone cannot tell a field
386 /// apart from the padding that may follow the one before it.
387 fn has_field(&self, version: u32, offset: usize, field_size: usize) -> bool {
388 self.version() >= version && self.size() >= offset + field_size
389 }
390
391 /// Whether to enable the compilation cache.
392 ///
393 /// Conservatively defaults to disabled for callers that predate the `cache`
394 /// field, so we don't break compilation measurements.
395 fn cache_enabled(&self) -> bool {
396 self.cache().unwrap_or(false)
397 }
398
399 fn working_dir(&self) -> Result<PathBuf> {
400 let working_dir =
401 unsafe { std::slice::from_raw_parts(self.working_dir_ptr(), self.working_dir_len()) };
402 let working_dir = std::str::from_utf8(working_dir)
403 .context("given working directory is not valid UTF-8")?;
404 Ok(working_dir.into())
405 }
406
407 fn stdout_path(&self) -> Result<PathBuf> {
408 let stdout_path =
409 unsafe { std::slice::from_raw_parts(self.stdout_path_ptr(), self.stdout_path_len()) };
410 let stdout_path =
411 std::str::from_utf8(stdout_path).context("given stdout path is not valid UTF-8")?;
412 Ok(stdout_path.into())
413 }
414
415 fn stderr_path(&self) -> Result<PathBuf> {
416 let stderr_path =
417 unsafe { std::slice::from_raw_parts(self.stderr_path_ptr(), self.stderr_path_len()) };
418 let stderr_path =
419 std::str::from_utf8(stderr_path).context("given stderr path is not valid UTF-8")?;
420 Ok(stderr_path.into())
421 }
422
423 fn stdin_path(&self) -> Result<Option<PathBuf>> {
424 let stdin_path_ptr = self.stdin_path_ptr();
425 if stdin_path_ptr.is_null() {
426 return Ok(None);
427 }
428
429 let stdin_path =
430 unsafe { std::slice::from_raw_parts(stdin_path_ptr, self.stdin_path_len()) };
431 let stdin_path =
432 std::str::from_utf8(stdin_path).context("given stdin path is not valid UTF-8")?;
433 Ok(Some(stdin_path.into()))
434 }
435
436 fn execution_flags(&self) -> Result<CommonOptions> {
437 let execution_flags_ptr = self.execution_flags_ptr();
438 let flags = if execution_flags_ptr.is_null() {
439 ""
440 } else {
441 let execution_flags = unsafe {
442 std::slice::from_raw_parts(execution_flags_ptr, self.execution_flags_len())
443 };
444 std::str::from_utf8(execution_flags)
445 .context("given execution flags string is not valid UTF-8")?
446 };
447 let options = CommonOptions::try_parse_from(
448 ["wasmtime"]
449 .into_iter()
450 .chain(flags.split(' ').filter(|s| !s.is_empty())),
451 )
452 .context("failed to parse options")?;
453 Ok(options)
454 }
455}
456
457/// Exposes a C-compatible way of creating the engine from the bytes of a single
458/// Wasm module.
459///
460/// On success, the `out_bench_ptr` is initialized to a pointer to a structure
461/// that contains the engine's initialized state, and `0` is returned. On
462/// failure, a non-zero status code is returned and `out_bench_ptr` is left
463/// untouched.
464///
465/// The `config` pointer only needs to remain valid for the duration of this
466/// call, as does everything it points to.
467#[unsafe(no_mangle)]
468pub extern "C" fn wasm_bench_create(
469 config: *const WasmBenchConfig,
470 out_bench_ptr: *mut *mut c_void,
471) -> ExitCode {
472 let result = (|| -> Result<_> {
473 let config = unsafe { Config::new(config)? };
474
475 // Clone paths for the core Wasm WASI closure.
476 let working_dir = config.working_dir()?;
477 let stdout_path = config.stdout_path()?;
478 let stderr_path = config.stderr_path()?;
479 let stdin_path = config.stdin_path()?;
480 let options = config.execution_flags()?;
481
482 let state = Box::new(BenchState::new(
483 options,
484 config.cache_enabled(),
485 config.compilation_timer(),
486 config.compilation_start(),
487 config.compilation_end(),
488 config.instantiation_timer(),
489 config.instantiation_start(),
490 config.instantiation_end(),
491 config.execution_timer(),
492 config.execution_start(),
493 config.execution_end(),
494 move || {
495 let mut cx = WasiCtx::builder();
496
497 let stdout = std::fs::File::create(&stdout_path)
498 .with_context(|| format!("failed to create {}", stdout_path.display()))?;
499 cx.stdout(OutputFile::new(stdout));
500
501 let stderr = std::fs::File::create(&stderr_path)
502 .with_context(|| format!("failed to create {}", stderr_path.display()))?;
503 cx.stderr(OutputFile::new(stderr));
504
505 if let Some(stdin_path) = &stdin_path {
506 let stdin = std::fs::File::open(stdin_path)
507 .with_context(|| format!("failed to open {}", stdin_path.display()))?;
508 cx.stdin(InputFile::new(stdin));
509 }
510
511 // Allow access to the working directory so that the benchmark can read
512 // its input workload(s).
513 cx.preopened_dir(working_dir.clone(), ".", FsPerms::ReadOnly)?;
514
515 // Pass this env var along so that the benchmark program can use smaller
516 // input workload(s) if it has them and that has been requested.
517 if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") {
518 cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val);
519 }
520
521 Ok(cx.build_p1())
522 },
523 )?);
524 Ok(Box::into_raw(state) as _)
525 })();
526
527 if let Ok(bench_ptr) = result {
528 unsafe {
529 assert!(!out_bench_ptr.is_null());
530 *out_bench_ptr = bench_ptr;
531 }
532 }
533
534 to_exit_code(result.map(|_| ()))
535}
536
537/// Free the engine state allocated by this library.
538#[unsafe(no_mangle)]
539pub extern "C" fn wasm_bench_free(state: *mut c_void) {
540 assert!(!state.is_null());
541 unsafe {
542 drop(Box::from_raw(state as *mut BenchState));
543 }
544}
545
546/// Compile the Wasm benchmark module.
547#[unsafe(no_mangle)]
548pub extern "C" fn wasm_bench_compile(
549 state: *mut c_void,
550 wasm_bytes: *const u8,
551 wasm_bytes_length: usize,
552) -> ExitCode {
553 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
554 let wasm_bytes = unsafe { slice::from_raw_parts(wasm_bytes, wasm_bytes_length) };
555 let result = state.compile(wasm_bytes).context("failed to compile");
556 to_exit_code(result)
557}
558
559/// Instantiate the Wasm benchmark module.
560#[unsafe(no_mangle)]
561pub extern "C" fn wasm_bench_instantiate(state: *mut c_void) -> ExitCode {
562 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
563 let result = state.instantiate().context("failed to instantiate");
564 to_exit_code(result)
565}
566
567/// Execute the Wasm benchmark module.
568#[unsafe(no_mangle)]
569pub extern "C" fn wasm_bench_execute(state: *mut c_void) -> ExitCode {
570 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
571 let result = state.execute().context("failed to execute");
572 to_exit_code(result)
573}
574
575/// Helper function for converting a Rust result to a C error code.
576///
577/// This will print an error indicating some information regarding the failure.
578fn to_exit_code<T>(result: impl Into<Result<T>>) -> ExitCode {
579 match result.into() {
580 Ok(_) => OK,
581 Err(error) => {
582 eprintln!("{error:?}");
583 ERR
584 }
585 }
586}
587
588/// This structure contains the actual Rust implementation of the state required
589/// to manage the Wasmtime engine between calls.
590struct BenchState {
591 linker: Linker<HostState>,
592 component_linker: wasmtime::component::Linker<HostState>,
593 compilation_timer: *mut u8,
594 compilation_start: extern "C" fn(*mut u8),
595 compilation_end: extern "C" fn(*mut u8),
596 instantiation_timer: *mut u8,
597 instantiation_start: extern "C" fn(*mut u8),
598 instantiation_end: extern "C" fn(*mut u8),
599 make_wasi_cx: Box<dyn FnMut() -> Result<WasiP1Ctx>>,
600 module: Option<Module>,
601 component: Option<wasmtime::component::Component>,
602 store_and_instance: Option<(Store<HostState>, Instance)>,
603 epoch_interruption: bool,
604 fuel: Option<u64>,
605}
606
607enum Instance {
608 Core(wasmtime::Instance),
609 Component(wasmtime::component::Instance),
610}
611
612struct HostState {
613 wasi: WasiP1Ctx,
614 #[cfg(feature = "wasi-nn")]
615 wasi_nn: wasmtime_wasi_nn::witx::WasiNnCtx,
616}
617
618impl WasiView for HostState {
619 fn ctx(&mut self) -> WasiCtxView<'_> {
620 self.wasi.ctx()
621 }
622}
623
624impl BenchState {
625 fn new(
626 mut options: CommonOptions,
627 cache: bool,
628 compilation_timer: *mut u8,
629 compilation_start: extern "C" fn(*mut u8),
630 compilation_end: extern "C" fn(*mut u8),
631 instantiation_timer: *mut u8,
632 instantiation_start: extern "C" fn(*mut u8),
633 instantiation_end: extern "C" fn(*mut u8),
634 execution_timer: *mut u8,
635 execution_start: extern "C" fn(*mut u8),
636 execution_end: extern "C" fn(*mut u8),
637 make_wasi_cx: impl FnMut() -> Result<WasiP1Ctx> + 'static,
638 ) -> Result<Self> {
639 let mut config = options.config(None)?;
640
641 // NB: set this either way, since `CommonOptions` enables the cache by
642 // default and we must not break compilation benchmarks.
643 config.cache(if cache {
644 Some(wasmtime::Cache::from_file(None)?)
645 } else {
646 None
647 });
648
649 let engine = Engine::new(&config)?;
650 let mut linker = Linker::<HostState>::new(&engine);
651 let mut component_linker = wasmtime::component::Linker::<HostState>::new(&engine);
652
653 // Define the benchmarking start/end functions.
654 let execution_timer = unsafe {
655 // Safe because this bench API's contract requires that its methods
656 // are only ever called from a single thread.
657 UnsafeSendSync::new(execution_timer)
658 };
659 linker.func_wrap("bench", "start", move || {
660 execution_start(*execution_timer.get());
661 Ok(())
662 })?;
663 linker.func_wrap("bench", "end", move || {
664 execution_end(*execution_timer.get());
665 Ok(())
666 })?;
667
668 // Define the same benchmarking functions on the component linker.
669 let mut bench_instance = component_linker.instance("bench")?;
670 bench_instance.func_wrap(
671 "start",
672 move |_: wasmtime::StoreContextMut<'_, _>, (): ()| {
673 execution_start(*execution_timer.get());
674 Ok(())
675 },
676 )?;
677 bench_instance.func_wrap("end", move |_: wasmtime::StoreContextMut<'_, _>, (): ()| {
678 execution_end(*execution_timer.get());
679 Ok(())
680 })?;
681
682 let epoch_interruption = options.wasm.epoch_interruption.unwrap_or(false);
683 let fuel = options.wasm.fuel;
684
685 if options.wasi.common != Some(false) {
686 wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |cx| &mut cx.wasi)?;
687 wasmtime_wasi::p2::add_to_linker_sync(&mut component_linker)?;
688 }
689
690 #[cfg(feature = "wasi-nn")]
691 if options.wasi.nn == Some(true) {
692 wasmtime_wasi_nn::witx::add_to_linker(&mut linker, |cx| &mut cx.wasi_nn)?;
693 }
694
695 Ok(Self {
696 linker,
697 component_linker,
698 compilation_timer,
699 compilation_start,
700 compilation_end,
701 instantiation_timer,
702 instantiation_start,
703 instantiation_end,
704 make_wasi_cx: Box::new(make_wasi_cx) as _,
705 module: None,
706 component: None,
707 store_and_instance: None,
708 epoch_interruption,
709 fuel,
710 })
711 }
712
713 fn compile(&mut self, bytes: &[u8]) -> Result<()> {
714 self.module = None;
715 self.component = None;
716
717 let mut builder = CodeBuilder::new(self.linker.engine());
718 builder.wasm_binary(bytes, None)?;
719
720 match builder.hint() {
721 Some(CodeHint::Component) => {
722 (self.compilation_start)(self.compilation_timer);
723 let component = builder.compile_component()?;
724 (self.compilation_end)(self.compilation_timer);
725 self.component = Some(component);
726 }
727 Some(CodeHint::Module) | None => {
728 (self.compilation_start)(self.compilation_timer);
729 let module = builder.compile_module()?;
730 (self.compilation_end)(self.compilation_timer);
731 self.module = Some(module);
732 }
733 }
734
735 Ok(())
736 }
737
738 fn instantiate(&mut self) -> Result<()> {
739 self.store_and_instance = None;
740
741 let host = HostState {
742 wasi: (self.make_wasi_cx)().context("failed to create a WASI context")?,
743 #[cfg(feature = "wasi-nn")]
744 wasi_nn: {
745 let (backends, registry) = wasmtime_wasi_nn::preload(&[])?;
746 wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry)
747 },
748 };
749
750 // NB: Start measuring instantiation time *after* we've created the
751 // WASI context, since that needs to do file I/O to setup
752 // stdin/stdout/stderr.
753 (self.instantiation_start)(self.instantiation_timer);
754
755 let mut store = Store::new(self.linker.engine(), host);
756 if self.epoch_interruption {
757 store.set_epoch_deadline(1);
758 }
759 if let Some(fuel) = self.fuel {
760 store.set_fuel(fuel).unwrap();
761 }
762
763 let instance = match &self.component {
764 Some(component) => {
765 Instance::Component(self.component_linker.instantiate(&mut store, component)?)
766 }
767 None => {
768 let module = self
769 .module
770 .as_ref()
771 .expect("compile the module before instantiating it");
772 Instance::Core(self.linker.instantiate(&mut store, &module)?)
773 }
774 };
775
776 (self.instantiation_end)(self.instantiation_timer);
777
778 self.store_and_instance = Some((store, instance));
779 Ok(())
780 }
781
782 fn execute(&mut self) -> Result<()> {
783 match self.store_and_instance.take() {
784 Some((mut store, Instance::Component(instance))) => {
785 let command =
786 wasmtime_wasi::p2::bindings::sync::Command::new(&mut store, &instance)?;
787 match command.wasi_cli_run().call_run(&mut store)? {
788 Ok(()) => Ok(()),
789 Err(()) => Err(format_err!("calling `run` failed")),
790 }
791 }
792 Some((mut store, Instance::Core(instance))) => {
793 let start_func = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
794 match start_func.call(&mut store, ()) {
795 Ok(_) => Ok(()),
796 Err(trap) => {
797 // Since _start will likely return by using the system `exit` call, we must
798 // check the trap code to see if it actually represents a successful exit.
799 if let Some(exit) = trap.downcast_ref::<I32Exit>() {
800 if exit.0 == 0 {
801 return Ok(());
802 }
803 }
804
805 Err(trap)
806 }
807 }
808 }
809 None => {
810 panic!("instantiate before execution")
811 }
812 }
813 }
814}