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

feat: Wasm module implementation #73

Merged
merged 29 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9ca9868
add a draft Module loader functionalit
Mr-Leshiy Jan 30, 2024
530377c
Merge branch 'main' into feat/wasm-module-loader
Mr-Leshiy Jan 30, 2024
6bc9f5b
remove rust-toolchain cargo config value
Mr-Leshiy Jan 30, 2024
e6e4119
fix scoping
Mr-Leshiy Jan 30, 2024
29a3fd9
update Error handling, update checks
Mr-Leshiy Jan 30, 2024
a19aa75
add Clone derive
Mr-Leshiy Jan 30, 2024
850c18d
fix some lints
Mr-Leshiy Jan 30, 2024
7938661
update
Mr-Leshiy Jan 30, 2024
e647211
add engine docs
Mr-Leshiy Jan 30, 2024
1fcc34b
add wasm module docs
Mr-Leshiy Jan 30, 2024
f1eba16
fix spell check
Mr-Leshiy Jan 30, 2024
c90355d
fix deny failure
Mr-Leshiy Jan 31, 2024
ddabd0d
update module
Mr-Leshiy Feb 1, 2024
fdb820d
wip
Mr-Leshiy Feb 1, 2024
342a3a2
update store management
Mr-Leshiy Feb 1, 2024
f472304
add more docs
Mr-Leshiy Feb 1, 2024
223dbdc
add Context struct, remove engine mod
Mr-Leshiy Feb 1, 2024
85b2dcb
wip
Mr-Leshiy Feb 1, 2024
6f662cd
fix checks
Mr-Leshiy Feb 1, 2024
288f9a7
update Cargo.toml file
Mr-Leshiy Feb 1, 2024
0adf84a
fix build
Mr-Leshiy Feb 1, 2024
5cd4ac7
fix build
Mr-Leshiy Feb 1, 2024
b8a170d
Merge branch 'main' into feat/wasm-module-loader
Mr-Leshiy Feb 1, 2024
28f2763
Update hermes/bin/src/wasm/module.rs
Mr-Leshiy Feb 1, 2024
d9e5289
fix suggestions
Mr-Leshiy Feb 2, 2024
13363a6
add docs
Mr-Leshiy Feb 2, 2024
9f8fc0c
Merge branch 'main' into feat/wasm-module-loader
Mr-Leshiy Feb 2, 2024
b3e9d56
Update hermes/bin/src/main.rs
Mr-Leshiy Feb 2, 2024
589d79a
cleanup
Mr-Leshiy Feb 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,6 @@ vitss
voteplan
voteplans
WASI
wasmtime
webasm
yoroi
4 changes: 3 additions & 1 deletion hermes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ version = "0.0.1"
authors = [
"Steven Johnson <steven.johnson@iohk.io>"
]
rust-version = "1.73"
homepage = "https://input-output-hk.github.io/hermes"
repository = "https://github.com/input-output-hk/hermes"
license = "MIT OR Apache-2.0"
Expand Down Expand Up @@ -55,3 +54,6 @@ missing_docs_in_private_items = "deny"
# issue: https://github.com/input-output-hk/hermes/issues/63
pallas = { version = "0.21.0", git = "https://github.com/input-output-hk/catalyst-pallas.git", branch = "feat/hardano-read-blocks-iter-send" }
pallas-hardano = { version = "0.21.0", git = "https://github.com/input-output-hk/catalyst-pallas.git", branch = "feat/hardano-read-blocks-iter-send" }

wasmtime = "17.0.0"
rusty_ulid = "2.0.0"
2 changes: 1 addition & 1 deletion hermes/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ VERSION 0.7

# Set up our target toolchains, and copy our files.
builder:
DO github.com/input-output-hk/catalyst-ci/earthly/rust:v2.6.0+SETUP
DO github.com/input-output-hk/catalyst-ci/earthly/rust:v2.7.0+SETUP

COPY --dir .cargo .config crates bin .
COPY Cargo.toml .
Expand Down
4 changes: 4 additions & 0 deletions hermes/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ repository.workspace = true

[lints]
workspace = true

[dependencies]
wasmtime = { workspace = true, features = ["component-model"] }
rusty_ulid = { workspace = true }
2 changes: 2 additions & 0 deletions hermes/bin/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
//! Intentionally empty
//! This file exists, so that doc tests can be used inside binary crates.

mod wasm;
4 changes: 3 additions & 1 deletion hermes/bin/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! The Hermes Node
//! The Hermes Node.

mod wasm;

fn main() {
println!("Hello, world!");
Expand Down
62 changes: 62 additions & 0 deletions hermes/bin/src/wasm/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//! WASM module's context implementation.

use rusty_ulid::Ulid;

/// A WASM module's context structure, which is intended to be passed to the
/// `wasmtime::Store` during the WASM module's state initialization process.
#[derive(Clone)]
pub(crate) struct Context {
/// Hermes application name
app_name: String,

/// module ULID id
module_id: Ulid,

/// event name to be executed
event_name: Option<String>,

/// module's execution counter
counter: u64,
}

impl Context {
/// Creates a new instance of the `Context`.
pub(crate) fn new(app_name: String) -> Self {
Self {
app_name,
module_id: Ulid::generate(),
event_name: None,
counter: 0,
}
}

/// Increments the module's execution counter and sets the event name to be executed
pub(crate) fn use_for(&mut self, even_name: String) {
self.event_name = Some(even_name);
self.counter += 1;
}

/// Get the application name
#[allow(dead_code)]
pub(crate) fn app_name(&self) -> &str {
&self.app_name
}

/// Get the module id
#[allow(dead_code)]
pub(crate) fn module_id(&self) -> &Ulid {
&self.module_id
}

/// Get the event name
#[allow(dead_code)]
pub(crate) fn event_name(&self) -> Option<&String> {
self.event_name.as_ref()
}

/// Get the counter value
#[allow(dead_code)]
pub(crate) fn counter(&self) -> u64 {
self.counter
}
}
45 changes: 45 additions & 0 deletions hermes/bin/src/wasm/engine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! WASM engine implementation
//! Wrapper over the `wasmtime::Engine` struct with some specific configuration setup.

use std::{
error::Error,
ops::{Deref, DerefMut},
};

use wasmtime::{Config as WasmConfig, Engine as WasmEngine};

/// WASM Engine struct
#[derive(Clone)]
pub(crate) struct Engine(WasmEngine);

impl Deref for Engine {
type Target = WasmEngine;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for Engine {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl Engine {
/// Creates a new instance of the `Engine`.
///
/// # Errors
///
/// - `wasmtime::Error`: engine initialization error.
#[allow(dead_code)]
pub(crate) fn new() -> Result<Self, Box<dyn Error>> {
let mut config = WasmConfig::new();
config.wasm_component_model(true);
config.consume_fuel(false);

let engine = WasmEngine::new(&config)?;

Ok(Self(engine))
}
}
6 changes: 6 additions & 0 deletions hermes/bin/src/wasm/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! WASM related structures and functions which are specific for the Hermes use case.
//! All implementation based on [wasmtime](https://crates.io/crates/wasmtime) crate dependency.

mod context;
mod engine;
mod module;
140 changes: 140 additions & 0 deletions hermes/bin/src/wasm/module.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//! WASM module implementation.
//! Wrapper over the `wasmtime::Module`, `wasmtime::Instance` etc. structs which
//! define a WASM module abstraction with capability to interact with it.
//!
//! All implementation based on [wasmtime](https://crates.io/crates/wasmtime) crate dependency.

use std::error::Error;

use wasmtime::{
InstancePre as WasmInstancePre, Linker as WasmLinker, Module as WasmModule, Store as WasmStore,
WasmParams, WasmResults,
};

use super::{context::Context, engine::Engine};

/// Interface for linking WASM imports
pub(crate) trait LinkImport<Context> {
/// Link imports to the `wasmtime::Linker`
fn link(&self, linker: &mut WasmLinker<Context>) -> Result<(), Box<dyn Error>>;
}

/// Structure defines an abstraction over the WASM module
/// It instantiates the module with the provided context data,
/// links all provided imports to the module instance,
/// handles an internal state of the WASM module.
///
/// The primary goal for it is to make a WASM state *immutable* along WASM module
/// execution. It means that `Module::call_func` execution does not have as side effect
/// for the WASM module's state, it becomes unchanged.
pub(crate) struct Module {
/// `wasmtime::InstancePre` entity
///
/// A reason why it is used a `wasmtime::InstancePre` instead of `wasmtime::Instance`
/// partially described in this [RFC](https://github.com/bytecodealliance/rfcs/blob/main/accepted/shared-host-functions.md).
/// It separates and optimizes the linkage of the imports to the WASM runtime from the
/// module actual initialization process.
pre_instance: WasmInstancePre<Context>,

/// `Engine` entity
engine: Engine,

/// `Context` entity
context: Context,
}

impl Module {
/// Instantiate WASM module
///
/// # Errors
/// - `wasmtime::Error`: WASM call error
#[allow(dead_code)]
pub(crate) fn new(
engine: Engine, app_name: String, module_bytes: &[u8],
imports: &[Box<dyn LinkImport<Context>>],
) -> Result<Self, Box<dyn Error>> {
let module = WasmModule::new(&engine, module_bytes)?;

let mut linker = WasmLinker::new(&engine);
for import in imports {
import.link(&mut linker)?;
}
let pre_instance = linker.instantiate_pre(&module)?;

Ok(Self {
pre_instance,
engine,
context: Context::new(app_name),
})
}

/// Call WASM module's function.
/// For each call creates a brand new `wasmtime::Store` instance, which means that
/// is has an initial state, based on the provided context for each call.
///
/// # Errors
/// - `wasmtime::Error`: WASM call error
#[allow(dead_code)]
pub(crate) fn call_func<Args, Ret>(
&mut self, name: &str, args: Args,
) -> Result<Ret, Box<dyn Error>>
where
Args: WasmParams,
Ret: WasmResults,
{
self.context.use_for(name.to_string());

let mut store = WasmStore::new(&self.engine, self.context.clone());
let instance = self.pre_instance.instantiate(&mut store)?;
let func = instance.get_typed_func(&mut store, name)?;
Ok(func.call(&mut store, args)?)
}
}

#[cfg(test)]
mod tests {
use super::*;

struct ImportHelloFunc;

impl LinkImport<Context> for ImportHelloFunc {
fn link(&self, linker: &mut WasmLinker<Context>) -> Result<(), Box<dyn Error>> {
linker.func_wrap("", "hello", || {
println!("hello");
})?;
Ok(())
}
}

#[test]
/// Tests that after instantiation of `Module` its state does not change after each
/// `Module::call_func` execution
fn preserve_module_state_test() {
let engine = Engine::new().expect("");
let wat = r#"
(module
(import "" "hello" (func $hello_0))
(export "call_hello" (func $call_hello))

(func $call_hello (result i32)
global.get $global_val
i32.const 1
i32.add
global.set $global_val
global.get $global_val
)

(global $global_val (mut i32) (i32.const 0))
)"#;

let mut module = Module::new(engine, "app".to_string(), wat.as_bytes(), &[Box::new(
ImportHelloFunc,
)])
.expect("");

for _ in 0..10 {
let res: i32 = module.call_func("call_hello", ()).expect("");
assert_eq!(res, 1);
}
}
}
1 change: 0 additions & 1 deletion hermes/crates/cardano-chain-follower/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ name = "cardano-chain-follower"
edition.workspace = true
version.workspace = true
authors.workspace = true
rust-version.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
Expand Down
1 change: 1 addition & 0 deletions hermes/deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ allow = [
"Apache-2.0",
"Unicode-DFS-2016",
"BSD-3-Clause",
"BSD-2-Clause",
"BlueOak-1.0.0",
"Apache-2.0 WITH LLVM-exception"
]
Expand Down
2 changes: 1 addition & 1 deletion wasm/wasi-hermes-component-adapter/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ VERSION 0.7

# Set up our target toolchains, and copy source files.
builder:
DO github.com/input-output-hk/catalyst-ci/earthly/rust:v2.6.0+SETUP
DO github.com/input-output-hk/catalyst-ci/earthly/rust:v2.7.0+SETUP

COPY --dir .cargo .config src byte-array-literals .
COPY ..+wasi-src/wasi ../wasi
Expand Down
1 change: 1 addition & 0 deletions wasm/wasi-hermes-component-adapter/deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ allow = [
"Apache-2.0",
"Unicode-DFS-2016",
"BSD-3-Clause",
"BSD-2-Clause",
"BlueOak-1.0.0",
"Apache-2.0 WITH LLVM-exception"
]
Expand Down