wasmerio / wasmer-ruby

💎🕸 WebAssembly runtime for Ruby
https://wasmer.io
MIT License
465 stars 18 forks source link

feat: Reb00t #48

Closed Hywan closed 3 years ago

Hywan commented 3 years ago

This PR is a massive rewrite the wasmer-ruby language embedding. Here are some highlights.

rutie-derive and rutie-derive-macros

We are using rutie as the layer to integrate Rust inside Ruby. It's nice, but the “DSL” is verbose and limited. We have decided to write 2 new crates:

  1. rutie-derive-macros, which provides the #[rubyclass], the #[rubymethods] and the #[rubyfunction] procedural macros,
  2. rutie-derive, which provides some extra logic needed by rutie-derive-macros to upcast some types from Ruby to Rust seemlessly.

This is not really super documented for the moment, as it's all experimental. The crates don't aim to be reused by other projects for the moment. There is still sharp edge corners, but it fulfills our initial objective: Making the Rust code more readable and easier to maintain.

Here is a before/after example, illustrating how the rutie-derive* crates improve the code. The before:

struct Module {
    inner: wasmer::Module,
}

rutie::wrappable_struct!(Module, ModuleWrapper, MODULE_WRAPPER);

rutie::class!(RubyModule);

impl rutie::VerifiedObject for RubyModule {
        fn is_correct_type<T>(object: &T) -> bool
        where T: rutie::Object
        {
            object.class() == rutie::Module::from_existing("Wasmer").get_nested_class("Module")
        }

        fn error_message() -> &'static str {
            "Error converting to `RubyModule`"
        }
}

rutie::methods!(
    Module,
    _itself,
    fn ruby_module_new(store: …, bytes: rutie::RString) -> rutie::AnyObject {
        unwrap_or_else(|| {
            let store = store?.get_data(&*STORE_WRAPPER);
            let bytes = bytes?

            let module = wasmer::Module::new(store.inner(), bytes.to_str_unchecked());

            let wasmer_module = rutie::Module::from_existing("Wasmer");
            let ruby_module: AnyObject = wasmer_module
                .get_nested_class("Module")
                .wrap_data(module, &*MODULE_WRAPPER);

            Ok(ruby_module)
        })
    }
);

And the after:

#[rubyclass(module = "Wasmer")]
struct Module {
    inner: wasmer::Module,
}

#[rubymethods]
impl Module {
    pub fn new(store: &Store, bytes: &RString) -> RubyResult<AnyObject> {
        let module = wasmer::Module::new(store.inner(), bytes.to_str_unchecked());

        Ok(Module::ruby_new(Module {
            inner: module.map_err(to_ruby_err::<RuntimeError, _>)?,
        }))
    }
}

It's not perfect as I said, but it's a good start!

The wasmer crate

Before, we were only able to create an instance from the raw Wasm bytes, and fetch an exported function or an exported global, with a poor API.

Now, we have a complete new API based on the Wasmer “standard API” (the one we use in Rust, but also in Go, in Python etc.), which includes:

This new API is much richer and complete than the previous one!

Left to do