Current section

Files

Jump to
wasp_vm lib wasp_vm.ex
Raw

lib/wasp_vm.ex

defmodule WaspVM do
use GenServer
alias WaspVM.Decoder
alias WaspVM.Stack
alias WaspVM.ModuleInstance
alias WaspVM.Store
alias WaspVM.Executor
require IEx
@enforce_keys [:modules, :stack, :store]
defstruct [:modules, :stack, :store]
@moduledoc """
Execute WebAssembly code
"""
@doc """
Starts the Virtual Machine and returns the PID which is used to
interface with the VM.
"""
@spec start :: {:ok, pid}
def start, do: GenServer.start_link(__MODULE__, [])
@doc false
def init(_args) do
{
:ok,
%WaspVM{
modules: [],
stack: Stack.new(),
store: %Store{}
}
}
end
@doc """
Load a binary WebAssembly file (.wasm) as a module into the VM
"""
@spec load_file(pid, String.t()) :: :ok
def load_file(ref, filename) do
GenServer.call(ref, {:load_module, Decoder.decode_file(filename)}, :infinity)
end
@doc """
Load a WebAssembly module directly from a binary into the VM
"""
@spec load(pid, binary) :: :ok
def load(ref, binary) when is_binary(binary) do
GenServer.call(ref, {:load_module, Decoder.decode(binary)}, :infinity)
end
@doc """
Call an exported function by name from the VM. The function must have
been loaded in through a module using load_file/2 or load/2 previously
"""
@spec execute(pid, String.t(), list) :: :ok | {:ok, any} | {:error, any}
def execute(ref, func, args \\ []) do
GenServer.call(ref, {:execute, func, args}, :infinity)
end
@doc """
Returns the state for a given VM instance
"""
@spec vm_state(pid) :: WaspVM
def vm_state(ref), do: GenServer.call(ref, :vm_state, :infinity)
def handle_call({:load_module, module}, _from, vm) do
{moduleinst, store} = ModuleInstance.instantiate(ModuleInstance.new(), module, vm.store)
modules = [moduleinst | vm.modules]
{:reply, :ok, Map.merge(vm, %{modules: modules, store: store})}
end
def handle_call({:execute, fname, args}, _from, vm) do
{func_addr, _module} =
Enum.find_value(vm.modules, fn module ->
a =
Enum.find_value(module.exports, fn export ->
if export !== nil do
{name, addr} = export
if name == fname, do: addr, else: false
else
false
end
end)
if a, do: {a, module}, else: {:not_found, 0}
end)
{reply, vm} =
case func_addr do
:not_found -> {{:error, :no_exported_function, fname}, vm}
addr -> execute_func(vm, addr, args)
end
{:reply, reply, vm}
end
def handle_call(:vm_state, _from, vm), do: {:reply, vm, vm}
@spec execute_func(WaspVM, integer, list) :: tuple
defp execute_func(vm, addr, args) do
stack = Enum.reduce(args, vm.stack, fn arg, s -> Stack.push(s, arg) end)
vm =
vm
|> Map.put(:stack, stack)
|> Executor.create_frame_and_execute(addr)
case vm do
tuple when is_tuple(tuple) -> tuple
_ -> {{:ok, hd(vm.stack.elements)}, vm}
end
end
end