Skip to content

Commit acda0a0

Browse files
authored
Merge pull request #692 from tessi/features/component-imports
Features/component imports
2 parents 4c7baeb + ceb2996 commit acda0a0

22 files changed

+718
-294
lines changed

lib/wasmex/components.ex

+36-9
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@ defmodule Wasmex.Components do
2525
def start_link(opts) when is_list(opts) do
2626
with {:ok, store} <- build_store(opts),
2727
component_bytes <- Keyword.get(opts, :bytes),
28+
imports <- Keyword.get(opts, :imports, %{}),
2829
{:ok, component} <- Wasmex.Components.Component.new(store, component_bytes) do
29-
GenServer.start_link(__MODULE__, %{store: store, component: component}, opts)
30+
GenServer.start_link(
31+
__MODULE__,
32+
%{store: store, component: component, imports: imports},
33+
opts
34+
)
3035
end
3136
end
3237

@@ -45,23 +50,45 @@ defmodule Wasmex.Components do
4550
end
4651

4752
@impl true
48-
def init(%{store: store, component: component} = state) do
49-
case Wasmex.Components.Instance.new(store, component) do
50-
{:ok, instance} -> {:ok, Map.merge(state, %{instance: instance})}
51-
{:error, reason} -> {:error, reason}
53+
def init(%{store: store, component: component, imports: imports} = state) do
54+
case Wasmex.Components.Instance.new(store, component, imports) do
55+
{:ok, instance} ->
56+
{:ok, Map.merge(state, %{instance: instance, component: component, imports: imports})}
57+
58+
{:error, reason} ->
59+
{:error, reason}
5260
end
5361
end
5462

5563
@impl true
5664
def handle_call(
5765
{:call_function, name, params},
58-
_from,
66+
from,
5967
%{instance: instance} = state
6068
) do
61-
case Wasmex.Components.Instance.call_function(instance, name, params) do
62-
{:ok, result} -> {:reply, {:ok, result}, state}
63-
{:error, error} -> {:reply, {:error, error}, state}
69+
:ok = Wasmex.Components.Instance.call_function(instance, name, params, from)
70+
{:noreply, state}
71+
end
72+
73+
@impl true
74+
def handle_info({:returned_function_call, result, from}, state) do
75+
case result do
76+
{:raise, reason} -> raise(reason)
77+
valid_result -> GenServer.reply(from, valid_result)
6478
end
79+
80+
{:noreply, state}
81+
end
82+
83+
@impl true
84+
def handle_info(
85+
{:invoke_callback, name, token, params},
86+
%{imports: imports, instance: _instance, component: component} = state
87+
) do
88+
{:fn, function} = Map.get(imports, name)
89+
result = apply(function, params)
90+
:ok = Wasmex.Native.component_receive_callback_result(component.resource, token, true, result)
91+
{:noreply, state}
6592
end
6693

6794
defp stringify(s) when is_binary(s), do: s

lib/wasmex/components/component.ex

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ defmodule Wasmex.Components.Component do
3333
end
3434

3535
defmacro __using__(opts) do
36+
macro_imports = Keyword.get(opts, :imports, %{})
37+
3638
genserver_setup =
3739
quote do
3840
use GenServer
3941

4042
def start_link(opts) do
41-
Wasmex.Components.start_link(opts)
43+
Wasmex.Components.start_link(opts |> Keyword.put(:imports, unquote(macro_imports)))
4244
end
4345

4446
def handle_call(request, from, state) do

lib/wasmex/components/component_instance.ex

+9-4
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ defmodule Wasmex.Components.Instance do
1919
}
2020
end
2121

22-
def new(store_or_caller, component) do
22+
def new(store_or_caller, component, imports) do
2323
%{resource: store_or_caller_resource} = store_or_caller
2424
%{resource: component_resource} = component
2525

26-
case Wasmex.Native.component_instance_new(store_or_caller_resource, component_resource) do
26+
case Wasmex.Native.component_instance_new(
27+
store_or_caller_resource,
28+
component_resource,
29+
imports
30+
) do
2731
{:error, err} -> {:error, err}
2832
resource -> {:ok, __wrap_resource__(store_or_caller_resource, resource)}
2933
end
@@ -32,8 +36,9 @@ defmodule Wasmex.Components.Instance do
3236
def call_function(
3337
%__MODULE__{store_resource: store_resource, instance_resource: instance_resource},
3438
function,
35-
args
39+
args,
40+
from
3641
) do
37-
Wasmex.Native.component_call_function(store_resource, instance_resource, function, args)
42+
Wasmex.Native.component_call_function(store_resource, instance_resource, function, args, from)
3843
end
3944
end

lib/wasmex/native.ex

+4-2
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,11 @@ defmodule Wasmex.Native do
8989

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

92-
def component_instance_new(_store, _component), do: error()
92+
def component_instance_new(_store, _component, _imports), do: error()
9393

94-
def component_call_function(_store, _instance, _function_name, _params), do: error()
94+
def component_call_function(_store, _instance, _function_name, _params, _from), do: error()
95+
96+
def component_receive_callback_result(_component, _2, _3, _4), do: error()
9597

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

native/wasmex/src/atoms.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
rustler::atoms! {
22
ok,
33
error,
4+
raise,
45
__nil__ = "nil",
56

67
// memory types

native/wasmex/src/component.rs

+19-39
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@ use rustler::Error;
44
use rustler::NifResult;
55
use rustler::ResourceArc;
66
use wasmtime::Store;
7+
use wit_parser::decoding::DecodedWasm;
8+
use wit_parser::Resolve;
9+
use wit_parser::WorldId;
710

811
use std::sync::Mutex;
9-
use wasmtime::component::{Component, Instance, Linker};
12+
use wasmtime::component::Component;
1013

1114
pub struct ComponentResource {
1215
pub inner: Mutex<Component>,
16+
pub parsed: ParsedComponent,
17+
}
18+
19+
pub struct ParsedComponent {
20+
pub world_id: WorldId,
21+
pub resolve: Resolve,
1322
}
1423

1524
#[rustler::resource_impl()]
@@ -27,49 +36,20 @@ pub fn new_component(
2736
)))
2837
})?);
2938
let bytes = component_binary.as_slice();
39+
let decoded_wasm = wit_parser::decoding::decode(bytes)
40+
.map_err(|e| Error::Term(Box::new(format!("Unable to decode WASM: {e}"))))?;
41+
let parsed_component = match decoded_wasm {
42+
DecodedWasm::WitPackage(_, _) => {
43+
return Err(rustler::Error::RaiseAtom("Only components are supported"))
44+
}
45+
DecodedWasm::Component(resolve, world_id) => ParsedComponent { world_id, resolve },
46+
};
3047

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

3451
Ok(ResourceArc::new(ComponentResource {
3552
inner: Mutex::new(component),
53+
parsed: parsed_component,
3654
}))
3755
}
38-
39-
#[rustler::nif(name = "component_instance_new")]
40-
pub fn new_component_instance(
41-
component_store_resource: ResourceArc<ComponentStoreResource>,
42-
component_resource: ResourceArc<ComponentResource>,
43-
) -> NifResult<ResourceArc<ComponentInstanceResource>> {
44-
let component_store: &mut Store<ComponentStoreData> =
45-
&mut *(component_store_resource.inner.lock().map_err(|e| {
46-
rustler::Error::Term(Box::new(format!(
47-
"Could not unlock component_store resource as the mutex was poisoned: {e}"
48-
)))
49-
})?);
50-
51-
let component = &mut component_resource.inner.lock().map_err(|e| {
52-
rustler::Error::Term(Box::new(format!(
53-
"Could not unlock component resource as the mutex was poisoned: {e}"
54-
)))
55-
})?;
56-
57-
let mut linker = Linker::new(component_store.engine());
58-
let _ = wasmtime_wasi::add_to_linker_sync(&mut linker);
59-
let _ = wasmtime_wasi_http::add_only_http_to_linker_sync(&mut linker);
60-
// Instantiate the component
61-
let instance = linker
62-
.instantiate(&mut *component_store, component)
63-
.map_err(|err| Error::Term(Box::new(err.to_string())))?;
64-
65-
Ok(ResourceArc::new(ComponentInstanceResource {
66-
inner: Mutex::new(instance),
67-
}))
68-
}
69-
70-
pub struct ComponentInstanceResource {
71-
pub inner: Mutex<Instance>,
72-
}
73-
74-
#[rustler::resource_impl()]
75-
impl rustler::Resource for ComponentInstanceResource {}

0 commit comments

Comments
 (0)