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

Features/component imports #692

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
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
51 changes: 42 additions & 9 deletions lib/wasmex/components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@
def start_link(opts) when is_list(opts) do
with {:ok, store} <- build_store(opts),
component_bytes <- Keyword.get(opts, :bytes),
imports <- Keyword.get(opts, :imports, %{}),
{:ok, component} <- Wasmex.Components.Component.new(store, component_bytes) do
GenServer.start_link(__MODULE__, %{store: store, component: component}, opts)
GenServer.start_link(
__MODULE__,
%{store: store, component: component, imports: imports},
opts
)
end
end

Expand All @@ -45,25 +50,53 @@
end

@impl true
def init(%{store: store, component: component} = state) do
case Wasmex.Components.Instance.new(store, component) do
{:ok, instance} -> {:ok, Map.merge(state, %{instance: instance})}
def init(
%{store: store, component: component, imports: imports} = state
) do
case Wasmex.Components.Instance.new(store, component, imports) do
{:ok, instance} -> {:ok, Map.merge(state, %{instance: instance, component: component, imports: imports})}
{:error, reason} -> {:error, reason}
end
end

@impl true
def handle_call(
{:call_function, name, params},
_from,
from,
%{instance: instance} = state
) do
case Wasmex.Components.Instance.call_function(instance, name, params) do
{:ok, result} -> {:reply, {:ok, result}, state}
{:error, error} -> {:reply, {:error, error}, state}
end
:ok = Wasmex.Components.Instance.call_function(instance, name, params, from)
{:noreply, state}
# {:ok, result} -> {:reply, {:ok, result}, state}
# {:error, error} -> {:reply, {:error, error}, state}
# end
end

@impl true
def handle_info({:returned_function_call, result, from}, state) do
GenServer.reply(from, result)
{:noreply, state}
end

@impl true
def handle_info(
{:invoke_callback, name, token, params},
%{imports: imports, instance: instance, component: component} = state

Check warning on line 84 in lib/wasmex/components.ex

View workflow job for this annotation

GitHub Actions / OTP 26.2 / Elixir 1.15.8

variable "instance" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 84 in lib/wasmex/components.ex

View workflow job for this annotation

GitHub Actions / OTP 25.2 / Elixir 1.15.8

variable "instance" is unused (if the variable is not meant to be used, prefix it with an underscore)
) do
{:fn, function} = Map.get(imports, name)
result = apply(function, params)
:ok = Wasmex.Native.component_receive_callback_result(component.resource, token, true, result)
{:noreply, state}
end

@impl true
def handle_info(msg, state) do
IO.inspect(msg, label: "in genserver handle_info")

Check warning on line 94 in lib/wasmex/components.ex

View workflow job for this annotation

GitHub Actions / credo

There should be no calls to `IO.inspect/1`.
{:noreply, state}
end

defp stringify(s) when is_binary(s), do: s
defp stringify(s) when is_atom(s), do: Atom.to_string(s)

defp elixirify(wasm_identifier), do: String.replace(wasm_identifier, "-", "_") |> String.to_atom()

Check warning on line 101 in lib/wasmex/components.ex

View workflow job for this annotation

GitHub Actions / OTP 26.2 / Elixir 1.15.8

function elixirify/1 is unused

Check warning on line 101 in lib/wasmex/components.ex

View workflow job for this annotation

GitHub Actions / OTP 25.2 / Elixir 1.15.8

function elixirify/1 is unused
end
9 changes: 5 additions & 4 deletions lib/wasmex/components/component_instance.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ defmodule Wasmex.Components.Instance do
}
end

def new(store_or_caller, component) do
def new(store_or_caller, component, imports) do
%{resource: store_or_caller_resource} = store_or_caller
%{resource: component_resource} = component

case Wasmex.Native.component_instance_new(store_or_caller_resource, component_resource) do
case Wasmex.Native.component_instance_new(store_or_caller_resource, component_resource, imports) do
{:error, err} -> {:error, err}
resource -> {:ok, __wrap_resource__(store_or_caller_resource, resource)}
end
Expand All @@ -32,8 +32,9 @@ defmodule Wasmex.Components.Instance do
def call_function(
%__MODULE__{store_resource: store_resource, instance_resource: instance_resource},
function,
args
args,
from
) do
Wasmex.Native.component_call_function(store_resource, instance_resource, function, args)
Wasmex.Native.component_call_function(store_resource, instance_resource, function, args, from)
end
end
14 changes: 14 additions & 0 deletions lib/wasmex/components/function_server.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule Wasmex.Components.FunctionServer do

Check warning on line 1 in lib/wasmex/components/function_server.ex

View workflow job for this annotation

GitHub Actions / credo

Modules should have a @moduledoc tag.
use GenServer

def start_link(arg), do: GenServer.start_link(__MODULE__, arg)

def init(_arg) do
{:ok, %{}}
end

def handle_info(msg, state) do
IO.inspect(msg, label: "In function server")

Check warning on line 11 in lib/wasmex/components/function_server.ex

View workflow job for this annotation

GitHub Actions / credo

There should be no calls to `IO.inspect/1`.
{:noreply, state}
end
end
6 changes: 4 additions & 2 deletions lib/wasmex/native.ex
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,11 @@ defmodule Wasmex.Native do

def component_new(_store, _component_bytes), do: error()

def component_instance_new(_store, _component), do: error()
def component_instance_new(_store, _component, _imports), do: error()

def component_call_function(_store, _instance, _function_name, _params), do: error()
def component_call_function(_store, _instance, _function_name, _params, _from), do: error()

def component_receive_callback_result(_component, _2, _3, _4), do: error()

def wit_exported_functions(_path, _wit), do: error()

Expand Down
66 changes: 26 additions & 40 deletions native/wasmex/src/component.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
use crate::store::{ComponentStoreData, ComponentStoreResource};
use rustler::Binary;
use rustler::Error;
use rustler::NifResult;
use rustler::ResourceArc;
use rustler::Error;
use wasmtime::Store;
use wit_parser::decoding::DecodedWasm;
use wit_parser::Resolve;
use wit_parser::WorldId;

use std::sync::Mutex;
use wasmtime::component::{Component, Instance, Linker};
use wasmtime::component::Component;

pub struct ComponentResource {
pub inner: Mutex<Component>,
pub parsed: ParsedComponent
}

pub struct ParsedComponent {
pub world_id: WorldId,
pub resolve: Resolve
}

#[rustler::resource_impl()]
Expand All @@ -27,49 +36,26 @@ pub fn new_component(
)))
})?);
let bytes = component_binary.as_slice();
let decoded_wasm = wit_parser::decoding::decode(bytes).map_err(|e|
Error::Term(Box::new(format!("Unable to decode WASM: {e}")))
)?;
let parsed_component = match decoded_wasm {
DecodedWasm::WitPackage(_, _) => {
return Err(rustler::Error::RaiseAtom("Only components are supported"))
},
DecodedWasm::Component(resolve, world_id) => {
ParsedComponent {
world_id: world_id,
resolve: resolve
}
}
};

let component = Component::new(store_or_caller.engine(), bytes)
.map_err(|err| rustler::Error::Term(Box::new(err.to_string())))?;

Ok(ResourceArc::new(ComponentResource {
inner: Mutex::new(component),
parsed: parsed_component,
}))
}

#[rustler::nif(name = "component_instance_new")]
pub fn new_component_instance(
component_store_resource: ResourceArc<ComponentStoreResource>,
component_resource: ResourceArc<ComponentResource>,
) -> NifResult<ResourceArc<ComponentInstanceResource>> {
let component_store: &mut Store<ComponentStoreData> =
&mut *(component_store_resource.inner.lock().map_err(|e| {
rustler::Error::Term(Box::new(format!(
"Could not unlock component_store resource as the mutex was poisoned: {e}"
)))
})?);

let component = &mut component_resource.inner.lock().map_err(|e| {
rustler::Error::Term(Box::new(format!(
"Could not unlock component resource as the mutex was poisoned: {e}"
)))
})?;

let mut linker = Linker::new(component_store.engine());
let _ = wasmtime_wasi::add_to_linker_sync(&mut linker);
let _ = wasmtime_wasi_http::add_only_http_to_linker_sync(&mut linker);
// Instantiate the component
let instance = linker
.instantiate(&mut *component_store, component)
.map_err(|err| Error::Term(Box::new(err.to_string())))?;

Ok(ResourceArc::new(ComponentInstanceResource {
inner: Mutex::new(instance),
}))
}

pub struct ComponentInstanceResource {
pub inner: Mutex<Instance>,
}

#[rustler::resource_impl()]
impl rustler::Resource for ComponentInstanceResource {}
Loading
Loading