-
Notifications
You must be signed in to change notification settings - Fork 5
/
host_function.ex
118 lines (86 loc) · 3.21 KB
/
host_function.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
defmodule AlchemyVM.HostFunction do
@moduledoc """
Exposes a DSL for defining and importing host functions
"""
@doc false
defmacro __using__(_opts) do
quote do
import AlchemyVM.HostFunction
@before_compile AlchemyVM.HostFunction
Module.register_attribute(__MODULE__, :host_funcs, accumulate: true)
end
end
@doc """
Defines a host function that can be passed in to the VM using `create_imports/1`
Will use the name of the module that it's defined in as the name of the
corresponding WebAssembly module that this host function can be imported from.
`fname` can be a string or an atom. A variable called `ctx` is available
within the context of the macro body as a pointer to VM state, to be used
with functions defined in `AlchemyVM.HostFunction.API`.
## Usage
Create an Elixir module that will be used to import host functions into
WebAssembly:
defmodule Math do
use AlchemyVM.HostFunction
defhost add(a, b) do
a + b
end
end
Somewhere in your app:
defmodule MyWaspApp do
def start do
{:ok, pid} = AlchemyVM.start()
imports = AlchemyVM.HostFunction.create_imports(Math)
AlchemyVM.load_file(pid, "path/to/wasm/file.wasm", imports)
end
end
In the above "path/to/wasm/file.wasm", the host function can now be imported:
(import "Math" "add" (func (param i32 i32) (result i32)))
Note that the Elixir module name was used to define the WebAssembly module name
that's being used for the import.
"""
defmacro defhost(head, do: block) do
{fname, args} = Macro.decompose_call(head)
name = to_string(fname)
quote generated: true do
def hostfunc(unquote(name), unquote(args), var!(ctx)), do: unquote(block)
Module.put_attribute(__MODULE__, :host_funcs, unquote(name))
end
end
@doc """
Pass in an Elixir module or list of Elixir modules that implement `defhost`
calls to generate imports for WebAssembly to be passed in when loading a
WebAssembly module into the VM
## Usage
When using a single module to define imports:
AlchemyVM.HostFunction.create_imports(Module1)
Functions will be accessible in the WebAssembly module as:
(import "Module1" "function_name")
When using multiple modules to define imports:
AlchemyVM.HostFunction.create_imports([Module1, Module2, Module3])
Functions will be accessible in the WebAssembly module as:
(import "Module1" "function_name")
(import "Module2" "function_name")
(import "Module3" "function_name")
"""
@spec create_imports(list | atom) :: map
def create_imports(modules) when is_list(modules) do
Enum.reduce(modules, %{}, fn mod, acc ->
"Elixir." <> mod_string = to_string(mod)
Map.put(acc, mod_string, create_import(mod))
end)
end
def create_imports(module), do: create_imports([module])
defp create_import(module) do
module
|> apply(:hostfuncs, [])
|> Enum.reduce(%{}, fn fname, acc ->
Map.put(acc, fname, fn ctx, args -> apply(module, :hostfunc, [fname, args, ctx]) end)
end)
end
defmacro __before_compile__(_env) do
quote do
def hostfuncs, do: @host_funcs
end
end
end