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: Reb00t #48

Merged
merged 83 commits into from
May 10, 2021
Merged

feat: Reb00t #48

merged 83 commits into from
May 10, 2021

Conversation

Hywan
Copy link
Contributor

@Hywan Hywan commented Apr 27, 2021

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, _>)?,
        }))
    }
}
  • Methods in #[rubymethods] can have a receiver of type &self or &mut self,
  • Methods' arguments are automatically “unwrapped”/upcasted to the Rust type from the Ruby class,
  • Exceptions are managed through the RubyResult type,
  • A new special ::ruby_new method is declared to create a Ruby object of a Rust type,
  • etc etc.

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:

  • Store, which holds the engine, the compiler etc.
  • Module, which represents a Wasm module, with validate, new, name=, name, exports, imports, custom_sections, serialize and deserialize,
  • Instance, which represents a Wasm instance, with new and exports,
  • Exports, which represents a collection of exported entities, with respond_to_missing?, method_missing, and length,
  • ImportObject, which represents a collection of imports that will be passed to Instance, with new, contains_namespace, and register,
  • Function, which represents an imported or exported function, with new, call and type,
  • Memory, which represents an imported or exported memory, with new, type, size, data_size, grow, + memory views that extends Enumerable,
  • Global, which represents an imported or exported global, with new, mutable?, value, value=, and type,
  • Table, which represents an imported or exported table, with new (small API for the moment),
  • Type, FunctionType, MemoryType, GlobalType and TableType,
  • ExportType and ImportType,
  • Value which represents a Wasm value,
  • Wasi module, which provides the Version, StateBuilder and Environment classes.

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

Left to do

  • WASI
  • Target + CpuFeature + Triple
  • Engine
  • Compiler

@Hywan Hywan merged commit 0c221e8 into wasmerio:master May 10, 2021
@Hywan Hywan self-assigned this May 11, 2021
@Hywan Hywan added 🎉 enhancement New feature or request 📚 documentation Do you like to read? 📦 component-extension About the Ruby extension written in Rust 📦 component-runtime About the Wasm runtime 🧪 tests I love tests labels May 11, 2021
@Hywan Hywan added this to 🌱 In progress in Kanban via automation May 11, 2021
@Hywan Hywan moved this from 🌱 In progress to 🎉 Done in Kanban May 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📦 component-extension About the Ruby extension written in Rust 📦 component-runtime About the Wasm runtime 📚 documentation Do you like to read? 🎉 enhancement New feature or request 🧪 tests I love tests
Projects
Kanban
  
🎉 Done
Development

Successfully merging this pull request may close these issues.

None yet

1 participant