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