1mod unsafe_send_sync;
137
138use crate::unsafe_send_sync::UnsafeSendSync;
139use clap::Parser;
140use std::os::raw::{c_int, c_void};
141use std::slice;
142use std::{env, path::PathBuf};
143use wasmtime::component::ResourceTable;
144use wasmtime::{
145 CodeBuilder, CodeHint, Engine, Instance, Linker, Module, Result, Store, error::Context as _,
146 format_err,
147};
148use wasmtime_cli_flags::CommonOptions;
149use wasmtime_wasi::cli::{InputFile, OutputFile};
150use wasmtime_wasi::{DirPerms, FilePerms, I32Exit, WasiCtx, WasiCtxView, WasiView, p1::WasiP1Ctx};
151
152pub type ExitCode = c_int;
153pub const OK: ExitCode = 0;
154pub const ERR: ExitCode = -1;
155
156#[cfg(feature = "shuffling-allocator")]
160#[global_allocator]
161static ALLOC: shuffling_allocator::ShufflingAllocator<std::alloc::System> =
162 shuffling_allocator::wrap!(&std::alloc::System);
163
164#[repr(C)]
166pub struct WasmBenchConfig {
167 pub working_dir_ptr: *const u8,
169 pub working_dir_len: usize,
170
171 pub stdout_path_ptr: *const u8,
173 pub stdout_path_len: usize,
174
175 pub stderr_path_ptr: *const u8,
177 pub stderr_path_len: usize,
178
179 pub stdin_path_ptr: *const u8,
182 pub stdin_path_len: usize,
183
184 pub compilation_timer: *mut u8,
187 pub compilation_start: extern "C" fn(*mut u8),
188 pub compilation_end: extern "C" fn(*mut u8),
189
190 pub instantiation_timer: *mut u8,
193 pub instantiation_start: extern "C" fn(*mut u8),
194 pub instantiation_end: extern "C" fn(*mut u8),
195
196 pub execution_timer: *mut u8,
199 pub execution_start: extern "C" fn(*mut u8),
200 pub execution_end: extern "C" fn(*mut u8),
201
202 pub execution_flags_ptr: *const u8,
205 pub execution_flags_len: usize,
206}
207
208impl WasmBenchConfig {
209 fn working_dir(&self) -> Result<PathBuf> {
210 let working_dir =
211 unsafe { std::slice::from_raw_parts(self.working_dir_ptr, self.working_dir_len) };
212 let working_dir = std::str::from_utf8(working_dir)
213 .context("given working directory is not valid UTF-8")?;
214 Ok(working_dir.into())
215 }
216
217 fn stdout_path(&self) -> Result<PathBuf> {
218 let stdout_path =
219 unsafe { std::slice::from_raw_parts(self.stdout_path_ptr, self.stdout_path_len) };
220 let stdout_path =
221 std::str::from_utf8(stdout_path).context("given stdout path is not valid UTF-8")?;
222 Ok(stdout_path.into())
223 }
224
225 fn stderr_path(&self) -> Result<PathBuf> {
226 let stderr_path =
227 unsafe { std::slice::from_raw_parts(self.stderr_path_ptr, self.stderr_path_len) };
228 let stderr_path =
229 std::str::from_utf8(stderr_path).context("given stderr path is not valid UTF-8")?;
230 Ok(stderr_path.into())
231 }
232
233 fn stdin_path(&self) -> Result<Option<PathBuf>> {
234 if self.stdin_path_ptr.is_null() {
235 return Ok(None);
236 }
237
238 let stdin_path =
239 unsafe { std::slice::from_raw_parts(self.stdin_path_ptr, self.stdin_path_len) };
240 let stdin_path =
241 std::str::from_utf8(stdin_path).context("given stdin path is not valid UTF-8")?;
242 Ok(Some(stdin_path.into()))
243 }
244
245 fn execution_flags(&self) -> Result<CommonOptions> {
246 let flags = if self.execution_flags_ptr.is_null() {
247 ""
248 } else {
249 let execution_flags = unsafe {
250 std::slice::from_raw_parts(self.execution_flags_ptr, self.execution_flags_len)
251 };
252 std::str::from_utf8(execution_flags)
253 .context("given execution flags string is not valid UTF-8")?
254 };
255 let options = CommonOptions::try_parse_from(
256 ["wasmtime"]
257 .into_iter()
258 .chain(flags.split(' ').filter(|s| !s.is_empty())),
259 )
260 .context("failed to parse options")?;
261 Ok(options)
262 }
263}
264
265#[unsafe(no_mangle)]
273pub extern "C" fn wasm_bench_create(
274 config: WasmBenchConfig,
275 out_bench_ptr: *mut *mut c_void,
276) -> ExitCode {
277 let result = (|| -> Result<_> {
278 let working_dir = config.working_dir()?;
280 let stdout_path = config.stdout_path()?;
281 let stderr_path = config.stderr_path()?;
282 let stdin_path = config.stdin_path()?;
283 let options = config.execution_flags()?;
284
285 let working_dir2 = working_dir.clone();
287 let stdout_path2 = stdout_path.clone();
288 let stderr_path2 = stderr_path.clone();
289 let stdin_path2 = stdin_path.clone();
290
291 let state = Box::new(BenchState::new(
292 options,
293 config.compilation_timer,
294 config.compilation_start,
295 config.compilation_end,
296 config.instantiation_timer,
297 config.instantiation_start,
298 config.instantiation_end,
299 config.execution_timer,
300 config.execution_start,
301 config.execution_end,
302 move || {
303 let mut cx = WasiCtx::builder();
304
305 let stdout = std::fs::File::create(&stdout_path)
306 .with_context(|| format!("failed to create {}", stdout_path.display()))?;
307 cx.stdout(OutputFile::new(stdout));
308
309 let stderr = std::fs::File::create(&stderr_path)
310 .with_context(|| format!("failed to create {}", stderr_path.display()))?;
311 cx.stderr(OutputFile::new(stderr));
312
313 if let Some(stdin_path) = &stdin_path {
314 let stdin = std::fs::File::open(stdin_path)
315 .with_context(|| format!("failed to open {}", stdin_path.display()))?;
316 cx.stdin(InputFile::new(stdin));
317 }
318
319 cx.preopened_dir(working_dir.clone(), ".", DirPerms::READ, FilePerms::READ)?;
322
323 if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") {
326 cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val);
327 }
328
329 Ok(cx.build_p1())
330 },
331 move || {
332 let mut cx = WasiCtx::builder();
333
334 let stdout = std::fs::File::create(&stdout_path2)
335 .with_context(|| format!("failed to create {}", stdout_path2.display()))?;
336 cx.stdout(OutputFile::new(stdout));
337
338 let stderr = std::fs::File::create(&stderr_path2)
339 .with_context(|| format!("failed to create {}", stderr_path2.display()))?;
340 cx.stderr(OutputFile::new(stderr));
341
342 if let Some(stdin_path) = &stdin_path2 {
343 let stdin = std::fs::File::open(stdin_path)
344 .with_context(|| format!("failed to open {}", stdin_path.display()))?;
345 cx.stdin(InputFile::new(stdin));
346 }
347
348 cx.preopened_dir(working_dir2.clone(), ".", DirPerms::READ, FilePerms::READ)?;
349
350 if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") {
351 cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val);
352 }
353
354 Ok(ComponentHostState {
355 wasi: cx.build(),
356 table: ResourceTable::new(),
357 })
358 },
359 )?);
360 Ok(Box::into_raw(state) as _)
361 })();
362
363 if let Ok(bench_ptr) = result {
364 unsafe {
365 assert!(!out_bench_ptr.is_null());
366 *out_bench_ptr = bench_ptr;
367 }
368 }
369
370 to_exit_code(result.map(|_| ()))
371}
372
373#[unsafe(no_mangle)]
375pub extern "C" fn wasm_bench_free(state: *mut c_void) {
376 assert!(!state.is_null());
377 unsafe {
378 drop(Box::from_raw(state as *mut BenchState));
379 }
380}
381
382#[unsafe(no_mangle)]
384pub extern "C" fn wasm_bench_compile(
385 state: *mut c_void,
386 wasm_bytes: *const u8,
387 wasm_bytes_length: usize,
388) -> ExitCode {
389 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
390 let wasm_bytes = unsafe { slice::from_raw_parts(wasm_bytes, wasm_bytes_length) };
391 let result = state.compile(wasm_bytes).context("failed to compile");
392 to_exit_code(result)
393}
394
395#[unsafe(no_mangle)]
397pub extern "C" fn wasm_bench_instantiate(state: *mut c_void) -> ExitCode {
398 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
399 let result = state.instantiate().context("failed to instantiate");
400 to_exit_code(result)
401}
402
403#[unsafe(no_mangle)]
405pub extern "C" fn wasm_bench_execute(state: *mut c_void) -> ExitCode {
406 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
407 let result = state.execute().context("failed to execute");
408 to_exit_code(result)
409}
410
411fn to_exit_code<T>(result: impl Into<Result<T>>) -> ExitCode {
415 match result.into() {
416 Ok(_) => OK,
417 Err(error) => {
418 eprintln!("{error:?}");
419 ERR
420 }
421 }
422}
423
424struct BenchState {
427 linker: Linker<HostState>,
428 component_linker: wasmtime::component::Linker<ComponentHostState>,
429 compilation_timer: *mut u8,
430 compilation_start: extern "C" fn(*mut u8),
431 compilation_end: extern "C" fn(*mut u8),
432 instantiation_timer: *mut u8,
433 instantiation_start: extern "C" fn(*mut u8),
434 instantiation_end: extern "C" fn(*mut u8),
435 make_wasi_cx: Box<dyn FnMut() -> Result<WasiP1Ctx>>,
436 make_component_wasi_cx: Box<dyn FnMut() -> Result<ComponentHostState>>,
437 module: Option<Module>,
438 component: Option<wasmtime::component::Component>,
439 store_and_instance: Option<(Store<HostState>, Instance)>,
440 component_store_and_instance:
441 Option<(Store<ComponentHostState>, wasmtime::component::Instance)>,
442 epoch_interruption: bool,
443 fuel: Option<u64>,
444}
445
446struct HostState {
447 wasi: WasiP1Ctx,
448 #[cfg(feature = "wasi-nn")]
449 wasi_nn: wasmtime_wasi_nn::witx::WasiNnCtx,
450}
451
452struct ComponentHostState {
453 wasi: WasiCtx,
454 table: ResourceTable,
455}
456
457impl WasiView for ComponentHostState {
458 fn ctx(&mut self) -> WasiCtxView<'_> {
459 WasiCtxView {
460 ctx: &mut self.wasi,
461 table: &mut self.table,
462 }
463 }
464}
465
466impl BenchState {
467 fn new(
468 mut options: CommonOptions,
469 compilation_timer: *mut u8,
470 compilation_start: extern "C" fn(*mut u8),
471 compilation_end: extern "C" fn(*mut u8),
472 instantiation_timer: *mut u8,
473 instantiation_start: extern "C" fn(*mut u8),
474 instantiation_end: extern "C" fn(*mut u8),
475 execution_timer: *mut u8,
476 execution_start: extern "C" fn(*mut u8),
477 execution_end: extern "C" fn(*mut u8),
478 make_wasi_cx: impl FnMut() -> Result<WasiP1Ctx> + 'static,
479 make_component_wasi_cx: impl FnMut() -> Result<ComponentHostState> + 'static,
480 ) -> Result<Self> {
481 let mut config = options.config(None)?;
482 config.cache(None);
484 let engine = Engine::new(&config)?;
485 let mut linker = Linker::<HostState>::new(&engine);
486 let mut component_linker = wasmtime::component::Linker::<ComponentHostState>::new(&engine);
487
488 let execution_timer = unsafe {
490 UnsafeSendSync::new(execution_timer)
493 };
494 linker.func_wrap("bench", "start", move || {
495 execution_start(*execution_timer.get());
496 Ok(())
497 })?;
498 linker.func_wrap("bench", "end", move || {
499 execution_end(*execution_timer.get());
500 Ok(())
501 })?;
502
503 let mut bench_instance = component_linker.instance("bench")?;
505 bench_instance.func_wrap(
506 "start",
507 move |_: wasmtime::StoreContextMut<'_, ComponentHostState>, (): ()| {
508 execution_start(*execution_timer.get());
509 Ok(())
510 },
511 )?;
512 bench_instance.func_wrap(
513 "end",
514 move |_: wasmtime::StoreContextMut<'_, ComponentHostState>, (): ()| {
515 execution_end(*execution_timer.get());
516 Ok(())
517 },
518 )?;
519
520 let epoch_interruption = options.wasm.epoch_interruption.unwrap_or(false);
521 let fuel = options.wasm.fuel;
522
523 if options.wasi.common != Some(false) {
524 wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |cx| &mut cx.wasi)?;
525 wasmtime_wasi::p2::add_to_linker_sync(&mut component_linker)?;
526 }
527
528 #[cfg(feature = "wasi-nn")]
529 if options.wasi.nn == Some(true) {
530 wasmtime_wasi_nn::witx::add_to_linker(&mut linker, |cx| &mut cx.wasi_nn)?;
531 }
532
533 Ok(Self {
534 linker,
535 component_linker,
536 compilation_timer,
537 compilation_start,
538 compilation_end,
539 instantiation_timer,
540 instantiation_start,
541 instantiation_end,
542 make_wasi_cx: Box::new(make_wasi_cx) as _,
543 make_component_wasi_cx: Box::new(make_component_wasi_cx) as _,
544 module: None,
545 component: None,
546 store_and_instance: None,
547 component_store_and_instance: None,
548 epoch_interruption,
549 fuel,
550 })
551 }
552
553 fn compile(&mut self, bytes: &[u8]) -> Result<()> {
554 self.module = None;
555 self.component = None;
556
557 let mut builder = CodeBuilder::new(self.linker.engine());
558 builder.wasm_binary(bytes, None)?;
559
560 match builder.hint() {
561 Some(CodeHint::Component) => {
562 (self.compilation_start)(self.compilation_timer);
563 let component = builder.compile_component()?;
564 (self.compilation_end)(self.compilation_timer);
565 self.component = Some(component);
566 }
567 Some(CodeHint::Module) | None => {
568 (self.compilation_start)(self.compilation_timer);
569 let module = builder.compile_module()?;
570 (self.compilation_end)(self.compilation_timer);
571 self.module = Some(module);
572 }
573 }
574
575 Ok(())
576 }
577
578 fn instantiate(&mut self) -> Result<()> {
579 self.store_and_instance = None;
580 self.component_store_and_instance = None;
581
582 if let Some(component) = &self.component {
583 let host = (self.make_component_wasi_cx)()
584 .context("failed to create a WASI context for component")?;
585
586 (self.instantiation_start)(self.instantiation_timer);
590
591 let mut store = Store::new(self.component_linker.engine(), host);
592 if self.epoch_interruption {
593 store.set_epoch_deadline(1);
594 }
595 if let Some(fuel) = self.fuel {
596 store.set_fuel(fuel).unwrap();
597 }
598
599 let instance = self.component_linker.instantiate(&mut store, component)?;
600
601 (self.instantiation_end)(self.instantiation_timer);
602
603 self.component_store_and_instance = Some((store, instance));
604 } else {
605 let module = self
606 .module
607 .as_ref()
608 .expect("compile the module before instantiating it");
609
610 let host = HostState {
611 wasi: (self.make_wasi_cx)().context("failed to create a WASI context")?,
612 #[cfg(feature = "wasi-nn")]
613 wasi_nn: {
614 let (backends, registry) = wasmtime_wasi_nn::preload(&[])?;
615 wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry)
616 },
617 };
618
619 (self.instantiation_start)(self.instantiation_timer);
623
624 let mut store = Store::new(self.linker.engine(), host);
625 if self.epoch_interruption {
626 store.set_epoch_deadline(1);
627 }
628 if let Some(fuel) = self.fuel {
629 store.set_fuel(fuel).unwrap();
630 }
631
632 let instance = self.linker.instantiate(&mut store, &module)?;
633
634 (self.instantiation_end)(self.instantiation_timer);
635
636 self.store_and_instance = Some((store, instance));
637 }
638 Ok(())
639 }
640
641 fn execute(&mut self) -> Result<()> {
642 if let Some((mut store, instance)) = self.component_store_and_instance.take() {
643 let command = wasmtime_wasi::p2::bindings::sync::Command::new(&mut store, &instance)?;
644 match command.wasi_cli_run().call_run(&mut store)? {
645 Ok(()) => Ok(()),
646 Err(()) => Err(format_err!("calling `run` failed")),
647 }
648 } else {
649 let (mut store, instance) = self
650 .store_and_instance
651 .take()
652 .expect("instantiate the module before executing it");
653
654 let start_func = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
655 match start_func.call(&mut store, ()) {
656 Ok(_) => Ok(()),
657 Err(trap) => {
658 if let Some(exit) = trap.downcast_ref::<I32Exit>() {
661 if exit.0 == 0 {
662 return Ok(());
663 }
664 }
665
666 Err(trap)
667 }
668 }
669 }
670 }
671}