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

Wasm on the Web #1990

Merged
merged 6 commits into from
Jun 5, 2024
Merged
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 .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ jobs:
--locked \
--target wasm32-unknown-unknown \
--no-default-features \
--features web
--features web,wasmer
- name: Compile the workspace with the default features (build)
run: |
cargo build --locked
Expand Down
22 changes: 8 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ wasm-bindgen = "0.2.92"
wasm-bindgen-test = "0.3.42"
wasm-encoder = "0.24.1"
wasm-instrument = "0.4.0"
wasmer = { version = "4.3.0-alpha.1", features = ["singlepass"] }
wasmer = { version = "4.3.0-alpha.1", default-features = false }
wasmer-compiler-singlepass = "4.3.0-alpha.1"
wasmparser = "0.101.1"
wasmtime = "1.0"
Expand Down Expand Up @@ -217,3 +217,8 @@ opt-level = 3
version = "0.4.1"
git = "https://github.com/Twey/rust-indexed-db"
branch = "no-uuid-wasm-bindgen"

[patch.crates-io.wasmer]
version = "4.3.0-alpha.1"
git = "https://github.com/Twey/wasmer"
branch = "non-send-environments"
21 changes: 7 additions & 14 deletions examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,8 @@ strip = 'debuginfo'

[profile.bench]
debug = true

[patch.crates-io.wasmer]
version = "4.3.0-alpha.1"
git = "https://github.com/Twey/wasmer"
branch = "non-send-environments"
8 changes: 7 additions & 1 deletion linera-execution/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,16 @@ thiserror.workspace = true
tracing = { workspace = true, features = ["log"] }
wasm-encoder = { workspace = true, optional = true }
wasm-instrument = { workspace = true, optional = true, features = ["sign_ext"] }
wasmer = { workspace = true, optional = true }
wasmparser = { workspace = true, optional = true }
wasmtime = { workspace = true, optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { workspace = true, features = ["rt-multi-thread"] }
wasmer = { workspace = true, optional = true, features = ["sys-default", "singlepass"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
tokio = { workspace = true, features = ["rt"] }
wasmer = { workspace = true, optional = true, features = ["js-default"] }

[dev-dependencies]
anyhow.workspace = true
Expand All @@ -87,3 +88,8 @@ cfg_aliases.workspace = true

[package.metadata.cargo-machete]
ignored = ["serde_bytes"]

[patch.crates-io.wasmer]
version = "4.3.0-alpha.1"
git = "https://github.com/Twey/wasmer"
branch = "non-send-environments"
4 changes: 3 additions & 1 deletion linera-execution/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

fn main() {
cfg_aliases::cfg_aliases! {
web: { all(target_arch = "wasm32", feature = "web") },

with_fs: { all(not(target_arch = "wasm32"), feature = "fs") },
with_metrics: { all(not(target_arch = "wasm32"), feature = "metrics") },
with_testing: { any(test, feature = "test") },
with_tokio_multi_thread: { not(target_arch = "wasm32") },
with_wasmer: { all(not(target_arch = "wasm32"), feature = "wasmer") },
with_wasmer: { feature = "wasmer" },
with_wasmtime: { all(not(target_arch = "wasm32"), feature = "wasmtime") },

// If you change this, don't forget to update `WasmRuntime` and
Expand Down
4 changes: 2 additions & 2 deletions linera-execution/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ pub type UserContractCode = Arc<dyn UserContractModule + Send + Sync + 'static>;
pub type UserServiceCode = Arc<dyn UserServiceModule + Send + Sync + 'static>;

/// An implementation of [`UserContract`].
pub type UserContractInstance = Box<dyn UserContract + Send + Sync + 'static>;
pub type UserContractInstance = Box<dyn UserContract + 'static>;

/// An implementation of [`UserService`].
pub type UserServiceInstance = Box<dyn UserService + Send + Sync + 'static>;
pub type UserServiceInstance = Box<dyn UserService + 'static>;

/// A factory trait to obtain a [`UserContract`] from a [`UserContractModule`]
pub trait UserContractModule {
Expand Down
4 changes: 2 additions & 2 deletions linera-execution/src/test_utils/mock_application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ impl UserContractModule for MockApplication {
fn instantiate(
&self,
runtime: ContractSyncRuntime,
) -> Result<Box<dyn UserContract + Send + Sync + 'static>, ExecutionError> {
) -> Result<Box<dyn UserContract + 'static>, ExecutionError> {
Ok(Box::new(self.create_mock_instance(runtime)))
}
}
Expand All @@ -204,7 +204,7 @@ impl UserServiceModule for MockApplication {
fn instantiate(
&self,
runtime: ServiceSyncRuntime,
) -> Result<Box<dyn UserService + Send + Sync + 'static>, ExecutionError> {
) -> Result<Box<dyn UserService + 'static>, ExecutionError> {
Ok(Box::new(self.create_mock_instance(runtime)))
}
}
Expand Down
6 changes: 3 additions & 3 deletions linera-execution/src/wasm/system_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub struct ContractSystemApi<Caller>(PhantomData<Caller>);
impl<Caller, Runtime> ContractSystemApi<Caller>
where
Caller: Instance<UserData = SystemApiData<Runtime>>,
Runtime: ContractRuntime + Send + 'static,
Runtime: ContractRuntime + 'static,
{
/// Returns the ID of the current chain.
fn get_chain_id(caller: &mut Caller) -> Result<ChainId, RuntimeError> {
Expand Down Expand Up @@ -360,7 +360,7 @@ pub struct ServiceSystemApi<Caller>(PhantomData<Caller>);
impl<Caller, Runtime> ServiceSystemApi<Caller>
where
Caller: Instance<UserData = SystemApiData<Runtime>>,
Runtime: ServiceRuntime + Send + 'static,
Runtime: ServiceRuntime + 'static,
{
/// Returns the ID of the current chain.
fn get_chain_id(caller: &mut Caller) -> Result<ChainId, RuntimeError> {
Expand Down Expand Up @@ -514,7 +514,7 @@ pub struct ViewSystemApi<Caller>(PhantomData<Caller>);
impl<Caller, Runtime> ViewSystemApi<Caller>
where
Caller: Instance<UserData = SystemApiData<Runtime>>,
Runtime: BaseRuntime + WriteBatch + Send + 'static,
Runtime: BaseRuntime + WriteBatch + 'static,
{
/// Creates a new promise to check if the `key` is in storage.
fn contains_key_new(caller: &mut Caller, key: Vec<u8>) -> Result<u32, RuntimeError> {
Expand Down
40 changes: 24 additions & 16 deletions linera-execution/src/wasm/wasmer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use linera_witty::{
};
use tokio::sync::Mutex;
use wasm_instrument::{gas_metering, parity_wasm};
use wasmer::{sys::EngineBuilder, Cranelift, Engine, Module, Singlepass, Store};
use wasmer::{Engine, Module, Store};

use super::{
module_cache::ModuleCache,
Expand All @@ -28,8 +28,15 @@ use crate::{

/// An [`Engine`] instance configured to run application services.
static SERVICE_ENGINE: Lazy<Engine> = Lazy::new(|| {
let compiler_config = Cranelift::new();
EngineBuilder::new(compiler_config).into()
#[cfg(web)]
{
wasmer::Engine::default()
}

#[cfg(not(web))]
{
wasmer::sys::EngineBuilder::new(wasmer::Cranelift::new()).into()
}
});

/// A cache of compiled contract modules, with their respective [`Engine`] instances.
Expand Down Expand Up @@ -65,7 +72,7 @@ impl WasmContractModule {

impl<Runtime> WasmerContractInstance<Runtime>
where
Runtime: ContractRuntime + WriteBatch + Clone + Send + Sync + Unpin + 'static,
Runtime: ContractRuntime + WriteBatch + Clone + Unpin + 'static,
{
/// Prepares a runtime instance to call into the Wasm contract.
pub fn prepare(
Expand Down Expand Up @@ -100,7 +107,7 @@ impl WasmServiceModule {

impl<Runtime> WasmerServiceInstance<Runtime>
where
Runtime: ServiceRuntime + WriteBatch + Clone + Send + Sync + Unpin + 'static,
Runtime: ServiceRuntime + WriteBatch + Clone + Unpin + 'static,
{
/// Prepares a runtime instance to call into the Wasm service.
pub fn prepare(service_module: &Module, runtime: Runtime) -> Result<Self, WasmExecutionError> {
Expand All @@ -118,7 +125,7 @@ where

impl<Runtime> crate::UserContract for WasmerContractInstance<Runtime>
where
Runtime: ContractRuntime + Send + Unpin + 'static,
Runtime: ContractRuntime + Unpin + 'static,
{
fn instantiate(
&mut self,
Expand Down Expand Up @@ -160,10 +167,7 @@ where
}
}

impl<Runtime> crate::UserService for WasmerServiceInstance<Runtime>
where
Runtime: Send + 'static,
{
impl<Runtime: 'static> crate::UserService for WasmerServiceInstance<Runtime> {
fn handle_query(
&mut self,
_context: QueryContext,
Expand Down Expand Up @@ -253,17 +257,21 @@ impl CachedContractModule {

/// Creates a new [`Engine`] to compile a contract bytecode.
fn create_compilation_engine() -> Engine {
let mut compiler_config = Singlepass::default();
compiler_config.canonicalize_nans(true);
#[cfg(not(web))]
{
let mut compiler_config = wasmer::Singlepass::default();
compiler_config.canonicalize_nans(true);

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove newline?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the newline is important here to visually separate the setup from the use of compiler_config.

(Ideally it would be a builder! And then the whole thing would be one expression.)

Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO this is all doing one thing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's small enough that I'm not particularly bothered by it. I think if we decide to make changes elsewhere I'll include it in the next push — otherwise I'd like to merge it without waiting another hour for CI so I can point linera-web to it 😅

wasmer::sys::EngineBuilder::new(compiler_config).into()
}

EngineBuilder::new(compiler_config).into()
#[cfg(web)]
wasmer::Engine::default()
}

/// Creates a [`Module`] from a compiled contract using a headless [`Engine`].
pub fn create_execution_instance(&self) -> Result<(Engine, Module), anyhow::Error> {
use wasmer::NativeEngineExt;

let engine = Engine::headless();
let engine = Engine::default();
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe @jvff to confirm this one?

Copy link
Contributor Author

@Twey Twey Jun 5, 2024

Choose a reason for hiding this comment

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

This one is fine, but I'd love @jvff's opinion on the safety of removing canonicalize_nans on the Web.

Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed on call, this should be an improbable edge case that doesn't affect consensus, only the possibility of a client proposing a block. We should fix it eventually, but it's not a blocker.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's an issue tracking this future work: #2103.

let store = Store::new(engine.clone());
let module = unsafe { Module::deserialize(&store, &*self.compiled_bytecode) }?;
Ok((engine, module))
Expand Down
Loading
Loading