diff --git a/lib/wasmex.ex b/lib/wasmex.ex index d3efebc..a1bd4a4 100644 --- a/lib/wasmex.ex +++ b/lib/wasmex.ex @@ -74,7 +74,9 @@ defmodule Wasmex do %{ memory: %Wasmex.Memory{}, - caller: %Wasmex.StoreOrCaller{} + caller: %Wasmex.StoreOrCaller{}, + pid: pid(), + instance: %Wasmex.Instance{} } = context The `caller` MUST be used instead of a `store` in Wasmex API functions. @@ -437,6 +439,17 @@ defmodule Wasmex do @spec module(pid()) :: {:ok, Wasmex.Module.t()} | {:error, any()} def module(pid), do: GenServer.call(pid, {:module}) + @doc ~S""" + Returns the `Wasmex.Instance` of the Wasm instance. + + ## Example + + iex> {:ok, pid} = Wasmex.start_link(%{bytes: File.read!(TestHelper.wasm_test_file_path())}) + iex> {:ok, %Wasmex.Instance{}} = Wasmex.instance(pid) + """ + @spec instance(pid()) :: {:ok, Wasmex.Instance.t()} | {:error, any()} + def instance(pid), do: GenServer.call(pid, {:instance}) + defp stringify_keys(struct) when is_struct(struct), do: struct defp stringify_keys(map) when is_map(map) do @@ -481,6 +494,11 @@ defmodule Wasmex do {:reply, {:ok, module}, state} end + @impl true + def handle_call({:instance}, _from, %{instance: instance} = state) do + {:reply, {:ok, instance}, state} + end + @impl true def handle_call( {:exported_function_exists, name}, @@ -510,14 +528,16 @@ defmodule Wasmex do @impl true def handle_info( {:invoke_callback, namespace_name, import_name, context, params, token}, - %{imports: imports} = state + %{imports: imports, instance: instance} = state ) do context = Map.merge( context, %{ memory: Wasmex.Memory.__wrap_resource__(Map.get(context, :memory)), - caller: Wasmex.StoreOrCaller.__wrap_resource__(Map.get(context, :caller)) + caller: Wasmex.StoreOrCaller.__wrap_resource__(Map.get(context, :caller)), + pid: self(), + instance: instance } ) diff --git a/test/wasmex_test.exs b/test/wasmex_test.exs index 0a1c83e..c7a4756 100644 --- a/test/wasmex_test.exs +++ b/test/wasmex_test.exs @@ -427,4 +427,52 @@ defmodule WasmexTest do assert Wasmex.Memory.read_string(store, memory, string_ptr, length) == "Hello World!" end end + + describe "call exported function in imported function callback" do + test "using instance in callback" do + wat = """ + (module + (func $add_import (import "env" "add_import") (param i32 i32) (result i32)) + + (func $call_import (export "call_import") (param i32 i32) (result i32) + (call $add_import (local.get 0) (local.get 1)) + ) + + (func $call_add (export "call_add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add + ) + ) + """ + + imports = %{ + env: %{ + add_import: + {:fn, [:i32, :i32], [:i32], + fn %{instance: instance, caller: caller}, a, b -> + Wasmex.Instance.call_exported_function(caller, instance, "call_add", [a, b], :from) + + receive do + { + :returned_function_call, + {:ok, [result]}, + :from + } -> + :ok + result + after + 1000 -> + raise "timeout on exported function call" + end + end} + } + } + + {:ok, store} = Wasmex.Store.new() + {:ok, module} = Wasmex.Module.compile(store, wat) + pid = start_supervised!({Wasmex, %{store: store, module: module, imports: imports}}) + assert Wasmex.call_function(pid, :call_import, [1, 2]) == {:ok, [3]} + end + end end