Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ report.html
results.json

#Ignore python cache
__pycache__/
__pycache__/
6 changes: 4 additions & 2 deletions Docker/Dockerfile.e2e
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,21 @@ ENV PATH="/root/.cargo/bin:${PATH}"
# Build lind-boot
FROM base as build-lind-boot
# NOTE: Using 'make' risks cache invalidation on unrelated Makefile changes
COPY --parents src/lind-boot src/wasmtime src/rawposix src/cage src/threei src/typemap src/fdtables src/sysdefs Makefile rust-toolchain.toml .
COPY --parents src/lind-boot src/wasmtime src/rawposix src/cage src/threei src/typemap src/fdtables src/sysdefs src/lind-perf Makefile rust-toolchain.toml .
RUN rm -f src/wasmtime/crates/cage \
src/wasmtime/crates/threei \
src/wasmtime/crates/fdtables \
src/wasmtime/crates/typemap \
src/wasmtime/crates/sysdefs \
src/wasmtime/crates/rawposix \
src/wasmtime/crates/lind-perf \
&& ln -s ../../cage src/wasmtime/crates/cage \
&& ln -s ../../threei src/wasmtime/crates/threei \
&& ln -s ../../fdtables src/wasmtime/crates/fdtables \
&& ln -s ../../typemap src/wasmtime/crates/typemap \
&& ln -s ../../sysdefs src/wasmtime/crates/sysdefs \
&& ln -s ../../rawposix src/wasmtime/crates/rawposix
&& ln -s ../../rawposix src/wasmtime/crates/rawposix \
&& ln -s ../../lind-perf src/wasmtime/crates/lind-perf
RUN make lind-boot


Expand Down
7 changes: 7 additions & 0 deletions src/lind-boot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ edition = "2024"
disable_signals = ["cage/disable_signals", "wasmtime-lind-multi-process/disable_signals"]
secure = ["typemap/secure"]
lind_debug = ["wasmtime-lind-common/lind_debug"]
lind_perf = [
"lind-perf/enabled",
"threei/lind_perf",
"rawposix/lind_perf",
"wasmtime-lind-common/lind_perf",
]

[dependencies]
wasmtime-lind-common = { path = "../wasmtime/crates/lind-common" }
Expand All @@ -21,6 +27,7 @@ wasmtime-lind-3i = { path = "../wasmtime/crates/lind-3i" }
wasmtime = { path = "../wasmtime/crates/wasmtime", features = ["cranelift", "pooling-allocator", "gc", "threads", "demangle", "addr2line", "cache"], default-features = false }
wasmtime-wasi-threads = { path = "../wasmtime/crates/wasi-threads" }
wasmtime-wasi = { version = "23.0.0", features = ["preview1"] , default-features = false }
lind-perf = { path = "../wasmtime/crates/lind-perf" }

anyhow = { version = "1.0.66", default-features = false }
clap = { version = "4", features = ["derive"] }
Expand Down
38 changes: 36 additions & 2 deletions src/lind-boot/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ At a high level, lind-boot sits at the boundary between the command-line interfa
src/
├── main.rs
├── cli.rs
└── lind-wasmtime/
├── perf.rs
└── lind_wasmtime/
├── mod.rs
├── execute.rs
├── host.rs
Expand All @@ -32,7 +33,10 @@ Supported flags:
```sh
--verbose
--debug
--precompile
--wasmtime-backtrace
--env NAME[=VAL]
--perf[=clock|tsc]
```

## Design Overview
Expand All @@ -43,6 +47,11 @@ From the user’s perspective, lind-boot behaves like a conventional process lau

Execution begins in main.rs, where command-line arguments are parsed and passed to the core execution logic. The entry point accepts a WebAssembly binary followed by program arguments.

The control flow is:
1. Handle `--precompile` early and exit.
2. If `--perf` is set, try to run in benchmark mode.
3. Otherwise run the normal single-execution path.

### host.rs

Host-side runtime state is encapsulated in `HostCtx`, defined in host.rs. This structure holds the WASI Preview1 context, the WASI threads context, and the Lind multi-process context.
Expand All @@ -59,9 +68,34 @@ Before execution begins, lind-boot attaches all required host-side APIs to the W

Module instantiation occurs in `load_main_module`. The WebAssembly module is instantiated inside a Lind cage, after which the runtime checks for and invokes the `main` function because of our glibc modification. The main entry point is then resolved, stack bounds are initialized, and signal and epoch-related state is set up for the main thread of the cage. At this point, the WebAssembly program is fully initialized and starts running code logic.

One responsibility of lind-boot is capturing and managing Wasmtime’s internal `VMContext` pointers. After instantiation, lind-boot extracts the `VMContext` associated with the running instance and stores it in a global table indexed by cage ID. Additional backup instances are created to populate a pool of `VMContext`s that can be reused during grate calls and syscall re-entry. (See more comments on lind-wasm/src/wasmtime/crates/lind-3i)
One responsibility of lind-boot is capturing and managing Wasmtime’s internal `VMContext` pointers. After instantiation, lind-boot extracts the `VMContext` associated with the running instance and stores it in a global table indexed by cage ID. Additional backup instances are created to populate a pool of `VMContext`s that can be reused during grate calls and syscall re-entry. (See more comments on `src/wasmtime/crates/lind-3i`)

### trampoline.rs

The re-entry mechanism is implemented in trampoline.rs. When 3i routes a syscall to a grate, it invokes a unified callback function registered by lind-boot. This trampoline retrieves the appropriate `VMContext` for the target cage, re-enters the Wasmtime runtime using `Caller::with`, and invokes a unified entry function inside the WebAssembly module. Control is then dispatched to the appropriate syscall implementation based on the function pointer originally registered with 3i. Once execution completes, the VMContext is returned to the global pool for future use.

### perf.rs

`perf.rs` defines `lind-perf` counters for lind-boot and dependency crates which are used by `main.rs` to run benchmarks.

`lind-boot` supports performance benchmarking via `--perf`, with optional timer selection:

```sh
# Default timer backend is CLOCK_MONOTONIC_RAW
lind-boot --perf=clock program.wasm

# `=clock` is optional
lind-boot --perf program.wasm

# Cycle-counter backend (RDTSC/RDTSCP on x86_64)
lind-boot --perf=tsc program.wasm
```

Perf mode runs the same workload multiple times, enabling one counter per run, then prints a final report.

Important behavior:

1. `--perf` is accepted by the CLI regardless of build mode.
2. If lind-boot is compiled without the crate feature `lind_perf`, `--perf` exits early with an explicit error.
3. The `lind_perf` feature in lind-boot enables `lind-perf/enabled`, which turns timing/reporting on. Without it, `lind-perf` stays linked but behaves as no-op.

46 changes: 46 additions & 0 deletions src/lind-boot/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
use clap::*;

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum PerfTimer {
/// Use `clock_gettime(CLOCK_MONOTONIC_RAW)` based timing.
Clock,
/// Use RDTSC/RDTSCP cycle counter timing.
Tsc,
}

#[derive(Debug, Parser, Clone)]
#[command(name = "lind-boot")]
pub struct CliOptions {
Expand Down Expand Up @@ -39,6 +47,23 @@ 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<String>)>,

/// Get performance information for the running module.
///
/// `--perf` defaults to `clock`; pass `--perf=tsc` for cycle-based timing.
///
/// `--perf` is always accepted by the CLI, but execution only proceeds when
/// lind-boot is compiled with the crate feature `lind_perf` (which wires
/// `lind-perf/enabled`).
#[arg(
long,
value_enum,
default_missing_value = "clock",
value_name = "clock|tsc",
num_args = 0..=1,
require_equals = true,
)]
pub perf: Option<PerfTimer>,
}

pub fn parse_env_var(s: &str) -> Result<(String, Option<String>), String> {
Expand All @@ -53,4 +78,25 @@ impl CliOptions {
pub fn wasm_file(&self) -> &str {
&self.args[0]
}

pub fn perf_timer_kind(&self) -> Option<lind_perf::TimerKind> {
// Runtime gate for the perf CLI path:
// - if lind-boot was compiled without `lind_perf`, reject `--perf` early
// with a clear error.
// - otherwise map the CLI timer selection to lind-perf's timer backend.
match lind_perf::ENABLED {
false => match self.perf {
Some(_) => {
eprintln!("--perf needs compilation with the feature `lind_perf` enabled.");
std::process::exit(1);
}
None => None,
},
true => match self.perf {
Some(PerfTimer::Clock) => Some(lind_perf::TimerKind::Clock),
Some(PerfTimer::Tsc) => Some(lind_perf::TimerKind::Rdtsc),
None => None,
},
}
}
}
15 changes: 13 additions & 2 deletions src/lind-boot/src/lind_wasmtime/trampoline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use wasmtime::{Caller, Instance};
use wasmtime_lind_3i::{VmCtxWrapper, get_vmctx, set_vmctx};
use wasmtime_lind_multi_process;

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.
///
Expand Down Expand Up @@ -45,6 +47,10 @@ pub extern "C" fn grate_callback_trampoline(
arg6: u64,
arg6cageid: u64,
) -> i32 {
// This timer measures the entire function since it is never explicitly dropped. The timer
// therefore ends only when the function exits.
let _grate_callback_timer = lind_perf::get_timer!(perf::GRATE_CALLBACK_TRAMPOLINE);

let vmctx_wrapper: VmCtxWrapper = match get_vmctx(cageid) {
Some(v) => v,
None => {
Expand Down Expand Up @@ -91,8 +97,11 @@ pub extern "C" fn grate_callback_trampoline(
u64,
), i32>(&mut store)?;

// This timer is used for a smaller snippet rather than an entire function or a scope,
// and therefore gets dropped manually to record the end time.
let _typed_func = lind_perf::get_timer!(perf::TYPED_FUNC_CALL);
// Call the entry function with all arguments and in grate function pointer
typed_func.call(
let ret = typed_func.call(
&mut store,
(
in_grate_fn_ptr_u64,
Expand All @@ -110,7 +119,9 @@ pub extern "C" fn grate_callback_trampoline(
arg6,
arg6cageid,
),
)
);
drop(_typed_func);
ret
})
.unwrap_or(threei_const::GRATE_ERR)
};
Expand Down
28 changes: 27 additions & 1 deletion src/lind-boot/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod cli;
mod lind_wasmtime;
mod perf;

use crate::{
cli::CliOptions,
Expand Down Expand Up @@ -64,10 +65,35 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
precompile_module(&lindboot_cli)?;
return Ok(());
}

// Not a precompile command, chroot to lindfs
chroot_to_lindfs();

// Check if --perf is enabled and avaible to decide whether to run in benchmarking mode.
if let Some(kind) = lindboot_cli.perf_timer_kind() {
// Initialize all counters.
perf::perf_init(kind);

let counters = perf::all_counter_names();

// Iterate over all counters:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the counter here mean how many loops we want to iterate? I'm a bit confused by this counter and the one in src/lind-perf/src/enabled/counter.rs

// - Exclusively enable the counter
// - Run the program to gather timing data.
for counter in counters {
perf::enable_one_counter(counter);

// Each sample run gets a fresh RawPOSIX lifecycle boundary to imitate actual
// behaviour.
rawposix_start(0);
let _ = execute_wasmtime(lindboot_cli.clone());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't remove the exit handling

rawposix_shutdown();
}

// Output final numbers to stdout.
perf::perf_report();

return Ok(());
}

// Initialize RawPOSIX and register RawPOSIX syscalls with 3i
rawposix_start(0);

Expand Down
49 changes: 49 additions & 0 deletions src/lind-boot/src/perf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/// 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.
use crate::cli::CliOptions;
use lind_perf::{Counter, TimerKind};

// These are counters defined within lind-boot.
pub static GRATE_CALLBACK_TRAMPOLINE: Counter =
Counter::new("lind_boot::grate_callback_trampoline");
pub static TYPED_FUNC_CALL: Counter = Counter::new("lind_boot::typed_func_call");

// Counter list used by the perf runner in `main.rs`. Each benchmark iteration
// enables exactly one counter name from this list.
pub static LIND_BOOT_COUNTERS: &[&Counter] = &[&GRATE_CALLBACK_TRAMPOLINE, &TYPED_FUNC_CALL];

/// Initialize counters for all modules, involves setting the TimerKind and resetting the
/// counts.
pub fn perf_init(kind: TimerKind) {
// Configure timer backend (Clock or TSC) for all local counters.
lind_perf::set_timer(all_counters(), kind);
// Reset all accumulated measurements before benchmark runs begin.
lind_perf::reset_all_counters(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_counter(name: &str) {
lind_perf::enable_counter_by_name(all_counters(), name);
}

fn all_counters() -> impl Iterator<Item = &'static Counter> {
LIND_BOOT_COUNTERS.iter().copied()
Comment thread
rennergade marked this conversation as resolved.
}

/// Get a list of all counter names.
pub fn all_counter_names() -> Vec<&'static str> {
all_counters().filter_map(|c| c.get_name()).collect()
}

/// Print a report for every module.
pub fn perf_report() {
// Note: `lind_perf::report*` are no-ops when lind-perf is built without
// its internal `enabled` feature.
lind_perf::report(LIND_BOOT_COUNTERS, format!("LIND-BOOT"));
}
11 changes: 11 additions & 0 deletions src/lind-perf/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "lind-perf"
version = "0.1.0"
edition = "2024"

[features]
default = []
enabled = []

[dependencies]
libc = "0.2"
Loading