Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Wasmi execution profiling infrastructure #974

Closed
wants to merge 20 commits into from
Closed
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
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ jobs:
- name: Build (all features)
run: cargo build --workspace --all-features
- name: Build (no_std)
run: cargo build --workspace --lib --no-default-features --target thumbv7em-none-eabi --exclude wasmi_cli --exclude wasmi_wasi --exclude wasmi_fuzz
run: cargo build --workspace --lib --no-default-features --target thumbv7em-none-eabi --exclude wasmi_cli --exclude wasmi_wasi --exclude wasmi_fuzz --exclude wasmi_profiling --exclude wasmi_profiling_macro
- name: Build (wasm32)
run: cargo build --workspace --lib --no-default-features --target wasm32-unknown-unknown --exclude wasmi_cli --exclude wasmi_wasi --exclude wasmi_fuzz
run: cargo build --workspace --lib --no-default-features --target wasm32-unknown-unknown --exclude wasmi_cli --exclude wasmi_wasi --exclude wasmi_fuzz --exclude wasmi_profiling --exclude wasmi_profiling_macro

test-asan:
name: Test (Address Sanitizer)
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ members = [
"crates/core",
"crates/wasmi",
"crates/wasi",
"crates/profiling",
"crates/profiling/macro",
"fuzz",
]
exclude = []
Expand All @@ -27,7 +29,10 @@ wasmi = { version = "0.32.0-beta.8", path = "crates/wasmi", default-features = f
wasmi_wasi = { version = "0.32.0-beta.8", path = "crates/wasi", default-features = false }
wasmi_core = { version = "0.17.0", path = "crates/core", default-features = false }
wasmi_arena = { version = "0.5.0", path = "crates/arena", default-features = false }
wasmi_profiling = { version = "0.32.0-beta.8", path = "crates/profiling" }
wasmi_profiling_macro = { version = "0.32.0-beta.8", path = "crates/profiling/macro" }
num-traits = { version = "0.2.8", default-features = false }
serde = { version = "1" }

[profile.bench]
lto = "fat"
Expand Down
18 changes: 18 additions & 0 deletions crates/profiling/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "wasmi_profiling"
version.workspace = true
rust-version.workspace = true
documentation = "https://docs.rs/wasmi/"
description = "WebAssembly interpreter profiling infrastructure"
authors.workspace = true
repository.workspace = true
edition.workspace = true
readme.workspace = true
license.workspace = true
keywords.workspace = true
categories.workspace = true
exclude.workspace = true

[dependencies]
wasmi_profiling_macro = { workspace = true }
serde = { workspace = true, features = ["derive"] }
31 changes: 31 additions & 0 deletions crates/profiling/macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "wasmi_profiling_macro"
version.workspace = true
rust-version.workspace = true
documentation = "https://docs.rs/wasmi/"
description = "WebAssembly interpreter profiling infrastructure proc. macro"
authors.workspace = true
repository.workspace = true
edition.workspace = true
readme.workspace = true
license.workspace = true
keywords.workspace = true
categories.workspace = true
exclude.workspace = true

[lib]
proc-macro = true

[dependencies]
quote = "1"
syn = { version = "2", features = ["extra-traits"] }
proc-macro2 = "1"
heck = "0.5"

[dev-dependencies]
wasmi_profiling = { workspace = true }
serde = { workspace = true }

[package.metadata.cargo-udeps.ignore]
# cargo-udeps cannot handle dependencies that are only used in doc tests.
development = ["wasmi_profiling", "serde"]
184 changes: 184 additions & 0 deletions crates/profiling/macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
mod utils;

use crate::utils::{to_snake_case_ident, AttributeExt as _};
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, quote_spanned};
use syn::{parse_macro_input, spanned::Spanned, DeriveInput};

/// Applied on the Wasmi bytecode `enum` to generate a `struct` to collect profiling data.
///
/// # Examples
///
/// ```rust
/// use wasmi_profiling::{WasmiProfiling, SelectInstr};
///
/// type Register = u16;
///
/// #[derive(WasmiProfiling)]
/// enum Instruction {
/// Trap(u32),
/// Return,
/// I32Add {
/// result: Register,
/// lhs: Register,
/// rhs: Register,
/// },
/// }
///
/// let mut data = <Instruction as WasmiProfiling>::data();
/// // The generated profiling data type should be used in the following way.
/// //
/// // - The `start` method must be called right before the first instruction dispatch.
/// // - Right before executing an instruction, their associated `start` method must be called.
/// // - Right after executing an instruction, their associated `stop` method must be called.
/// data.start();
/// data.instr().trap().start();
/// data.instr().trap().stop();
/// data.instr().r#return().start();
/// data.instr().r#return().stop();
/// data.instr().i32_add().start();
/// data.instr().i32_add().stop();
/// ```
#[proc_macro_derive(WasmiProfiling)]
pub fn wasmi_profiling(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let syn::Data::Enum(data_enum) = &input.data else {
panic!(

Check warning on line 47 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L44-L47

Added lines #L44 - L47 were not covered by tests
"wasmi_profiling_macro: can only operate on `enum` but got: {:?}",
&input.data

Check warning on line 49 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L49

Added line #L49 was not covered by tests
);
};
let span = input.span();
let ident = &input.ident;
let data_type = generate_data_type(span, data_enum);
let instr_data_type = generate_instr_data_type(span, data_enum);
let expanded = quote! {

Check warning on line 56 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L52-L56

Added lines #L52 - L56 were not covered by tests
const _: () = {
#data_type
#instr_data_type

impl ::wasmi_profiling::WasmiProfiling for #ident {
type Data = Data;

fn data() -> Self::Data {
<Self::Data>::default()
}
}
};
};
TokenStream::from(expanded)
}

Check warning on line 71 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L70-L71

Added lines #L70 - L71 were not covered by tests

fn generate_data_type(span: Span, data_enum: &syn::DataEnum) -> TokenStream2 {
let select_instr = data_enum.variants.iter().map(|variant| {
let span = variant.span();
let snake_ident = to_snake_case_ident(&variant.ident);
let docs = variant.attrs.iter().filter(|attr| attr.is_doc());
quote_spanned!(span=>

Check warning on line 78 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L73-L78

Added lines #L73 - L78 were not covered by tests
#( #docs )*
#[inline]
pub fn #snake_ident(self) -> ::wasmi_profiling::SelectedInstr<'a> {
::wasmi_profiling::SelectedInstr::new(
&mut self.data.dispatch,
&mut self.data.ticker,
&mut self.data.instr.#snake_ident,
)
}
)
});
quote_spanned!(span=>

Check warning on line 90 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L89-L90

Added lines #L89 - L90 were not covered by tests
#[derive(
::core::fmt::Debug,
::core::default::Default,
::core::marker::Copy,
::core::clone::Clone,
::wasmi_profiling::serde::Serialize,
)]
#[repr(transparent)]
pub struct Data {
data: ::wasmi_profiling::ProfilingData<InstrData>,
}

impl Data {
/// Start profiling a Wasmi execution run.
///
/// # Note
///
/// This should be invoked right before the first instruction dispatch.
pub fn start(&mut self) {
self.data.start();
}
}

impl ::wasmi_profiling::SelectInstr for Data {
type Selector<'a> = InstrSelector<'a>;

#[inline]
fn instr(&mut self) -> Self::Selector<'_> {
Self::Selector { data: &mut self.data }
}
}

#[derive(Debug)]
#[repr(transparent)]
pub struct InstrSelector<'a> {
data: &'a mut ::wasmi_profiling::ProfilingData<InstrData>,
}

impl<'a> InstrSelector<'a> {
#( #select_instr )*
}
)
}

Check warning on line 133 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L133

Added line #L133 was not covered by tests

fn generate_instr_data_type(span: Span, data_enum: &syn::DataEnum) -> TokenStream2 {
let fields = data_enum.variants.iter().map(|variant| {
let span = variant.span();
let snake_ident = to_snake_case_ident(&variant.ident);
let docs = variant.attrs.iter().filter(|attr| attr.is_doc());
quote_spanned!(span=>

Check warning on line 140 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L135-L140

Added lines #L135 - L140 were not covered by tests
#[serde(skip_serializing_if = "::wasmi_profiling::InstrTracker::is_never_called")]
#( #docs )*
pub #snake_ident: ::wasmi_profiling::InstrTracker
)
});
let total_time_impl = data_enum.variants.iter().map(|variant| {
let span = variant.span();
let snake_ident = to_snake_case_ident(&variant.ident);
quote_spanned!(span=>

Check warning on line 149 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L145-L149

Added lines #L145 - L149 were not covered by tests
self.#snake_ident.total_time
)
});
let count_impl = data_enum.variants.iter().map(|variant| {
let span = variant.span();
let snake_ident = to_snake_case_ident(&variant.ident);
quote_spanned!(span=>

Check warning on line 156 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L152-L156

Added lines #L152 - L156 were not covered by tests
self.#snake_ident.count
)
});
quote_spanned!(span=>

Check warning on line 160 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L159-L160

Added lines #L159 - L160 were not covered by tests
#[derive(
::core::fmt::Debug,
::core::default::Default,
::core::marker::Copy,
::core::clone::Clone,
::wasmi_profiling::serde::Serialize,
)]
pub struct InstrData {
#( #fields ),*
}

impl ::wasmi_profiling::InstrsTotalTime for InstrData {
fn instrs_total_time(&self) -> ::core::time::Duration {
#( #total_time_impl )+*
}
}

impl ::wasmi_profiling::InstrsCount for InstrData {
fn instrs_count(&self) -> ::core::primitive::u64 {
#( #count_impl )+*
}
}
)
}

Check warning on line 184 in crates/profiling/macro/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/lib.rs#L184

Added line #L184 was not covered by tests
18 changes: 18 additions & 0 deletions crates/profiling/macro/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// Extension methods for [`struct@syn::Attribute`].
pub trait AttributeExt {
/// Returns `true` if the [`struct@syn::Attribute`] is a Rust documentation attribute.
fn is_doc(&self) -> bool;
}

impl AttributeExt for syn::Attribute {
fn is_doc(&self) -> bool {
self.path().is_ident("doc")

Check warning on line 9 in crates/profiling/macro/src/utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/utils.rs#L8-L9

Added lines #L8 - L9 were not covered by tests
}
}

/// Converts the `ident` to a snake-case raw [`struct@syn::Ident`].
pub fn to_snake_case_ident(ident: &syn::Ident) -> syn::Ident {
let span = ident.span();
let snake_name = heck::AsSnakeCase(ident.to_string()).to_string();
syn::Ident::new_raw(&snake_name, span)

Check warning on line 17 in crates/profiling/macro/src/utils.rs

View check run for this annotation

Codecov / codecov/patch

crates/profiling/macro/src/utils.rs#L14-L17

Added lines #L14 - L17 were not covered by tests
}
Loading
Loading