diff --git a/Makefile b/Makefile index 06c42a36f..ef4c9152e 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,12 @@ lind-boot: build-dir cargo build --manifest-path src/lind-boot/Cargo.toml --release cp src/lind-boot/target/release/lind-boot $(LINDBOOT_BIN) +.PHONY: lind-boot-perf +lind-boot-perf: build-dir + # Build lind-boot with low-overhead cycle counters enabled. + cargo build --manifest-path src/lind-boot/Cargo.toml --release --features lind_perf + cp src/lind-boot/target/release/lind-boot $(LINDBOOT_BIN) + .PHONY: lindfs lindfs: @for d in $(LINDFS_DIRS); do \ diff --git a/scripts/run_microbench.sh b/scripts/run_microbench.sh new file mode 100755 index 000000000..dbc826ad1 --- /dev/null +++ b/scripts/run_microbench.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Requires lind-boot to be built with the `lind_perf` feature. +# Use `make lind-boot-perf` for this. + +set -euo pipefail + + +# Check if we need to re-exec with sudo +if [[ $EUID -ne 0 ]]; then + # Not running as root, re-exec with sudo + exec sudo -E "$0" "$@" +fi + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="${SCRIPT_DIR%scripts}" +BENCH_ROOT="${REPO_ROOT}/tests/benchmarks" + +echo "Compiling Tests..." + +"${SCRIPT_DIR}/lind_compile" "${BENCH_ROOT}/libc_syscall.c" &>/dev/null && mv "${BENCH_ROOT}/libc_syscall.wasm" "${REPO_ROOT}/lindfs/" +"${SCRIPT_DIR}/lind_compile" "${BENCH_ROOT}/fdtables_syscall.c" &>/dev/null && mv "${BENCH_ROOT}/fdtables_syscall.wasm" "${REPO_ROOT}/lindfs/" +"${SCRIPT_DIR}/lind_compile" --compile-grate "${BENCH_ROOT}/grate_syscall.c" &>/dev/null && mv "${BENCH_ROOT}/grate_syscall.wasm" "${REPO_ROOT}/lindfs/" + +echo -en "\nLIBC Test\t" +sudo lind-boot --perf libc_syscall.wasm + +echo -en "\nFDTABLE Test\t" +sudo lind-boot --perf fdtables_syscall.wasm + +echo -en "\nGRATE Test\t" +sudo lind-boot --perf grate_syscall.wasm libc_syscall.wasm diff --git a/src/fdtables/Cargo.toml b/src/fdtables/Cargo.toml index 0250cb461..10e90e366 100644 --- a/src/fdtables/Cargo.toml +++ b/src/fdtables/Cargo.toml @@ -12,11 +12,15 @@ categories = ["os", "filesystem"] [dependencies] libc = "0.2" dashmap = { version = "5.1", features=["serde"] } +lind-perf = { path = "../lind-perf", optional = true } [dependencies.lazy_static] version = "1.0" features = ["spin_no_std"] +[features] +lind_perf = ["dep:lind-perf"] + [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"]} diff --git a/src/fdtables/src/dashmaparrayglobal.rs b/src/fdtables/src/dashmaparrayglobal.rs index 84e4121d2..ee15d5a36 100644 --- a/src/fdtables/src/dashmaparrayglobal.rs +++ b/src/fdtables/src/dashmaparrayglobal.rs @@ -3,6 +3,8 @@ // Static DashMap. Let's see if having the FDTableEntries be a static // array is any faster... +#[cfg(feature = "lind_perf")] +use crate::perf; use crate::threei; use dashmap::DashMap; @@ -393,33 +395,42 @@ lazy_static! { #[doc = include_str!("../docs/close_virtualfd.md")] pub fn close_virtualfd(cageid:u64, virtfd:u64) -> Result<(),threei::RetVal> { + #[cfg(feature = "lind_perf")] + let _close_vfd_scope = perf::enabled::CLOSE_VIRTUALFD.scope(); + + let ret = (|| { + // Below condition checks if the virtualfd is out of bounds and if yes it throws an error + // Note that this assumes that all virtualfd numbers returned < FD_PER_PROCESS_MAX + if virtfd >= FD_PER_PROCESS_MAX { + return Err(threei::Errno::EBADFD as u64); + } - // Below condition checks if the virtualfd is out of bounds and if yes it throws an error - // Note that this assumes that all virtualfd numbers returned < FD_PER_PROCESS_MAX - if virtfd >= FD_PER_PROCESS_MAX { - return Err(threei::Errno::EBADFD as u64); - } + assert!(FDTABLE.contains_key(&cageid),"Unknown cageid in fdtable access"); - assert!(FDTABLE.contains_key(&cageid),"Unknown cageid in fdtable access"); + // derefing this so I don't hold a lock and deadlock close handlers + let mut myfdrow = *FDTABLE.get_mut(&cageid).unwrap(); - // derefing this so I don't hold a lock and deadlock close handlers - let mut myfdrow = *FDTABLE.get_mut(&cageid).unwrap(); + if myfdrow[virtfd as usize].is_some() { + let entry = myfdrow[virtfd as usize]; - if myfdrow[virtfd as usize].is_some() { - let entry = myfdrow[virtfd as usize]; + // Zero out this entry before calling the close handler... + myfdrow[virtfd as usize] = None; - // Zero out this entry before calling the close handler... - myfdrow[virtfd as usize] = None; + // Re-insert the modified myfdrow since I've been modifying a copy + FDTABLE.insert(cageid, myfdrow.clone()); - // Re-insert the modified myfdrow since I've been modifying a copy - FDTABLE.insert(cageid, myfdrow.clone()); - - // always _decrement last as it may call the user handler... - _decrement_fdcount(entry.unwrap()); - return Ok(()); - } - Err(threei::Errno::EBADFD as u64) + // always _decrement last as it may call the user handler... + _decrement_fdcount(entry.unwrap()); + return Ok(()); + } + Err(threei::Errno::EBADFD as u64) + })(); + + #[cfg(feature = "lind_perf")] + std::hint::black_box(&_close_vfd_scope); + + ret } diff --git a/src/fdtables/src/lib.rs b/src/fdtables/src/lib.rs index c60241e46..af1ca2663 100644 --- a/src/fdtables/src/lib.rs +++ b/src/fdtables/src/lib.rs @@ -115,6 +115,10 @@ // This includes the specific implementation of the algorithm chosen. include!("current_impl"); +/// Enable lind_perf related features for benchmarking. +#[cfg(feature = "lind_perf")] +pub mod perf; + // This includes general constants and definitions for things that are // needed everywhere, like FDTableEntry. I use the * import here to flatten // the namespace so folks importing this have the symbols directly imported. diff --git a/src/fdtables/src/perf.rs b/src/fdtables/src/perf.rs new file mode 100644 index 000000000..d34dc03e8 --- /dev/null +++ b/src/fdtables/src/perf.rs @@ -0,0 +1,11 @@ +/// lind-perf related feature modules. +#[cfg(feature = "lind_perf")] +pub mod enabled { + use lind_perf::Counter; + + /// Define a counter for close_virtualfd + pub static CLOSE_VIRTUALFD: Counter = Counter::new("fdtables::close_virtualfd"); + + /// Define a list of all counters + pub static ALL_COUNTERS: &[&Counter] = &[&CLOSE_VIRTUALFD]; +} diff --git a/src/glibc/lind_syscall/lind_syscall.c b/src/glibc/lind_syscall/lind_syscall.c index f1abfe949..9fb683284 100644 --- a/src/glibc/lind_syscall/lind_syscall.c +++ b/src/glibc/lind_syscall/lind_syscall.c @@ -164,3 +164,40 @@ int copy_data_between_cages(uint64_t thiscage, uint64_t targetcage, uint64_t src 0 /* translate_errno=0: we want to return the raw result without errno translation */ ); } + +// --------------------------------------------------------------------------------------------------------------------- + +// Wrapper for LIBC_SYSCALL which is used to benchmark calls that end up calling the Linux kernel. +int libc_syscall() { + return make_threei_call( + LIBC_SYSCALL, + 0, + __lind_cageid, + __lind_cageid, + 0, __lind_cageid, + 0, __lind_cageid, + 0, __lind_cageid, + 0, __lind_cageid, + 0, __lind_cageid, + 0, __lind_cageid, + 1 + ); +} + +// Wrapper for FDTABLES_SYSCALL which is used to benchmark calls that do not call the Linux kernel but instead +// perform some internal processing. +int fdtable_syscall() { + return make_threei_call( + FDTABLE_SYSCALL, + 0, + __lind_cageid, + __lind_cageid, + -1, __lind_cageid, // This syscall mimics close(-1), so we enforce that the fd argument is set to -1. + 0, __lind_cageid, + 0, __lind_cageid, + 0, __lind_cageid, + 0, __lind_cageid, + 0, __lind_cageid, + 1 + ); +} diff --git a/src/glibc/lind_syscall/lind_syscall.h b/src/glibc/lind_syscall/lind_syscall.h index 07d30e660..531c4c366 100644 --- a/src/glibc/lind_syscall/lind_syscall.h +++ b/src/glibc/lind_syscall/lind_syscall.h @@ -40,4 +40,7 @@ int copy_data_between_cages(uint64_t thiscage, uint64_t targetcage, uint64_t destaddr, uint64_t destcage, uint64_t len, uint64_t copytype); +int libc_syscall(); +int fdt_syscall(); + #endif // _LIND_SYSCALL_H diff --git a/src/glibc/lind_syscall/lind_syscall_num.h b/src/glibc/lind_syscall/lind_syscall_num.h index 2605465ba..1a8b5bae1 100644 --- a/src/glibc/lind_syscall/lind_syscall_num.h +++ b/src/glibc/lind_syscall/lind_syscall_num.h @@ -121,5 +121,9 @@ #define REGISTER_HANDLER_SYSCALL 1001 #define COPY_DATA_BETWEEN_CAGES_SYSCALL 1002 +/* Special syscalls used for benchmarking */ +#define LIBC_SYSCALL 2001 +#define FDTABLE_SYSCALL 2002 + #endif /* _LIND_SYSCALL_NUM_H */ diff --git a/src/lind-boot/Cargo.toml b/src/lind-boot/Cargo.toml index 2b074de1e..c7692e02d 100644 --- a/src/lind-boot/Cargo.toml +++ b/src/lind-boot/Cargo.toml @@ -7,6 +7,12 @@ edition = "2024" disable_signals = [] secure = ["typemap/secure"] lind_debug = ["wasmtime-lind-common/lind_debug"] +lind_perf = [ + "dep:lind-perf", + "threei/lind_perf", + "rawposix/lind_perf", + "wasmtime-lind-common/lind_perf", +] [dependencies] wasmtime-lind-common = { path = "../wasmtime/crates/lind-common" } @@ -17,6 +23,7 @@ cage = { path = "../wasmtime/crates/cage" } threei = { path = "../wasmtime/crates/threei" } sysdefs = { path = "../wasmtime/crates/sysdefs" } typemap = { path = "../wasmtime/crates/typemap" } +fdtables = { path = "../wasmtime/crates/fdtables" } wasi-common = { path = "../wasmtime/crates/wasi-common", features = ["sync"] ,default-features = false } wasmtime-lind-3i = { path = "../wasmtime/crates/lind-3i" } wasmtime = { path = "../wasmtime/crates/wasmtime", features = ["cranelift", "pooling-allocator", "gc", "threads", "demangle", "addr2line"], default-features = false } @@ -28,3 +35,4 @@ anyhow = { version = "1.0.66", default-features = false } cap-std = { version = "3.4.2", default-features = false } clap = { version = "4", features = ["derive"] } cfg-if = "1.0" +lind-perf = { path = "../wasmtime/crates/lind-perf", optional = true } diff --git a/src/lind-boot/src/cli.rs b/src/lind-boot/src/cli.rs index 68df0d289..d41dde20f 100644 --- a/src/lind-boot/src/cli.rs +++ b/src/lind-boot/src/cli.rs @@ -39,6 +39,16 @@ pub struct CliOptions { /// cause the environment variable `FOO` to be inherited. #[arg(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)] pub vars: Vec<(String, Option)>, + + /// Run performance benchmark with CLOCK_GETTIME (requires the `lind_perf` feature) + #[cfg(feature = "lind_perf")] + #[arg(long)] + pub perf: bool, + + /// Run performance benchmarks with TSC (requires the `lind_perf` feature) + #[cfg(feature = "lind_perf")] + #[arg(long)] + pub perftsc: bool, } pub fn parse_env_var(s: &str) -> Result<(String, Option), String> { diff --git a/src/lind-boot/src/lind_wasmtime/execute.rs b/src/lind-boot/src/lind_wasmtime/execute.rs index 7953a7656..3fd8253e3 100644 --- a/src/lind-boot/src/lind_wasmtime/execute.rs +++ b/src/lind-boot/src/lind_wasmtime/execute.rs @@ -10,7 +10,7 @@ use sysdefs::constants::lind_platform_const::{RAWPOSIX_CAGEID, WASMTIME_CAGEID}; use threei::threei_const; use wasi_common::sync::WasiCtxBuilder; use wasmtime::{ - AsContextMut, Engine, Func, InstantiateType, Linker, Module, Precompiled, Store, Val, ValType, + AsContextMut, Engine, Func, InstantiateType, Linker, Module, Store, Val, ValType, WasmBacktraceDetails, }; use wasmtime_lind_3i::{VmCtxWrapper, init_vmctx_pool, rm_vmctx, set_vmctx, set_vmctx_thread}; @@ -18,6 +18,9 @@ use wasmtime_lind_multi_process::{CAGE_START_ID, LindCtx, THREAD_START_ID}; use wasmtime_lind_utils::LindCageManager; use wasmtime_wasi_threads::WasiThreadsCtx; +#[cfg(feature = "lind_perf")] +use crate::perf; + /// Boots the Lind + RawPOSIX + 3i runtime and executes the initial Wasm program /// in the first cage. /// @@ -376,6 +379,9 @@ fn load_main_module( cageid: u64, args: &[String], ) -> Result> { + #[cfg(feature = "lind_perf")] + let _perf_scope = perf::enabled::LOAD_MAIN_MODULE.scope(); + // todo: // I don't setup `epoch_handler` since it seems not being used by our previous implementation. // Not sure if this is related to our thread exit problem @@ -534,6 +540,9 @@ fn read_wasm_or_cwasm(engine: &Engine, path: &Path) -> Result { /// This function takes a Wasm function (Func) and a list of string arguments, parses the /// arguments into Wasm values based on expected types (ValType), and invokes the function fn invoke_func(store: &mut Store, func: Func, args: &[String]) -> Result> { + #[cfg(feature = "lind_perf")] + let _perf_scope = perf::enabled::INVOKE_FUNC.scope(); + let ty = func.ty(&store); if ty.params().len() > 0 { eprintln!( diff --git a/src/lind-boot/src/lind_wasmtime/trampoline.rs b/src/lind-boot/src/lind_wasmtime/trampoline.rs index 214b8b96f..46a951da6 100644 --- a/src/lind-boot/src/lind_wasmtime/trampoline.rs +++ b/src/lind-boot/src/lind_wasmtime/trampoline.rs @@ -6,6 +6,9 @@ use wasmtime::{Caller, Instance}; use wasmtime_lind_3i::{VmCtxWrapper, get_vmctx, set_vmctx}; use wasmtime_lind_multi_process; +#[cfg(feature = "lind_perf")] +use crate::perf; + /// The callback function registered with 3i uses a unified Wasm entry /// function as the single re-entry point into the Wasm executable. /// @@ -45,12 +48,19 @@ pub extern "C" fn grate_callback_trampoline( arg6: u64, arg6cageid: u64, ) -> i32 { + #[cfg(feature = "lind_perf")] + let _trampoline_scope = perf::enabled::GRATE_CALLBACK_TRAMPOLINE.scope(); + + #[cfg(feature = "lind_perf")] + let _get_vmctx_scope = perf::enabled::TRAMPOLINE_GET_VMCTX.scope(); let vmctx_wrapper: VmCtxWrapper = match get_vmctx(cageid) { Some(v) => v, None => { panic!("no VMContext found for cage_id {}", cageid); } }; + #[cfg(feature = "lind_perf")] + drop(_get_vmctx_scope); // Convert back to VMContext let opaque: *mut VMOpaqueContext = vmctx_wrapper.as_ptr() as *mut VMOpaqueContext; @@ -59,6 +69,8 @@ pub extern "C" fn grate_callback_trampoline( // Re-enter Wasmtime using the stored vmctx pointer let grate_ret = unsafe { + #[cfg(feature = "lind_perf")] + let _caller_scope = perf::enabled::TRAMPOLINE_CALLER_WITH.scope(); Caller::with(vmctx_raw, |caller: Caller<'_, HostCtx>| { let Caller { mut store, @@ -66,6 +78,8 @@ pub extern "C" fn grate_callback_trampoline( } = caller; // Resolve the unified entry function once per call + #[cfg(feature = "lind_perf")] + let _get_entry_scope = perf::enabled::TRAMPOLINE_GET_PASS_FPTR_TO_WT.scope(); let entry_func = instance .host_state() .downcast_ref::() @@ -73,6 +87,8 @@ pub extern "C" fn grate_callback_trampoline( .get_export(&mut store, "pass_fptr_to_wt") .and_then(|f| f.into_func()) .ok_or_else(|| anyhow!("missing export `pass_fptr_to_wt`"))?; + #[cfg(feature = "lind_perf")] + drop(_get_entry_scope); let typed_func = entry_func.typed::<( u64, @@ -92,7 +108,9 @@ pub extern "C" fn grate_callback_trampoline( ), i32>(&mut store)?; // Call the entry function with all arguments and in grate function pointer - typed_func.call( + #[cfg(feature = "lind_perf")] + let _call_scope = perf::enabled::TRAMPOLINE_TYPED_DISPATCH_CALL.scope(); + let call_res = typed_func.call( &mut store, ( in_grate_fn_ptr_u64, @@ -110,12 +128,17 @@ pub extern "C" fn grate_callback_trampoline( arg6, arg6cageid, ), - ) + ); + #[cfg(feature = "lind_perf")] + drop(_call_scope); + call_res }) .unwrap_or(threei_const::GRATE_ERR) }; // Push the vmctx back to the global pool set_vmctx(cageid, vmctx_wrapper); + #[cfg(feature = "lind_perf")] + std::hint::black_box(&_trampoline_scope); grate_ret } diff --git a/src/lind-boot/src/main.rs b/src/lind-boot/src/main.rs index 145ac20cb..0145e2c2f 100644 --- a/src/lind-boot/src/main.rs +++ b/src/lind-boot/src/main.rs @@ -1,11 +1,16 @@ mod cli; mod lind_wasmtime; +mod perf; use crate::{ cli::CliOptions, lind_wasmtime::{execute_wasmtime, precompile_module}, }; use clap::Parser; + +#[cfg(feature = "lind_perf")] +use lind_perf::TimerKind; + use rawposix::init::{rawposix_shutdown, rawposix_start}; /// Entry point of the lind-boot executable. @@ -21,7 +26,48 @@ use rawposix::init::{rawposix_shutdown, rawposix_start}; fn main() -> Result<(), Box> { let lindboot_cli = CliOptions::parse(); - // AOT-compile only — no runtime needed + // Entry point for a lind_perf enabled build. + // + // When run with --perf flags, it performs the required setup and teardown, along with running + // the inputted wasm benchmark multiple times (once per counter). + #[cfg(feature = "lind_perf")] + { + // Determine which timer to use. --perftsc => Rdtsc, --perf => Clock + let kind = if lindboot_cli.perftsc { + Some(TimerKind::Rdtsc) + } else if lindboot_cli.perf { + Some(TimerKind::Clock) + } else { + None + }; + + match kind { + Some(k) => { + // Initiate all counters + perf::enabled::init(k); + + // Iterate over all counters, enable one at a time, run the wasm module. + for name in perf::enabled::all_counter_names() { + perf::enabled::enable_one(name); + + rawposix_start(0); + + let _ = execute_wasmtime(lindboot_cli.clone()); + + rawposix_shutdown(); + } + + // Print the final report. + perf::enabled::report(); + + return Ok(()); + } + // In case neither --perf flag is set, fall back to default lind-boot behaviour. + None => {} + }; + } + + // AOT-compile only �~@~T no runtime needed if lindboot_cli.precompile { precompile_module(&lindboot_cli)?; return Ok(()); @@ -32,10 +78,12 @@ fn main() -> Result<(), Box> { // Execute with user-selected runtime. Can be switched to other runtime implementation // in the future (e.g.: MPK). - execute_wasmtime(lindboot_cli)?; + let run_result = execute_wasmtime(lindboot_cli.clone()); // after all cage exits, finalize the lind rawposix_shutdown(); + run_result?; + Ok(()) } diff --git a/src/lind-boot/src/perf.rs b/src/lind-boot/src/perf.rs new file mode 100644 index 000000000..8bee16701 --- /dev/null +++ b/src/lind-boot/src/perf.rs @@ -0,0 +1,95 @@ +/// lind-boot's perf file binds together every other module's perf file. +/// +/// This involves: +/// - Reading their COUNTERS +/// - Initializing them +/// - Combining all the COUNTERS into one list to iterate over and sequentially enable +/// - Printing a combined lind-perf report. +#[cfg(feature = "lind_perf")] +pub mod enabled { + use lind_perf::{Counter, TimerKind, enable_name, reset_all, set_timer}; + + // These are counters defined within lind-boot. + pub static READ_WASM_OR_CWASM: Counter = Counter::new("lind_boot::read_wasm_or_cwasm"); + pub static LOAD_MAIN_MODULE: Counter = Counter::new("lind_boot::load_main_module"); + pub static INVOKE_FUNC: Counter = Counter::new("lind_boot::invoke_func"); + pub static GRATE_CALLBACK_TRAMPOLINE: Counter = + Counter::new("lind_boot::grate_callback_trampoline"); + pub static TRAMPOLINE_GET_VMCTX: Counter = Counter::new("lind_boot::trampoline::get_vmctx"); + pub static TRAMPOLINE_CALLER_WITH: Counter = + Counter::new("lind_boot::trampoline::Caller::with"); + pub static TRAMPOLINE_GET_PASS_FPTR_TO_WT: Counter = + Counter::new("lind_boot::trampoline::get_pass_fptr_to_wt"); + pub static TRAMPOLINE_TYPED_DISPATCH_CALL: Counter = + Counter::new("lind_boot::trampoline::typed_dispatch_call"); + + pub static LIND_BOOT_COUNTERS: &[&Counter] = &[ + &READ_WASM_OR_CWASM, + &LOAD_MAIN_MODULE, + &INVOKE_FUNC, + &GRATE_CALLBACK_TRAMPOLINE, + &TRAMPOLINE_GET_VMCTX, + &TRAMPOLINE_CALLER_WITH, + &TRAMPOLINE_GET_PASS_FPTR_TO_WT, + &TRAMPOLINE_TYPED_DISPATCH_CALL, + ]; + + /// Initialize counters for all modules, involves setting the TimerKind and resetting the + /// counts. + pub fn init(kind: TimerKind) { + set_timer(LIND_BOOT_COUNTERS, kind); + set_timer(wasmtime_lind_common::perf::enabled::ALL_COUNTERS, kind); + set_timer(rawposix::perf::enabled::ALL_COUNTERS, kind); + set_timer(threei::perf::enabled::ALL_COUNTERS, kind); + set_timer(fdtables::perf::enabled::ALL_COUNTERS, kind); + + reset_all(LIND_BOOT_COUNTERS); + reset_all(wasmtime_lind_common::perf::enabled::ALL_COUNTERS); + reset_all(rawposix::perf::enabled::ALL_COUNTERS); + reset_all(threei::perf::enabled::ALL_COUNTERS); + reset_all(fdtables::perf::enabled::ALL_COUNTERS); + } + + /// Finds a counter by it's name and searches for it across modules to enable it. Disables all + /// other counters. + pub fn enable_one(name: &str) { + enable_name(LIND_BOOT_COUNTERS, name); + enable_name(wasmtime_lind_common::perf::enabled::ALL_COUNTERS, name); + enable_name(rawposix::perf::enabled::ALL_COUNTERS, name); + enable_name(threei::perf::enabled::ALL_COUNTERS, name); + enable_name(fdtables::perf::enabled::ALL_COUNTERS, name); + } + + /// Get a list of all counter names. + pub fn all_counter_names() -> Vec<&'static str> { + let mut names = Vec::new(); + names.extend(LIND_BOOT_COUNTERS.iter().map(|c| c.name)); + names.extend( + wasmtime_lind_common::perf::enabled::ALL_COUNTERS + .iter() + .map(|c| c.name), + ); + names.extend(threei::perf::enabled::ALL_COUNTERS.iter().map(|c| c.name)); + names.extend(rawposix::perf::enabled::ALL_COUNTERS.iter().map(|c| c.name)); + names.extend(fdtables::perf::enabled::ALL_COUNTERS.iter().map(|c| c.name)); + names + } + + /// Print a report for every module + pub fn report() { + lind_perf::report_header(format!("LIND-BOOT")); + lind_perf::report(LIND_BOOT_COUNTERS); + + lind_perf::report_header(format!("LIND-COMMON")); + lind_perf::report(wasmtime_lind_common::perf::enabled::ALL_COUNTERS); + + lind_perf::report_header(format!("THREE-I")); + lind_perf::report(threei::perf::enabled::ALL_COUNTERS); + + lind_perf::report_header(format!("RAWPOSIX")); + lind_perf::report(rawposix::perf::enabled::ALL_COUNTERS); + + lind_perf::report_header(format!("FDTABLES")); + lind_perf::report(fdtables::perf::enabled::ALL_COUNTERS); + } +} diff --git a/src/lind-perf/Cargo.lock b/src/lind-perf/Cargo.lock new file mode 100644 index 000000000..27c6f0a05 --- /dev/null +++ b/src/lind-perf/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "lind-perf" +version = "0.1.0" +dependencies = [ + "libc", +] diff --git a/src/lind-perf/Cargo.toml b/src/lind-perf/Cargo.toml new file mode 100644 index 000000000..023af9a6f --- /dev/null +++ b/src/lind-perf/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "lind-perf" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/lib.rs" + +[dependencies] +libc = "0.2" diff --git a/src/lind-perf/README.md b/src/lind-perf/README.md new file mode 100644 index 000000000..ee55dcaad --- /dev/null +++ b/src/lind-perf/README.md @@ -0,0 +1,112 @@ +# lind-perf + +`lind-perf` is a microbenchmarking library for lind-wasm. It generates timing reports for hot +paths in the syscall lifecycle by measuring the total time spent in specific functions across +modules. + +Sample output for running `close(-1)`: + +```bash +FDTABLE Test ................ +--------------------------------------------LIND-BOOT-------------------------------------------- +name calls total avg +------------------------------------------------------------------------------------------------- +lind_boot::load_main_module 1 111.482ms 111.482ms +lind_boot::invoke_func 1 111.282ms 111.282ms + +-------------------------------------------LIND-COMMON------------------------------------------- +name calls total avg +------------------------------------------------------------------------------------------------- +lind_common::add_to_linker::make-syscall 1000000 94.274ms 94.000ns + +---------------------------------------------THREEI---------------------------------------------- +name calls total avg +------------------------------------------------------------------------------------------------- +threei::make_syscall 1000000 90.815ms 90.000ns + +--------------------------------------------RAWPOSIX--------------------------------------------- +name calls total avg +------------------------------------------------------------------------------------------------- +rawposix::close_syscall 1000000 21.255ms 21.000ns + +--------------------------------------------FDTABLES--------------------------------------------- +name calls total avg +------------------------------------------------------------------------------------------------- +fdtables::close_virtualfd 1000000 14.372ms 14.000ns +``` + +## Building + +`lind-perf` is only included in the final binary if `--features lind_perf` is set during build. + +`make lind-boot-perf` is a shorthand for building a `release` version of `lind-boot` with `lind-perf` enabled. + +## Running Benchmarks + +`lind-perf` will generate a report for any module that is run using `lind-boot` with the +`--perf` or `--perftsc` flag. + +e.g. `sudo lind-boot --perf libc_syscall.wasm` + +Standard benchmarks can be run using: [`./scripts/run_microbench.sh`](../../scripts/run_microbench.sh) + +Flags: +- `--perf`: Uses the default Clock timer (nanoseconds) +- `--perftsc`: Uses the `rdtsc` timer (CPU cycles) + +## Internals + +### How the timer works +Each benchmark site is a `Counter`. A counter tracks: +- total elapsed time across calls +- number of calls + +Timing is scoped. The common pattern is: +1. Create a guard at the start of the function. +2. The guard records the start time immediately. +3. When the function returns, the guard is dropped and records the end time. +4. The elapsed time is added to the counter total and the call count increments. + +This means early returns are timed as well. If the guard is dropped before the work +finishes (e.g., because of a `return foo(...)` expression), the measurement will be too +small. Keep the guard alive until after the work: + +```rust +let _scope = perf::enabled::YOUR_COUNTER.scope(); +let ret = (|| { + // measured work + ... +})(); +std::hint::black_box(&_scope); // Tells Rust to be pessimistic about optimizing this variable. +ret +``` + +### Ensuring only one active timer +`lind-boot` runs the benchmark module once per counter. On each run it enables exactly one +counter and disables the rest, then prints a report. This avoids stacked measurement overhead +from multiple counters running at the same time. + +The logic for this can be seen in [`lind-boot/src/main.rs`](../lind-boot/src/main.rs) + +### Adding a new benchmark site +Suppose we want to add a new timer in `threei` for the `copy_data_between_cages` function. We will need to make the following changes: + +1. Add a counter in `src/threei/src/perf.rs` and include it in `ALL_COUNTERS`. +2. Add a scoped timer in `src/threei/src/threei.rs` at the top of the `copy_data_between_cages` function. +3. Keep the guard alive until after the measured work if the function has multiple return paths. This can be done by moving measured work into an unnamed scope, and using the `std::hint::black_box` to avoid the scope being optimized out early. + +In case we want to only benchmark a snippet of a function instead of the entire thing, we can `drop` the scope manually: + +```rust +let scope = perf::enabled::YOUR_COUNTER.scope(); +// measured snippet +drop(scope); +``` + +### Adding a new crate +Currently the crates that are supported are `wasmtime_lind_common`, `fdtables`, `rawposix`, and `threei`. In order to add support for a new crate, the following changes are needed: + +1. Add a `perf.rs` module to the new crate and define counters plus `ALL_COUNTERS`. +2. Export `ALL_COUNTERS` from the crate’s `perf` module. +3. Add the crate’s counters to `lind-boot` enumeration, enable/reset, and reporting. +4. Rebuild `lind-boot` with `--features lind_perf` to include the new module. diff --git a/src/lind-perf/src/counter.rs b/src/lind-perf/src/counter.rs new file mode 100644 index 000000000..8fbd98111 --- /dev/null +++ b/src/lind-perf/src/counter.rs @@ -0,0 +1,142 @@ +use crate::timers::{default_timer_kind, read_end, read_start, TimerKind}; +use std::sync::atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering}; + +/// Counter stores information pertaining to a specific benchmarking site. +/// +/// Typically declared as `static` and imported in lind-boot. +pub struct Counter { + /// Counts the total number of CPU cycles or Nanoseconds spent. + pub cycles: AtomicU64, + /// Counts the total number of invocations. + pub calls: AtomicU64, + pub name: &'static str, + /// Only one Counter is globally enabled during a given run. + pub enabled: AtomicBool, + /// Stores TimerKind + timer: AtomicU8, +} + +impl Counter { + /// Create a counter with the default timer. + /// + /// Use this for most counters; change the timer only when you need cycles. + pub const fn new(name: &'static str) -> Self { + Self { + cycles: AtomicU64::new(0), + calls: AtomicU64::new(0), + name, + enabled: AtomicBool::new(false), + timer: AtomicU8::new(default_timer_kind() as u8), + } + } + + #[inline(always)] + /// Start a measurement for this counter. + /// + /// Returns `0` if the counter is disabled. + pub fn start(&self) -> u64 { + if self.enabled.load(Ordering::Relaxed) { + read_start(self.timer_kind()) + } else { + 0 + } + } + + #[inline(always)] + /// Record a measurement using the start timestamp. + /// + /// This is a no-op when the counter is disabled. + pub fn record(&self, start: u64) { + if self.enabled.load(Ordering::Relaxed) { + let elapsed = read_end(self.timer_kind()).saturating_sub(start); + // Add elapsed time to the counter. + self.cycles.fetch_add(elapsed, Ordering::Relaxed); + // Increment total calls. + self.calls.fetch_add(1, Ordering::Relaxed); + } + } + + #[inline(always)] + /// Create an RAII scope guard that records on drop. + pub fn scope(&self) -> Scope<'_> { + Scope { + counter: self, + start: self.start(), + } + } + + /// Enable this counter. + pub fn enable(&self) { + self.enabled.store(true, Ordering::Relaxed); + } + + /// Disable this counter. + pub fn disable(&self) { + self.enabled.store(false, Ordering::Relaxed); + } + + /// Reset totals for this counter. + pub fn reset(&self) { + self.cycles.store(0, Ordering::Relaxed); + self.calls.store(0, Ordering::Relaxed); + } + + /// Set the timer backend for this counter. + /// + /// This does not reset totals. + pub fn set_timer_kind(&self, kind: TimerKind) { + self.timer.store(kind as u8, Ordering::Relaxed); + } + + /// Read the current timer backend. + pub fn timer_kind(&self) -> TimerKind { + match self.timer.load(Ordering::Relaxed) { + 0 => TimerKind::Rdtsc, + _ => TimerKind::Clock, + } + } +} + +/// Scope is the implementation of the RAII guard which stores a Counter and the start time (when +/// it was introduced). +/// +/// Upon drop, it records for the Counter the total time elapsed. +pub struct Scope<'a> { + counter: &'a Counter, + start: u64, +} + +impl Drop for Scope<'_> { + fn drop(&mut self) { + self.counter.record(self.start); + } +} + +/// Reset all counters in a group. +pub fn reset_all(counters: &[&Counter]) { + for c in counters { + c.reset(); + } +} + +/// Set a timer for a counter group. +/// +/// This updates the backend for all counters in the slice. +pub fn set_timer(counters: &[&Counter], kind: TimerKind) { + for c in counters { + c.set_timer_kind(kind); + } +} + +/// Enable only the named counter in a group. +/// +/// All other counters in the slice are disabled. +pub fn enable_name(counters: &[&Counter], name: &str) { + for c in counters { + if c.name == name { + c.enable(); + } else { + c.disable(); + } + } +} diff --git a/src/lind-perf/src/lib.rs b/src/lind-perf/src/lib.rs new file mode 100644 index 000000000..927f28852 --- /dev/null +++ b/src/lind-perf/src/lib.rs @@ -0,0 +1,17 @@ +mod counter; +mod report; +mod timers; + +pub use counter::*; +pub use report::*; +pub use timers::*; + +/// Create a scope guard for a counter. +/// +/// This macro is a convenience wrapper around `Counter::scope()`. +#[macro_export] +macro_rules! scope { + ($counter:expr) => { + let _lind_perf_scope = $counter.scope(); + }; +} diff --git a/src/lind-perf/src/report.rs b/src/lind-perf/src/report.rs new file mode 100644 index 000000000..449be5eca --- /dev/null +++ b/src/lind-perf/src/report.rs @@ -0,0 +1,74 @@ +use crate::counter::Counter; +use crate::timers::{PrettyDuration, TimerKind}; +use std::sync::atomic::Ordering; +use std::time::Duration; + +/// Print a section header. +pub fn report_header(header: String) { + let pad = "-"; + let total = 97 - header.len(); + let left = total / 2; + let right = total - left; + + println!("\n{}{}{}", pad.repeat(left), header, pad.repeat(right),); +} + +/// Print a report for a counter group. +/// +/// The report is sorted by definition order, not by cost. +pub fn report(counters: &[&Counter]) { + // Tunable constants + const NAME_W: usize = 60; + const CALLS_W: usize = 10; + const NUM_W: usize = 12; + + let mut rows: Vec = Vec::new(); + + for c in counters { + let calls = c.calls.load(Ordering::Relaxed); + if calls == 0 { + continue; + } + + let cycles = match c.timer_kind() { + TimerKind::Rdtsc => format!("{:#?}", c.cycles.load(Ordering::Relaxed)), + TimerKind::Clock => format!( + "{}", + PrettyDuration(Duration::from_nanos(c.cycles.load(Ordering::Relaxed))) + ), + }; + + let avg = match c.timer_kind() { + TimerKind::Rdtsc => format!("{:#?}", c.cycles.load(Ordering::Relaxed) / calls), + TimerKind::Clock => format!( + "{}", + PrettyDuration(Duration::from_nanos( + c.cycles.load(Ordering::Relaxed) / calls + )) + ), + }; + + // {:CALLS_W$} {:>NUM_W$} {:>NUM_W$}", + c.name, calls, cycles, avg, + )); + } + + if rows.len() == 0 { + return; + } + + eprintln!( + "{:CALLS_W$} {:>NUM_W$} {:>NUM_W$}", + "name", "calls", "total", "avg", + ); + + eprintln!("{}", "-".repeat(NAME_W + CALLS_W + NUM_W * 2 + 3)); + + for i in rows { + eprintln!("{}", i); + } + + println!(""); +} diff --git a/src/lind-perf/src/timers.rs b/src/lind-perf/src/timers.rs new file mode 100644 index 000000000..5b6a91935 --- /dev/null +++ b/src/lind-perf/src/timers.rs @@ -0,0 +1,103 @@ +use std::time::Duration; + +/// Formats nanosecond totals for reports. Converts nanosecond input to larger units where +/// appropriate and truncates to 3 decimal points. +pub struct PrettyDuration(pub Duration); + +impl std::fmt::Display for PrettyDuration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let ns_f = self.0.as_nanos() as f64; + + let format = if ns_f < 1_000.0 { + format!("{:.3}ns", ns_f) + } else if ns_f < 1_000_000.0 { + format!("{:.3}µs", ns_f / 1_000.0) + } else if ns_f < 1_000_000_000.0 { + format!("{:.3}ms", ns_f / 1_000_000.0) + } else { + format!("{:.3}s", ns_f / 1_000_000_000.0) + }; + + write!(f, "{}", format) + } +} + +/// TimerKind defines the timer-backend to be used for benchmarks. We support two kinds of timers +/// currently, +/// +/// RDTSC: Time Stamp Counter that counts the number of CPU cycles that have elapsed. +/// Clock: Uses CLOCK_MONOTONIC_RAW to get the current time in nanoseconds. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TimerKind { + Rdtsc = 0, + Clock = 1, +} + +/// Get the default timer. +pub const fn default_timer_kind() -> TimerKind { + TimerKind::Clock +} + +/// Public functions to record start and end times depending on the TimerKind being used. +#[inline(always)] +pub fn read_start(kind: TimerKind) -> u64 { + match kind { + TimerKind::Rdtsc => rdtsc_start(), + TimerKind::Clock => clock_now(), + } +} + +#[inline(always)] +pub fn read_end(kind: TimerKind) -> u64 { + match kind { + TimerKind::Rdtsc => rdtsc_end(), + TimerKind::Clock => clock_now(), + } +} + +#[inline(always)] +fn rdtsc_start() -> u64 { + // RDTSC is only available of x864 machines. + // In case this API is not exposed, default back to Clock. + #[cfg(target_arch = "x86_64")] + unsafe { + // From Intel's documentation : + // + // Perform a serializing operation on all load-from-memory instructions that were + // issued prior to this instruction. Guarantees that every load instruction that precedes, + // in program order, is globally visible before any load instruction which follows + // the fence in program order. + core::arch::x86_64::_mm_lfence(); + return core::arch::x86_64::_rdtsc(); + } + return clock_now(); +} + +#[inline(always)] +fn rdtsc_end() -> u64 { + #[cfg(target_arch = "x86_64")] + unsafe { + // End the TSC timer. + let mut aux = 0u32; + let tsc = core::arch::x86_64::__rdtscp(&mut aux); + // End the load fence. + core::arch::x86_64::_mm_lfence(); + return tsc; + } + return clock_now(); +} + +#[inline(always)] +fn clock_now() -> u64 { + let mut ts = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + let rc = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_RAW, &mut ts) }; + if rc != 0 { + panic!("Unable to get a CLOCK_MONOTONIC_RAW time. Aborting benchmarks."); + } + return (ts.tv_sec as u64) + .saturating_mul(1_000_000_000) + .saturating_add(ts.tv_nsec as u64); +} diff --git a/src/rawposix/Cargo.lock b/src/rawposix/Cargo.lock new file mode 100644 index 000000000..f71b4cdca --- /dev/null +++ b/src/rawposix/Cargo.lock @@ -0,0 +1,538 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "btree_monstrousity" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec92912346b936c974181a172d9abc81f50d41e40118fc101dac8aa8134bee3" +dependencies = [ + "cfg-if", + "rustversion", +] + +[[package]] +name = "cage" +version = "0.1.0" +dependencies = [ + "dashmap 5.5.3", + "fdtables", + "libc", + "nodit", + "once_cell", + "parking_lot", + "quick_cache", + "sysdefs", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "serde", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fdtables" +version = "0.1.0" +dependencies = [ + "dashmap 5.5.3", + "lazy_static", + "libc", + "lind-perf", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "lind-perf" +version = "0.1.0" +dependencies = [ + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "nodit" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f74369f80df24efd2266602fdcd8fcd56a17c2e2c94ab48d2f7a15eaa137bf49" +dependencies = [ + "btree_monstrousity", + "itertools", + "smallvec", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick_cache" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" +dependencies = [ + "ahash", + "equivalent", + "hashbrown 0.16.1", + "parking_lot", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rawposix" +version = "0.1.0" +dependencies = [ + "cage", + "dashmap 5.5.3", + "fdtables", + "lazy_static", + "libc", + "lind-perf", + "once_cell", + "parking_lot", + "sysdefs", + "threei", + "tracing", + "tracing-subscriber", + "typemap", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "syn" +version = "2.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysdefs" +version = "0.1.0" +dependencies = [ + "libc", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "threei" +version = "0.1.0" +dependencies = [ + "cage", + "dashmap 6.1.0", + "lazy_static", + "libc", + "lind-perf", + "nodit", + "once_cell", + "parking_lot", + "sysdefs", + "typemap", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typemap" +version = "0.1.0" +dependencies = [ + "cage", + "fdtables", + "libc", + "sysdefs", +] + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/src/rawposix/Cargo.toml b/src/rawposix/Cargo.toml index 27cc71a42..6767994ed 100644 --- a/src/rawposix/Cargo.toml +++ b/src/rawposix/Cargo.toml @@ -16,10 +16,12 @@ sysdefs = { path = "../sysdefs" } typemap = { path = "../typemap" } cage = { path = "../cage" } threei = { path = "../threei" } +lind-perf = { path = "../lind-perf", optional = true } [features] default = ["fast"] fast = [] secure = [] +lind_perf = ["dep:lind-perf", "threei/lind_perf", "fdtables/lind_perf"] [dev-dependencies] diff --git a/src/rawposix/src/bench_calls.rs b/src/rawposix/src/bench_calls.rs new file mode 100644 index 000000000..e03b1978a --- /dev/null +++ b/src/rawposix/src/bench_calls.rs @@ -0,0 +1,103 @@ +#[cfg(feature = "lind_perf")] +use crate::perf; +use fdtables; +use sysdefs::constants::err_const::{syscall_error, Errno}; +use typemap::datatype_conversion::*; + +/// libc_syscall emulates a regular RawPOSIX syscall that needs to invoke the Linux kernel with +/// minimal argument and return value parsing. +/// +/// Here we use the implementation for geteuid_syscall() +pub extern "C" fn libc_syscall( + cageid: u64, + arg1: u64, + arg1_cageid: u64, + arg2: u64, + arg2_cageid: u64, + arg3: u64, + arg3_cageid: u64, + arg4: u64, + arg4_cageid: u64, + arg5: u64, + arg5_cageid: u64, + arg6: u64, + arg6_cageid: u64, +) -> i32 { + #[cfg(feature = "lind_perf")] + let _libc_scope = perf::enabled::LIBC_SYSCALL.scope(); + + // Validate that each extra argument is unused. + if !(sc_unusedarg(arg1, arg1_cageid) + && sc_unusedarg(arg2, arg2_cageid) + && sc_unusedarg(arg3, arg3_cageid) + && sc_unusedarg(arg4, arg4_cageid) + && sc_unusedarg(arg5, arg5_cageid) + && sc_unusedarg(arg6, arg6_cageid)) + { + panic!( + "{}: unused arguments contain unexpected values -- security violation", + "libc_syscall" + ); + } + + let ret = (unsafe { libc::geteuid() }) as i32; + + #[cfg(feature = "lind_perf")] + std::hint::black_box(&_libc_scope); + + ret +} + +/// fdtables_syscalls mimics syscalls that *don't* go into the Linux kernel but rather are resolved +/// within lind. We use fdtables as an example path that the syscall might take. +/// +/// Here the implementation is equivalent to calling `close(-1)`, vfd_arg is already set through +/// glibc. +pub extern "C" fn fdtables_syscall( + cageid: u64, + vfd_arg: u64, + vfd_cageid: u64, + arg2: u64, + arg2_cageid: u64, + arg3: u64, + arg3_cageid: u64, + arg4: u64, + arg4_cageid: u64, + arg5: u64, + arg5_cageid: u64, + arg6: u64, + arg6_cageid: u64, +) -> i32 { + #[cfg(feature = "lind_perf")] + let _fdtables_scope = perf::enabled::FDTABLES_SYSCALL.scope(); + + if !(sc_unusedarg(arg2, arg2_cageid) + && sc_unusedarg(arg3, arg3_cageid) + && sc_unusedarg(arg4, arg4_cageid) + && sc_unusedarg(arg5, arg5_cageid) + && sc_unusedarg(arg6, arg6_cageid)) + { + panic!( + "{}: unused arguments contain unexpected values -- security violation", + "fdtables_syscall" + ); + } + + match fdtables::close_virtualfd(cageid, vfd_arg) { + Ok(()) => { + return 0; + } + Err(e) => { + if e == Errno::EBADFD as u64 { + return syscall_error(Errno::EBADF, "close", "Bad File Descriptor"); + } else if e == Errno::EINTR as u64 { + return syscall_error(Errno::EINTR, "close", "Interrupted system call"); + } else { + return syscall_error(Errno::EIO, "close", "I/O error"); + } + } + }; + + #[cfg(feature = "lind_perf")] + std::hint::black_box(&_fdtables_scope); +} diff --git a/src/rawposix/src/init.rs b/src/rawposix/src/init.rs index 2a94d96cc..3a7987e22 100644 --- a/src/rawposix/src/init.rs +++ b/src/rawposix/src/init.rs @@ -184,6 +184,11 @@ pub fn rawposix_start(verbosity: isize) { let lindfs_path = CString::new(LINDFS_ROOT).unwrap(); libc::mkdir(lindfs_path.as_ptr(), 0o775); let ret = libc::chroot(lindfs_path.as_ptr()); + + // When using lind-perf, modules are run multiple times. + // `rawposix_start` is invoked for every run, and a duplicate chroot causes an error. This + // ensure that the error checking is disabled in case of benchmark runs. + #[cfg(not(feature = "lind_perf"))] if ret != 0 { panic!( "Failed to chroot to {}: {}", @@ -191,6 +196,7 @@ pub fn rawposix_start(verbosity: isize) { std::io::Error::last_os_error() ); } + let root = CString::new("/").unwrap(); let ret = libc::chdir(root.as_ptr()); if ret != 0 { diff --git a/src/rawposix/src/lib.rs b/src/rawposix/src/lib.rs index d1aebdbc8..6d49cddb3 100644 --- a/src/rawposix/src/lib.rs +++ b/src/rawposix/src/lib.rs @@ -3,10 +3,13 @@ // This library provides POSIX-compliant system call implementations that operate // within the Lind-WASM sandbox environment using the 3i (Three Interposition) system. +pub mod bench_calls; pub mod fs_calls; pub mod init; pub mod net_calls; pub mod sys_calls; pub mod syscall_table; - pub use syscall_table::*; + +#[cfg(feature = "lind_perf")] +pub mod perf; diff --git a/src/rawposix/src/perf.rs b/src/rawposix/src/perf.rs new file mode 100644 index 000000000..2e538cdaa --- /dev/null +++ b/src/rawposix/src/perf.rs @@ -0,0 +1,9 @@ +#[cfg(feature = "lind_perf")] +pub mod enabled { + use lind_perf::Counter; + + pub static FDTABLES_SYSCALL: Counter = Counter::new("rawposix::fdtableS_syscall"); + pub static LIBC_SYSCALL: Counter = Counter::new("rawposix::libc_syscall"); + + pub static ALL_COUNTERS: &[&Counter] = &[&FDTABLES_SYSCALL, &LIBC_SYSCALL]; +} diff --git a/src/rawposix/src/syscall_table.rs b/src/rawposix/src/syscall_table.rs index 27047598b..c01eb65b2 100644 --- a/src/rawposix/src/syscall_table.rs +++ b/src/rawposix/src/syscall_table.rs @@ -3,6 +3,7 @@ //! https://github.com/torvalds/linux/blob/v6.16-rc1/arch/x86/entry/syscalls/syscall_64.tbl //! https://filippo.io/linux-syscall-table/ //! Keep these in sync with glibc's lind_syscall_num.h +use super::bench_calls::{fdtables_syscall, libc_syscall}; use super::fs_calls::{ access_syscall, brk_syscall, chdir_syscall, chmod_syscall, clock_gettime_syscall, close_syscall, dup2_syscall, dup3_syscall, dup_syscall, fchdir_syscall, fchmod_syscall, @@ -118,4 +119,6 @@ pub const SYSCALL_TABLE: &[(u64, RawCallFunc)] = &[ (292, dup3_syscall), (293, pipe2_syscall), (318, getrandom_syscall), + (2001, libc_syscall), + (2002, fdtables_syscall), ]; diff --git a/src/threei/Cargo.lock b/src/threei/Cargo.lock new file mode 100644 index 000000000..46e3576fb --- /dev/null +++ b/src/threei/Cargo.lock @@ -0,0 +1,509 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "btree_monstrousity" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec92912346b936c974181a172d9abc81f50d41e40118fc101dac8aa8134bee3" +dependencies = [ + "cfg-if", + "rustversion", +] + +[[package]] +name = "cage" +version = "0.1.0" +dependencies = [ + "dashmap 5.5.3", + "fdtables", + "libc", + "nodit", + "once_cell", + "parking_lot", + "quick_cache", + "sysdefs", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "serde", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fdtables" +version = "0.1.0" +dependencies = [ + "dashmap 5.5.3", + "lazy_static", + "libc", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.181" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" + +[[package]] +name = "lind-perf" +version = "0.1.0" +dependencies = [ + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "nodit" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f74369f80df24efd2266602fdcd8fcd56a17c2e2c94ab48d2f7a15eaa137bf49" +dependencies = [ + "btree_monstrousity", + "itertools", + "smallvec", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick_cache" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" +dependencies = [ + "ahash", + "equivalent", + "hashbrown 0.16.1", + "parking_lot", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serial_test" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysdefs" +version = "0.1.0" +dependencies = [ + "libc", +] + +[[package]] +name = "threei" +version = "0.1.0" +dependencies = [ + "cage", + "dashmap 6.1.0", + "lazy_static", + "libc", + "lind-perf", + "nodit", + "once_cell", + "parking_lot", + "serial_test", + "sysdefs", + "typemap", +] + +[[package]] +name = "typemap" +version = "0.1.0" +dependencies = [ + "cage", + "fdtables", + "libc", + "sysdefs", +] + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/src/threei/Cargo.toml b/src/threei/Cargo.toml index e3f838b13..27e9cad89 100644 --- a/src/threei/Cargo.toml +++ b/src/threei/Cargo.toml @@ -13,11 +13,13 @@ once_cell = "1.18" lazy_static = "1.4" parking_lot = "0.12" nodit = "0.9.2" # Used for VMMAP +lind-perf = { path = "../lind-perf", optional = true } [features] default = ["hashmap"] hashmap = [] dashmap = [] +lind_perf = ["dep:lind-perf"] [dev-dependencies] serial_test = "3" diff --git a/src/threei/src/handler_table/hashmap_impl.rs b/src/threei/src/handler_table/hashmap_impl.rs index bf2452b0c..37ce1475e 100644 --- a/src/threei/src/handler_table/hashmap_impl.rs +++ b/src/threei/src/handler_table/hashmap_impl.rs @@ -1,6 +1,7 @@ use crate::threei_const; use std::collections::HashMap; use std::sync::Mutex; +use sysdefs::constants::RAWPOSIX_CAGEID; /// HANDLERTABLE: /// A nested hash map used to define fine-grained per-syscall interposition rules. @@ -67,8 +68,13 @@ pub fn _get_handler(self_cageid: u64, syscall_num: u64) -> Option<(u64, u64)> { handler_table .get(&self_cageid) // Get the first HashMap> .and_then(|sub_table| sub_table.get(&syscall_num)) // Get the second HashMap - .and_then(|map| map.iter().next()) // Extract the first (key, value) pair - .map(|(&call_index, &grateid)| (call_index, grateid)) // Convert to (u64, u64) + .and_then(|map| { + // In case a non RAWPOSIX_CAGEID grate exists, use that. + map.iter() + .find(|(_, &grateid)| grateid != RAWPOSIX_CAGEID) + .or_else(|| map.iter().next()) + }) + .map(|(&call_index, &grateid)| (call_index, grateid)) } /// Removes **ALL** handler entries across all cages that point to a specific grateid. diff --git a/src/threei/src/lib.rs b/src/threei/src/lib.rs index 7de0715c2..16f9a9924 100644 --- a/src/threei/src/lib.rs +++ b/src/threei/src/lib.rs @@ -2,5 +2,8 @@ pub mod handler_table; pub mod threei; pub mod threei_const; +#[cfg(feature = "lind_perf")] +pub mod perf; + pub use threei::*; pub use threei_const::*; diff --git a/src/threei/src/perf.rs b/src/threei/src/perf.rs new file mode 100644 index 000000000..a697117ad --- /dev/null +++ b/src/threei/src/perf.rs @@ -0,0 +1,18 @@ +#[cfg(feature = "lind_perf")] +pub mod enabled { + use lind_perf::Counter; + + pub static MAKE_SYSCALL: Counter = Counter::new("threei::make_syscall"); + pub static MAKE_SYSCALL_CHECK_HANDLER_TABLE: Counter = + Counter::new("threei::make_syscall::check_handler_table"); + pub static CALL_GRATE_FUNC: Counter = Counter::new("threei::_call_grate_func"); + pub static CALL_GRATE_FUNC_GET_RUNTIME_TRAMPOLINE: Counter = + Counter::new("threei::_call_grate_func::get_runtime_trampoline"); + + pub static ALL_COUNTERS: &[&Counter] = &[ + &MAKE_SYSCALL, + &MAKE_SYSCALL_CHECK_HANDLER_TABLE, + &CALL_GRATE_FUNC, + &CALL_GRATE_FUNC_GET_RUNTIME_TRAMPOLINE, + ]; +} diff --git a/src/threei/src/threei.rs b/src/threei/src/threei.rs index 065d224b8..45fd54296 100644 --- a/src/threei/src/threei.rs +++ b/src/threei/src/threei.rs @@ -14,6 +14,7 @@ use crate::handler_table::{ _check_cage_handler_exists, _get_handler, _rm_cage_from_handler, _rm_grate_from_handler, copy_handler_table_to_cage_impl, print_handler_table, register_handler_impl, }; +use crate::perf; use crate::threei_const; pub const EXIT_SYSCALL: u64 = 60; // exit syscall number. Public for tests. @@ -201,40 +202,61 @@ fn _call_grate_func( arg6: u64, arg6_cageid: u64, ) -> Option { - let runtimeid = match get_cage_runtime(grateid) { - Some(r) => r, - None => panic!( - "[3i|_call_grate_func] grate runtime not found! grateid: {}", - grateid - ), - }; + #[cfg(feature = "lind_perf")] + let _call_grate_scope = perf::enabled::CALL_GRATE_FUNC.scope(); + + let ret = (|| { + let (_runtimeid, trampoline) = { + #[cfg(feature = "lind_perf")] + let _runtime_lookup_scope = + perf::enabled::CALL_GRATE_FUNC_GET_RUNTIME_TRAMPOLINE.scope(); + + let runtimeid = match get_cage_runtime(grateid) { + Some(r) => r, + None => panic!( + "[3i|_call_grate_func] grate runtime not found! grateid: {}", + grateid + ), + }; - let trampoline = match get_runtime_trampoline(runtimeid) { - Some(f) => f, - None => panic!( - "[3i|_call_grate_func] grate trampoline not found! runtimeid: {}", - runtimeid - ), - }; + let trampoline = match get_runtime_trampoline(runtimeid) { + Some(f) => f, + None => panic!( + "[3i|_call_grate_func] grate trampoline not found! runtimeid: {}", + runtimeid + ), + }; - let rc = (trampoline)( - in_grate_fn_ptr_u64, - grateid, - arg1, - arg1_cageid, - arg2, - arg2_cageid, - arg3, - arg3_cageid, - arg4, - arg4_cageid, - arg5, - arg5_cageid, - arg6, - arg6_cageid, - ); + #[cfg(feature = "lind_perf")] + std::hint::black_box(&_runtime_lookup_scope); + + (runtimeid, trampoline) + }; + + let rc = (trampoline)( + in_grate_fn_ptr_u64, + grateid, + arg1, + arg1_cageid, + arg2, + arg2_cageid, + arg3, + arg3_cageid, + arg4, + arg4_cageid, + arg5, + arg5_cageid, + arg6, + arg6_cageid, + ); - Some(rc) + Some(rc) + })(); + + #[cfg(feature = "lind_perf")] + std::hint::black_box(&_call_grate_scope); + + ret } /// EXITING_TABLE: @@ -413,6 +435,62 @@ pub fn make_syscall( arg5_cageid: u64, arg6: u64, arg6_cageid: u64, +) -> i32 { + // Only activate this timer for a syscall that's explicitly defined as a benchmark-related + // call which should be defined as 2XXX. + // + // Currently, we only have 2 such calls, FDTABLES_SYSCALL and LIBC_SYSCALL + #[cfg(feature = "lind_perf")] + let _make_syscall_scope = if syscall_num > 2000 && syscall_num < 3000 { + Some(perf::enabled::MAKE_SYSCALL.scope()) + } else { + None + }; + + // The core logic for make_syscall was moved to make_syscall_impl in order to ensure that + // lind_perf based scoped timer is not dropped early. + let ret = make_syscall_impl( + self_cageid, + syscall_num, + _syscall_name, + target_cageid, + arg1, + arg1_cageid, + arg2, + arg2_cageid, + arg3, + arg3_cageid, + arg4, + arg4_cageid, + arg5, + arg5_cageid, + arg6, + arg6_cageid, + ); + + #[cfg(feature = "lind_perf")] + std::hint::black_box(&_make_syscall_scope); + + ret +} + +pub fn make_syscall_impl( + self_cageid: u64, // is required to get the cage instance + syscall_num: u64, + _syscall_name: u64, // syscall name pointer in the calling Wasm instance + target_cageid: u64, + arg1: u64, + arg1_cageid: u64, + arg2: u64, + arg2_cageid: u64, + arg3: u64, + arg3_cageid: u64, + arg4: u64, + arg4_cageid: u64, + arg5: u64, + arg5_cageid: u64, + arg6: u64, + arg6_cageid: u64, ) -> i32 { // Return error if the target cage/grate is exiting. We need to add this check beforehead, because make_syscall will also // contain cases that can directly redirect a syscall when self_cageid == target_id, which will bypass the handlertable check @@ -469,6 +547,7 @@ pub fn make_syscall( arg6_cageid, ); } + // Grate case: call into the corresponding grate function // // Theoretically, the complexity is O(1), shouldn't affect performance a lot @@ -503,10 +582,10 @@ pub fn make_syscall( } panic!( - "[3i|make_syscall] syscall number {} not found in handler table for cage {}, targetcage {}!", - syscall_num, - self_cageid, - target_cageid, + "[3i|make_syscall] syscall number {} not found in handler table for cage {}, targetcage {}!", + syscall_num, + self_cageid, + target_cageid, ); } diff --git a/src/wasmtime/crates/lind-common/Cargo.toml b/src/wasmtime/crates/lind-common/Cargo.toml index bd95c8e3b..ae0b20bfc 100644 --- a/src/wasmtime/crates/lind-common/Cargo.toml +++ b/src/wasmtime/crates/lind-common/Cargo.toml @@ -22,6 +22,8 @@ sysdefs = { path = "../sysdefs" } wasmtime-lind-3i = { path = "../lind-3i" } cage = { path = "../cage" } typemap = { path = "../typemap" } +lind-perf = { path = "../lind-perf", optional = true } [features] lind_debug = [] +lind_perf = ["dep:lind-perf", "threei/lind_perf", "rawposix/lind_perf"] diff --git a/src/wasmtime/crates/lind-common/src/lib.rs b/src/wasmtime/crates/lind-common/src/lib.rs index 3d0c51290..f4d1eb27a 100644 --- a/src/wasmtime/crates/lind-common/src/lib.rs +++ b/src/wasmtime/crates/lind-common/src/lib.rs @@ -1,5 +1,8 @@ #![allow(dead_code)] +#[cfg(feature = "lind_perf")] +pub mod perf; + use anyhow::Result; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; @@ -53,63 +56,84 @@ pub fn add_to_linker< arg6: u64, arg6cageid: u64| -> i32 { - // TODO: - // 1. add a signal check here as Linux also has a signal check when transition from kernel to userspace - // However, Asyncify management in this function should be carefully rethinking if adding signal check here - - // With Asyncify enabled, an unwind/rewind resumes Wasmtime execution by re-entering - // the original call site. This means the same hostcall/trampoline path can be - // executed multiple times while representing a *single* logical operation. - // - // `clone` is particularly sensitive here: during a logical `clone`, the lind - // trampoline can be re-entered multiple times (e.g., 3 times) after unwind/rewind. - // If we forward the syscall to RawPOSIX on every re-entry, we will perform the - // operation multiple times. + // Only activate this timer for a syscall that's explicitly defined as a benchmark-related + // call which should be defined as 2XXX. // - // In lind-boot we forward syscalls directly to RawPOSIX, so we replicate the state - // check here to early-return when we are on a rewind replay path. - if call_number as i32 == CLONE_SYSCALL { - if let Some(rewind_res) = wasmtime_lind_multi_process::catch_rewind(&mut caller) { - return rewind_res; - } - } - - // Some thread-related operations must be executed against a specific thread's - // VMContext (e.g., pthread_create/exit). Because syscalls may be interposed/routed - // through 3i functionality and the effective thread instance cannot be reliably derived - // from self/target cage IDs or per-argument cage IDs, we explicitly attach the *current* - // source thread id (tid) for selected syscalls. (Note: `self_cageid == target_cageid` means - // the syscall executes from cage) - // - // Concretely, for CLONE/EXEC we override arg2 with the current tid so that, when the call back - // to wasmtime, it can resolve the correct thread instance deterministically, independent of - // interposition or cross-cage routing. - let final_arg2 = if self_cageid == target_cageid - && matches!(call_number as i32, CLONE_SYSCALL | EXIT_SYSCALL) - { - wasmtime_lind_multi_process::current_tid(&mut caller) as u64 + // Currently, we only have 2 such calls, FDTABLES_SYSCALL and LIBC_SYSCALL + #[cfg(feature = "lind_perf")] + let _make_syscall_scope = if call_number > 2000 && call_number < 3000 { + Some(perf::enabled::ADD_TO_LINKER_MAKE_SYSCALL.scope()) } else { - arg2 + None }; - make_syscall( - self_cageid, - call_number as u64, - call_name, - target_cageid, - arg1, - arg1cageid, - final_arg2, - arg2cageid, - arg3, - arg3cageid, - arg4, - arg4cageid, - arg5, - arg5cageid, - arg6, - arg6cageid, - ) + // Core implementation of this function was moved to an unnamed scope, so that the + // lind_perf timer is not dropped early. + let ret = (|| { + // TODO: + // 1. add a signal check here as Linux also has a signal check when transition from kernel to userspace + // However, Asyncify management in this function should be carefully rethinking if adding signal check here + + // With Asyncify enabled, an unwind/rewind resumes Wasmtime execution by re-entering + // the original call site. This means the same hostcall/trampoline path can be + // executed multiple times while representing a *single* logical operation. + // + // `clone` is particularly sensitive here: during a logical `clone`, the lind + // trampoline can be re-entered multiple times (e.g., 3 times) after unwind/rewind. + // If we forward the syscall to RawPOSIX on every re-entry, we will perform the + // operation multiple times. + // + // In lind-boot we forward syscalls directly to RawPOSIX, so we replicate the state + // check here to early-return when we are on a rewind replay path. + if call_number as i32 == CLONE_SYSCALL { + if let Some(rewind_res) = wasmtime_lind_multi_process::catch_rewind(&mut caller) + { + return rewind_res; + } + } + + // Some thread-related operations must be executed against a specific thread's + // VMContext (e.g., pthread_create/exit). Because syscalls may be interposed/routed + // through 3i functionality and the effective thread instance cannot be reliably derived + // from self/target cage IDs or per-argument cage IDs, we explicitly attach the *current* + // source thread id (tid) for selected syscalls. (Note: `self_cageid == target_cageid` means + // the syscall executes from cage) + // + // Concretely, for CLONE/EXEC we override arg2 with the current tid so that, when the call back + // to wasmtime, it can resolve the correct thread instance deterministically, independent of + // interposition or cross-cage routing. + let final_arg2 = if self_cageid == target_cageid + && matches!(call_number as i32, CLONE_SYSCALL | EXIT_SYSCALL) + { + wasmtime_lind_multi_process::current_tid(&mut caller) as u64 + } else { + arg2 + }; + + make_syscall( + self_cageid, + call_number as u64, + call_name, + target_cageid, + arg1, + arg1cageid, + final_arg2, + arg2cageid, + arg3, + arg3cageid, + arg4, + arg4cageid, + arg5, + arg5cageid, + arg6, + arg6cageid, + ) + })(); + + #[cfg(feature = "lind_perf")] + std::hint::black_box(&_make_syscall_scope); + + ret }, )?; diff --git a/src/wasmtime/crates/lind-common/src/perf.rs b/src/wasmtime/crates/lind-common/src/perf.rs new file mode 100644 index 000000000..f7ae9c96e --- /dev/null +++ b/src/wasmtime/crates/lind-common/src/perf.rs @@ -0,0 +1,9 @@ +#[cfg(feature = "lind_perf")] +pub mod enabled { + use lind_perf::Counter; + + pub static ADD_TO_LINKER_MAKE_SYSCALL: Counter = + Counter::new("lind_common::add_to_linker::make-syscall"); + + pub static ALL_COUNTERS: &[&Counter] = &[&ADD_TO_LINKER_MAKE_SYSCALL]; +} diff --git a/src/wasmtime/crates/lind-perf b/src/wasmtime/crates/lind-perf new file mode 120000 index 000000000..300980f24 --- /dev/null +++ b/src/wasmtime/crates/lind-perf @@ -0,0 +1 @@ +../../lind-perf/ \ No newline at end of file diff --git a/tests/benchmarks/fdtables_syscall.c b/tests/benchmarks/fdtables_syscall.c new file mode 100644 index 000000000..15fddf217 --- /dev/null +++ b/tests/benchmarks/fdtables_syscall.c @@ -0,0 +1,22 @@ +/* + * Benchmark for a syscall that goes to a RawPOSIX handler that + * does not go to the kernel, instead gets resolved within fdtables. + * + * Implemented as an alias of close(-1); + * + * Run with `sudo lind-boot --perf fdtcall.wasm` + */ + +#include +#include + +#define LOOP_COUNT 1000000 + +int main() { + int ret; + for (int i=0; i < LOOP_COUNT; i++) { + ret = fdt_syscall(); + } + + printf("."); +} diff --git a/tests/benchmarks/grate_syscall.c b/tests/benchmarks/grate_syscall.c new file mode 100644 index 000000000..251540525 --- /dev/null +++ b/tests/benchmarks/grate_syscall.c @@ -0,0 +1,78 @@ +/* + * Benchmark for a syscall that is resolved through a grate. + * + * Interposes on both FDT_CALL and LIBC_CALL syscalls. + * + * Run with `sudo lind-boot --perf gratecall.wasm [fdtcall.wasm | libccall.wasm]` + */ + +#include +#include +#include +#include +#include +#include +#include + +// Dispatcher function +int pass_fptr_to_wt(uint64_t fn_ptr_uint, uint64_t cageid, uint64_t arg1, + uint64_t arg1cage, uint64_t arg2, uint64_t arg2cage, + uint64_t arg3, uint64_t arg3cage, uint64_t arg4, + uint64_t arg4cage, uint64_t arg5, uint64_t arg5cage, + uint64_t arg6, uint64_t arg6cage) { + if (fn_ptr_uint == 0) { + return -1; + } + + int (*fn)(uint64_t) = (int (*)(uint64_t))(uintptr_t)fn_ptr_uint; + + return fn(cageid); +} + +// Function ptr and signatures of this grate +int geteuid_grate(uint64_t); + +int geteuid_grate(uint64_t cageid) { + return 10; +} + +// Main function will always be same in all grates +int main(int argc, char *argv[]) { + // Should be at least two inputs (at least one grate file and one cage file) + if (argc < 2) { + fprintf(stderr, "Usage: %s [...]\n", + argv[0]); + exit(EXIT_FAILURE); + } + + int grateid = getpid(); + + pid_t pid = fork(); + if (pid < 0) { + perror("fork failed"); + exit(EXIT_FAILURE); + } else if (pid == 0) { + int cageid = getpid(); + + uint64_t fn_ptr_addr = (uint64_t)(uintptr_t)&geteuid_grate; + + // Register both the FDT_CALL and LIBC_CALL syscall numbers. + int ret = register_handler(cageid, 2001, 1, grateid, fn_ptr_addr); + ret = register_handler(cageid, 2002, 1, grateid, fn_ptr_addr); + + if (execv(argv[1], &argv[1]) == -1) { + perror("execv failed"); + exit(EXIT_FAILURE); + } + } + + int status; + int failed = 0; + while (wait(&status) > 0) { + if (status != 0) { + failed = 1; + } + } + + return 0; +} diff --git a/tests/benchmarks/libc_syscall.c b/tests/benchmarks/libc_syscall.c new file mode 100644 index 000000000..1bebd2c17 --- /dev/null +++ b/tests/benchmarks/libc_syscall.c @@ -0,0 +1,22 @@ +/* + * Benchmark for a syscall that goes to a RawPOSIX handler that + * goes to the Linux kernel with minimal processing. + * + * Implemented as an alias of geteuid(); + * + * Run with `sudo lind-boot --perf libccall.wasm` + */ + +#include +#include + +#define LOOP_COUNT 1000000 + +int main() { + int ret; + for (int i=0; i < LOOP_COUNT; i++) { + ret = libc_syscall(); + } + + printf("."); +}