1mod unsafe_send_sync;
137
138use crate::unsafe_send_sync::UnsafeSendSync;
139use anyhow::{Context, Result};
140use clap::Parser;
141use std::os::raw::{c_int, c_void};
142use std::slice;
143use std::{env, path::PathBuf};
144use wasi_common::{sync::WasiCtxBuilder, I32Exit, WasiCtx};
145use wasmtime::{Engine, Instance, Linker, Module, Store};
146use wasmtime_cli_flags::CommonOptions;
147
148pub type ExitCode = c_int;
149pub const OK: ExitCode = 0;
150pub const ERR: ExitCode = -1;
151
152#[cfg(feature = "shuffling-allocator")]
156#[global_allocator]
157static ALLOC: shuffling_allocator::ShufflingAllocator<std::alloc::System> =
158 shuffling_allocator::wrap!(&std::alloc::System);
159
160#[repr(C)]
162pub struct WasmBenchConfig {
163 pub working_dir_ptr: *const u8,
165 pub working_dir_len: usize,
166
167 pub stdout_path_ptr: *const u8,
169 pub stdout_path_len: usize,
170
171 pub stderr_path_ptr: *const u8,
173 pub stderr_path_len: usize,
174
175 pub stdin_path_ptr: *const u8,
178 pub stdin_path_len: usize,
179
180 pub compilation_timer: *mut u8,
183 pub compilation_start: extern "C" fn(*mut u8),
184 pub compilation_end: extern "C" fn(*mut u8),
185
186 pub instantiation_timer: *mut u8,
189 pub instantiation_start: extern "C" fn(*mut u8),
190 pub instantiation_end: extern "C" fn(*mut u8),
191
192 pub execution_timer: *mut u8,
195 pub execution_start: extern "C" fn(*mut u8),
196 pub execution_end: extern "C" fn(*mut u8),
197
198 pub execution_flags_ptr: *const u8,
201 pub execution_flags_len: usize,
202}
203
204impl WasmBenchConfig {
205 fn working_dir(&self) -> Result<PathBuf> {
206 let working_dir =
207 unsafe { std::slice::from_raw_parts(self.working_dir_ptr, self.working_dir_len) };
208 let working_dir = std::str::from_utf8(working_dir)
209 .context("given working directory is not valid UTF-8")?;
210 Ok(working_dir.into())
211 }
212
213 fn stdout_path(&self) -> Result<PathBuf> {
214 let stdout_path =
215 unsafe { std::slice::from_raw_parts(self.stdout_path_ptr, self.stdout_path_len) };
216 let stdout_path =
217 std::str::from_utf8(stdout_path).context("given stdout path is not valid UTF-8")?;
218 Ok(stdout_path.into())
219 }
220
221 fn stderr_path(&self) -> Result<PathBuf> {
222 let stderr_path =
223 unsafe { std::slice::from_raw_parts(self.stderr_path_ptr, self.stderr_path_len) };
224 let stderr_path =
225 std::str::from_utf8(stderr_path).context("given stderr path is not valid UTF-8")?;
226 Ok(stderr_path.into())
227 }
228
229 fn stdin_path(&self) -> Result<Option<PathBuf>> {
230 if self.stdin_path_ptr.is_null() {
231 return Ok(None);
232 }
233
234 let stdin_path =
235 unsafe { std::slice::from_raw_parts(self.stdin_path_ptr, self.stdin_path_len) };
236 let stdin_path =
237 std::str::from_utf8(stdin_path).context("given stdin path is not valid UTF-8")?;
238 Ok(Some(stdin_path.into()))
239 }
240
241 fn execution_flags(&self) -> Result<CommonOptions> {
242 let flags = if self.execution_flags_ptr.is_null() {
243 ""
244 } else {
245 let execution_flags = unsafe {
246 std::slice::from_raw_parts(self.execution_flags_ptr, self.execution_flags_len)
247 };
248 std::str::from_utf8(execution_flags)
249 .context("given execution flags string is not valid UTF-8")?
250 };
251 let options = CommonOptions::try_parse_from(
252 ["wasmtime"]
253 .into_iter()
254 .chain(flags.split(' ').filter(|s| !s.is_empty())),
255 )
256 .context("failed to parse options")?;
257 Ok(options)
258 }
259}
260
261#[unsafe(no_mangle)]
269pub extern "C" fn wasm_bench_create(
270 config: WasmBenchConfig,
271 out_bench_ptr: *mut *mut c_void,
272) -> ExitCode {
273 let result = (|| -> Result<_> {
274 let working_dir = config.working_dir()?;
275 let working_dir =
276 cap_std::fs::Dir::open_ambient_dir(&working_dir, cap_std::ambient_authority())
277 .with_context(|| {
278 format!(
279 "failed to preopen the working directory: {}",
280 working_dir.display(),
281 )
282 })?;
283
284 let stdout_path = config.stdout_path()?;
285 let stderr_path = config.stderr_path()?;
286 let stdin_path = config.stdin_path()?;
287 let options = config.execution_flags()?;
288
289 let state = Box::new(BenchState::new(
290 options,
291 config.compilation_timer,
292 config.compilation_start,
293 config.compilation_end,
294 config.instantiation_timer,
295 config.instantiation_start,
296 config.instantiation_end,
297 config.execution_timer,
298 config.execution_start,
299 config.execution_end,
300 move || {
301 let mut cx = WasiCtxBuilder::new();
302
303 let stdout = std::fs::File::create(&stdout_path)
304 .with_context(|| format!("failed to create {}", stdout_path.display()))?;
305 let stdout = cap_std::fs::File::from_std(stdout);
306 let stdout = wasi_common::sync::file::File::from_cap_std(stdout);
307 cx.stdout(Box::new(stdout));
308
309 let stderr = std::fs::File::create(&stderr_path)
310 .with_context(|| format!("failed to create {}", stderr_path.display()))?;
311 let stderr = cap_std::fs::File::from_std(stderr);
312 let stderr = wasi_common::sync::file::File::from_cap_std(stderr);
313 cx.stderr(Box::new(stderr));
314
315 if let Some(stdin_path) = &stdin_path {
316 let stdin = std::fs::File::open(stdin_path)
317 .with_context(|| format!("failed to open {}", stdin_path.display()))?;
318 let stdin = cap_std::fs::File::from_std(stdin);
319 let stdin = wasi_common::sync::file::File::from_cap_std(stdin);
320 cx.stdin(Box::new(stdin));
321 }
322
323 cx.preopened_dir(working_dir.try_clone()?, ".")?;
326
327 if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") {
330 cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val)?;
331 }
332
333 Ok(cx.build())
334 },
335 )?);
336 Ok(Box::into_raw(state) as _)
337 })();
338
339 if let Ok(bench_ptr) = result {
340 unsafe {
341 assert!(!out_bench_ptr.is_null());
342 *out_bench_ptr = bench_ptr;
343 }
344 }
345
346 to_exit_code(result.map(|_| ()))
347}
348
349#[unsafe(no_mangle)]
351pub extern "C" fn wasm_bench_free(state: *mut c_void) {
352 assert!(!state.is_null());
353 unsafe {
354 drop(Box::from_raw(state as *mut BenchState));
355 }
356}
357
358#[unsafe(no_mangle)]
360pub extern "C" fn wasm_bench_compile(
361 state: *mut c_void,
362 wasm_bytes: *const u8,
363 wasm_bytes_length: usize,
364) -> ExitCode {
365 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
366 let wasm_bytes = unsafe { slice::from_raw_parts(wasm_bytes, wasm_bytes_length) };
367 let result = state.compile(wasm_bytes).context("failed to compile");
368 to_exit_code(result)
369}
370
371#[unsafe(no_mangle)]
373pub extern "C" fn wasm_bench_instantiate(state: *mut c_void) -> ExitCode {
374 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
375 let result = state.instantiate().context("failed to instantiate");
376 to_exit_code(result)
377}
378
379#[unsafe(no_mangle)]
381pub extern "C" fn wasm_bench_execute(state: *mut c_void) -> ExitCode {
382 let state = unsafe { (state as *mut BenchState).as_mut().unwrap() };
383 let result = state.execute().context("failed to execute");
384 to_exit_code(result)
385}
386
387fn to_exit_code<T>(result: impl Into<Result<T>>) -> ExitCode {
391 match result.into() {
392 Ok(_) => OK,
393 Err(error) => {
394 eprintln!("{error:?}");
395 ERR
396 }
397 }
398}
399
400struct BenchState {
403 linker: Linker<HostState>,
404 compilation_timer: *mut u8,
405 compilation_start: extern "C" fn(*mut u8),
406 compilation_end: extern "C" fn(*mut u8),
407 instantiation_timer: *mut u8,
408 instantiation_start: extern "C" fn(*mut u8),
409 instantiation_end: extern "C" fn(*mut u8),
410 make_wasi_cx: Box<dyn FnMut() -> Result<WasiCtx>>,
411 module: Option<Module>,
412 store_and_instance: Option<(Store<HostState>, Instance)>,
413 epoch_interruption: bool,
414 fuel: Option<u64>,
415}
416
417struct HostState {
418 wasi: WasiCtx,
419 #[cfg(feature = "wasi-nn")]
420 wasi_nn: wasmtime_wasi_nn::witx::WasiNnCtx,
421}
422
423impl BenchState {
424 fn new(
425 mut options: CommonOptions,
426 compilation_timer: *mut u8,
427 compilation_start: extern "C" fn(*mut u8),
428 compilation_end: extern "C" fn(*mut u8),
429 instantiation_timer: *mut u8,
430 instantiation_start: extern "C" fn(*mut u8),
431 instantiation_end: extern "C" fn(*mut u8),
432 execution_timer: *mut u8,
433 execution_start: extern "C" fn(*mut u8),
434 execution_end: extern "C" fn(*mut u8),
435 make_wasi_cx: impl FnMut() -> Result<WasiCtx> + 'static,
436 ) -> Result<Self> {
437 let mut config = options.config(None)?;
438 config.disable_cache();
440 let engine = Engine::new(&config)?;
441 let mut linker = Linker::<HostState>::new(&engine);
442
443 let execution_timer = unsafe {
445 UnsafeSendSync::new(execution_timer)
448 };
449 linker.func_wrap("bench", "start", move || {
450 execution_start(*execution_timer.get());
451 Ok(())
452 })?;
453 linker.func_wrap("bench", "end", move || {
454 execution_end(*execution_timer.get());
455 Ok(())
456 })?;
457
458 let epoch_interruption = options.wasm.epoch_interruption.unwrap_or(false);
459 let fuel = options.wasm.fuel;
460
461 if options.wasi.common != Some(false) {
462 wasi_common::sync::add_to_linker(&mut linker, |cx| &mut cx.wasi)?;
463 }
464
465 #[cfg(feature = "wasi-nn")]
466 if options.wasi.nn == Some(true) {
467 wasmtime_wasi_nn::witx::add_to_linker(&mut linker, |cx| &mut cx.wasi_nn)?;
468 }
469
470 Ok(Self {
471 linker,
472 compilation_timer,
473 compilation_start,
474 compilation_end,
475 instantiation_timer,
476 instantiation_start,
477 instantiation_end,
478 make_wasi_cx: Box::new(make_wasi_cx) as _,
479 module: None,
480 store_and_instance: None,
481 epoch_interruption,
482 fuel,
483 })
484 }
485
486 fn compile(&mut self, bytes: &[u8]) -> Result<()> {
487 self.module = None;
488
489 (self.compilation_start)(self.compilation_timer);
490 let module = Module::from_binary(self.linker.engine(), bytes)?;
491 (self.compilation_end)(self.compilation_timer);
492
493 self.module = Some(module);
494 Ok(())
495 }
496
497 fn instantiate(&mut self) -> Result<()> {
498 self.store_and_instance = None;
499
500 let module = self
501 .module
502 .as_ref()
503 .expect("compile the module before instantiating it");
504
505 let host = HostState {
506 wasi: (self.make_wasi_cx)().context("failed to create a WASI context")?,
507 #[cfg(feature = "wasi-nn")]
508 wasi_nn: {
509 let (backends, registry) = wasmtime_wasi_nn::preload(&[])?;
510 wasmtime_wasi_nn::witx::WasiNnCtx::new(backends, registry)
511 },
512 };
513
514 (self.instantiation_start)(self.instantiation_timer);
518 let mut store = Store::new(self.linker.engine(), host);
519 if self.epoch_interruption {
520 store.set_epoch_deadline(1);
521 }
522 if let Some(fuel) = self.fuel {
523 store.set_fuel(fuel).unwrap();
524 }
525
526 let instance = self.linker.instantiate(&mut store, &module)?;
527 (self.instantiation_end)(self.instantiation_timer);
528
529 self.store_and_instance = Some((store, instance));
530 Ok(())
531 }
532
533 fn execute(&mut self) -> Result<()> {
534 let (mut store, instance) = self
535 .store_and_instance
536 .take()
537 .expect("instantiate the module before executing it");
538
539 let start_func = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
540 match start_func.call(&mut store, ()) {
541 Ok(_) => Ok(()),
542 Err(trap) => {
543 if let Some(exit) = trap.downcast_ref::<I32Exit>() {
546 if exit.0 == 0 {
547 return Ok(());
548 }
549 }
550
551 Err(trap)
552 }
553 }
554 }
555}