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::{
144 CodeBuilder, CodeHint, Engine, Linker, Module, Result, Store, error::Context as _, format_err,
145};
146use wasmtime_cli_flags::CommonOptions;
147use wasmtime_wasi::cli::{InputFile, OutputFile};
148use wasmtime_wasi::{DirPerms, FilePerms, I32Exit, WasiCtx, WasiCtxView, WasiView, p1::WasiP1Ctx};
149
150pub type ExitCode = c_int;
151pub const OK: ExitCode = 0;
152pub const ERR: ExitCode = -1;
153
154#[cfg(feature = "shuffling-allocator")]
158#[global_allocator]
159static ALLOC: shuffling_allocator::ShufflingAllocator<std::alloc::System> =
160 shuffling_allocator::wrap!(&std::alloc::System);
161
162#[repr(C)]
164pub struct WasmBenchConfig {
165 pub working_dir_ptr: *const u8,
167 pub working_dir_len: usize,
168
169 pub stdout_path_ptr: *const u8,
171 pub stdout_path_len: usize,
172
173 pub stderr_path_ptr: *const u8,
175 pub stderr_path_len: usize,
176
177 pub stdin_path_ptr: *const u8,
180 pub stdin_path_len: usize,
181
182 pub compilation_timer: *mut u8,
185 pub compilation_start: extern "C" fn(*mut u8),
186 pub compilation_end: extern "C" fn(*mut u8),
187
188 pub instantiation_timer: *mut u8,
191 pub instantiation_start: extern "C" fn(*mut u8),
192 pub instantiation_end: extern "C" fn(*mut u8),
193
194 pub execution_timer: *mut u8,
197 pub execution_start: extern "C" fn(*mut u8),
198 pub execution_end: extern "C" fn(*mut u8),
199
200 pub execution_flags_ptr: *const u8,
203 pub execution_flags_len: usize,
204}
205
206impl WasmBenchConfig {
207 fn working_dir(&self) -> Result<PathBuf> {
208 let working_dir =
209 unsafe { std::slice::from_raw_parts(self.working_dir_ptr, self.working_dir_len) };
210 let working_dir = std::str::from_utf8(working_dir)
211 .context("given working directory is not valid UTF-8")?;
212 Ok(working_dir.into())
213 }
214
215 fn stdout_path(&self) -> Result<PathBuf> {
216 let stdout_path =
217 unsafe { std::slice::from_raw_parts(self.stdout_path_ptr, self.stdout_path_len) };
218 let stdout_path =
219 std::str::from_utf8(stdout_path).context("given stdout path is not valid UTF-8")?;
220 Ok(stdout_path.into())
221 }
222
223 fn stderr_path(&self) -> Result<PathBuf> {
224 let stderr_path =
225 unsafe { std::slice::from_raw_parts(self.stderr_path_ptr, self.stderr_path_len) };
226 let stderr_path =
227 std::str::from_utf8(stderr_path).context("given stderr path is not valid UTF-8")?;
228 Ok(stderr_path.into())
229 }
230
231 fn stdin_path(&self) -> Result<Option<PathBuf>> {
232 if self.stdin_path_ptr.is_null() {
233 return Ok(None);
234 }
235
236 let stdin_path =
237 unsafe { std::slice::from_raw_parts(self.stdin_path_ptr, self.stdin_path_len) };
238 let stdin_path =
239 std::str::from_utf8(stdin_path).context("given stdin path is not valid UTF-8")?;
240 Ok(Some(stdin_path.into()))
241 }
242
243 fn execution_flags(&self) -> Result<CommonOptions> {
244 let flags = if self.execution_flags_ptr.is_null() {
245 ""
246 } else {
247 let execution_flags = unsafe {
248 std::slice::from_raw_parts(self.execution_flags_ptr, self.execution_flags_len)
249 };
250 std::str::from_utf8(execution_flags)
251 .context("given execution flags string is not valid UTF-8")?
252 };
253 let options = CommonOptions::try_parse_from(
254 ["wasmtime"]
255 .into_iter()
256 .chain(flags.split(' ').filter(|s| !s.is_empty())),
257 )
258 .context("failed to parse options")?;
259 Ok(options)
260 }
261}
262
263#[unsafe(no_mangle)]
271pub extern "C" fn wasm_bench_create(
272 config: WasmBenchConfig,
273 out_bench_ptr: *mut *mut c_void,
274) -> ExitCode {
275 let result = (|| -> Result<_> {
276 let working_dir = config.working_dir()?;
278 let stdout_path = config.stdout_path()?;
279 let stderr_path = config.stderr_path()?;
280 let stdin_path = config.stdin_path()?;
281 let options = config.execution_flags()?;
282
283 let state = Box::new(BenchState::new(
284 options,
285 config.compilation_timer,
286 config.compilation_start,
287 config.compilation_end,
288 config.instantiation_timer,
289 config.instantiation_start,
290 config.instantiation_end,
291 config.execution_timer,
292 config.execution_start,
293 config.execution_end,
294 move || {
295 let mut cx = WasiCtx::builder();
296
297 let stdout = std::fs::File::create(&stdout_path)
298 .with_context(|| format!("failed to create {}", stdout_path.display()))?;
299 cx.stdout(OutputFile::new(stdout));
300
301 let stderr = std::fs::File::create(&stderr_path)
302 .with_context(|| format!("failed to create {}", stderr_path.display()))?;
303 cx.stderr(OutputFile::new(stderr));
304
305 if let Some(stdin_path) = &stdin_path {
306 let stdin = std::fs::File::open(stdin_path)
307 .with_context(|| format!("failed to open {}", stdin_path.display()))?;
308 cx.stdin(InputFile::new(stdin));
309 }
310
311 cx.preopened_dir(working_dir.clone(), ".", DirPerms::READ, FilePerms::READ)?;
314
315 if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") {
318 cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val);
319 }
320
321 Ok(cx.build_p1())
322 },
323 )?);
324 Ok(Box::into_raw(state) as _)
325 })();
326
327 if let Ok(bench_ptr) = result {
328 unsafe {
329 assert!(!out_bench_ptr.is_null());
330 *out_bench_ptr = bench_ptr;
331 }
332 }
333
334 to_exit_code(result.map(|_| ()))
335}
336
337#[unsafe(no_mangle)]
339pub extern "C" fn wasm_bench_free(state: *mut c_void) {
340 assert!(!state.is_null());
341 unsafe {
342 drop(Box::from_raw(state as *mut BenchState));
343 }
344}
345
346#[unsafe(no_mangle)]
348pub extern "C" fn wasm_bench_compile(
349 state: *mut c_void,
350 wasm_bytes: *const u8,
351 wasm_bytes_length: usize,
352) -> ExitCode {
353 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
354 let wasm_bytes = unsafe { slice::from_raw_parts(wasm_bytes, wasm_bytes_length) };
355 let result = state.compile(wasm_bytes).context("failed to compile");
356 to_exit_code(result)
357}
358
359#[unsafe(no_mangle)]
361pub extern "C" fn wasm_bench_instantiate(state: *mut c_void) -> ExitCode {
362 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
363 let result = state.instantiate().context("failed to instantiate");
364 to_exit_code(result)
365}
366
367#[unsafe(no_mangle)]
369pub extern "C" fn wasm_bench_execute(state: *mut c_void) -> ExitCode {
370 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
371 let result = state.execute().context("failed to execute");
372 to_exit_code(result)
373}
374
375fn to_exit_code<T>(result: impl Into<Result<T>>) -> ExitCode {
379 match result.into() {
380 Ok(_) => OK,
381 Err(error) => {
382 eprintln!("{error:?}");
383 ERR
384 }
385 }
386}
387
388struct BenchState {
391 linker: Linker<HostState>,
392 component_linker: wasmtime::component::Linker<HostState>,
393 compilation_timer: *mut u8,
394 compilation_start: extern "C" fn(*mut u8),
395 compilation_end: extern "C" fn(*mut u8),
396 instantiation_timer: *mut u8,
397 instantiation_start: extern "C" fn(*mut u8),
398 instantiation_end: extern "C" fn(*mut u8),
399 make_wasi_cx: Box<dyn FnMut() -> Result<WasiP1Ctx>>,
400 module: Option<Module>,
401 component: Option<wasmtime::component::Component>,
402 store_and_instance: Option<(Store<HostState>, Instance)>,
403 epoch_interruption: bool,
404 fuel: Option<u64>,
405}
406
407enum Instance {
408 Core(wasmtime::Instance),
409 Component(wasmtime::component::Instance),
410}
411
412struct HostState {
413 wasi: WasiP1Ctx,
414 #[cfg(feature = "wasi-nn")]
415 wasi_nn: wasmtime_wasi_nn::witx::WasiNnCtx,
416}
417
418impl WasiView for HostState {
419 fn ctx(&mut self) -> WasiCtxView<'_> {
420 self.wasi.ctx()
421 }
422}
423
424impl BenchState {
425 fn new(
426 mut options: CommonOptions,
427 compilation_timer: *mut u8,
428 compilation_start: extern "C" fn(*mut u8),
429 compilation_end: extern "C" fn(*mut u8),
430 instantiation_timer: *mut u8,
431 instantiation_start: extern "C" fn(*mut u8),
432 instantiation_end: extern "C" fn(*mut u8),
433 execution_timer: *mut u8,
434 execution_start: extern "C" fn(*mut u8),
435 execution_end: extern "C" fn(*mut u8),
436 make_wasi_cx: impl FnMut() -> Result<WasiP1Ctx> + 'static,
437 ) -> Result<Self> {
438 let mut config = options.config(None)?;
439 config.cache(None);
441 let engine = Engine::new(&config)?;
442 let mut linker = Linker::<HostState>::new(&engine);
443 let mut component_linker = wasmtime::component::Linker::<HostState>::new(&engine);
444
445 let execution_timer = unsafe {
447 UnsafeSendSync::new(execution_timer)
450 };
451 linker.func_wrap("bench", "start", move || {
452 execution_start(*execution_timer.get());
453 Ok(())
454 })?;
455 linker.func_wrap("bench", "end", move || {
456 execution_end(*execution_timer.get());
457 Ok(())
458 })?;
459
460 let mut bench_instance = component_linker.instance("bench")?;
462 bench_instance.func_wrap(
463 "start",
464 move |_: wasmtime::StoreContextMut<'_, _>, (): ()| {
465 execution_start(*execution_timer.get());
466 Ok(())
467 },
468 )?;
469 bench_instance.func_wrap("end", move |_: wasmtime::StoreContextMut<'_, _>, (): ()| {
470 execution_end(*execution_timer.get());
471 Ok(())
472 })?;
473
474 let epoch_interruption = options.wasm.epoch_interruption.unwrap_or(false);
475 let fuel = options.wasm.fuel;
476
477 if options.wasi.common != Some(false) {
478 wasmtime_wasi::p1::add_to_linker_sync(&mut linker, |cx| &mut cx.wasi)?;
479 wasmtime_wasi::p2::add_to_linker_sync(&mut component_linker)?;
480 }
481
482 #[cfg(feature = "wasi-nn")]
483 if options.wasi.nn == Some(true) {
484 wasmtime_wasi_nn::witx::add_to_linker(&mut linker, |cx| &mut cx.wasi_nn)?;
485 }
486
487 Ok(Self {
488 linker,
489 component_linker,
490 compilation_timer,
491 compilation_start,
492 compilation_end,
493 instantiation_timer,
494 instantiation_start,
495 instantiation_end,
496 make_wasi_cx: Box::new(make_wasi_cx) as _,
497 module: None,
498 component: None,
499 store_and_instance: None,
500 epoch_interruption,
501 fuel,
502 })
503 }
504
505 fn compile(&mut self, bytes: &[u8]) -> Result<()> {
506 self.module = None;
507 self.component = None;
508
509 let mut builder = CodeBuilder::new(self.linker.engine());
510 builder.wasm_binary(bytes, None)?;
511
512 match builder.hint() {
513 Some(CodeHint::Component) => {
514 (self.compilation_start)(self.compilation_timer);
515 let component = builder.compile_component()?;
516 (self.compilation_end)(self.compilation_timer);
517 self.component = Some(component);
518 }
519 Some(CodeHint::Module) | None => {
520 (self.compilation_start)(self.compilation_timer);
521 let module = builder.compile_module()?;
522 (self.compilation_end)(self.compilation_timer);
523 self.module = Some(module);
524 }
525 }
526
527 Ok(())
528 }
529
530 fn instantiate(&mut self) -> Result<()> {
531 self.store_and_instance = None;
532
533 let host = HostState {
534 wasi: (self.make_wasi_cx)().context("failed to create a WASI context")?,
535 #[cfg(feature = "wasi-nn")]
536 wasi_nn: {
537 let (backends, registry) = wasmtime_wasi_nn::preload(&[])?;
538 wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry)
539 },
540 };
541
542 (self.instantiation_start)(self.instantiation_timer);
546
547 let mut store = Store::new(self.linker.engine(), host);
548 if self.epoch_interruption {
549 store.set_epoch_deadline(1);
550 }
551 if let Some(fuel) = self.fuel {
552 store.set_fuel(fuel).unwrap();
553 }
554
555 let instance = match &self.component {
556 Some(component) => {
557 Instance::Component(self.component_linker.instantiate(&mut store, component)?)
558 }
559 None => {
560 let module = self
561 .module
562 .as_ref()
563 .expect("compile the module before instantiating it");
564 Instance::Core(self.linker.instantiate(&mut store, &module)?)
565 }
566 };
567
568 (self.instantiation_end)(self.instantiation_timer);
569
570 self.store_and_instance = Some((store, instance));
571 Ok(())
572 }
573
574 fn execute(&mut self) -> Result<()> {
575 match self.store_and_instance.take() {
576 Some((mut store, Instance::Component(instance))) => {
577 let command =
578 wasmtime_wasi::p2::bindings::sync::Command::new(&mut store, &instance)?;
579 match command.wasi_cli_run().call_run(&mut store)? {
580 Ok(()) => Ok(()),
581 Err(()) => Err(format_err!("calling `run` failed")),
582 }
583 }
584 Some((mut store, Instance::Core(instance))) => {
585 let start_func = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
586 match start_func.call(&mut store, ()) {
587 Ok(_) => Ok(()),
588 Err(trap) => {
589 if let Some(exit) = trap.downcast_ref::<I32Exit>() {
592 if exit.0 == 0 {
593 return Ok(());
594 }
595 }
596
597 Err(trap)
598 }
599 }
600 }
601 None => {
602 panic!("instantiate before execution")
603 }
604 }
605 }
606}