defmodule Zig.Nif.Threaded do
@behaviour Zig.Nif.Concurrency
alias Zig.ErrorProng
alias Zig.Nif
alias Zig.Type
require EEx
import Zig.QuoteErl
@impl true
def render_elixir(%{type: %{name: name, arity: arity}} = nif) do
def_or_defp = if nif.export, do: :def, else: :defp
{empty_params, used_params} =
case arity do
0 ->
{[], []}
n ->
1..n
|> Enum.map(&{{:"_arg#{&1}", [], Elixir}, {:"arg#{&1}", [], Elixir}})
|> Enum.unzip()
end
ignore = [nif.name, :"#{nif.name}-join"]
error_prongs =
nif
|> error_prongs()
|> Enum.flat_map(&apply(ErrorProng, &1, [:elixir, ignore]))
quote context: Elixir do
unquote(def_or_defp)(unquote(name)(unquote_splicing(used_params))) do
ref = unquote(entrypoint(nif, :launch))(unquote_splicing(used_params))
receive do
{:done, ^ref} ->
unquote(entrypoint(nif, :join))(ref)
{:error, ^ref} ->
raise unquote("thread for function #{name} failed during launch")
end
catch
unquote(error_prongs)
end
defp unquote(entrypoint(nif, :launch))(unquote_splicing(empty_params)) do
:erlang.nif_error(unquote("nif for function #{name}/#{arity} not bound"))
end
defp unquote(entrypoint(nif, :join))(_ref) do
:erlang.nif_error(unquote("nif for function #{name}/#{arity} not bound"))
end
end
end
@impl true
def render_erlang(%{type: %{name: name, arity: arity}} = nif) do
{unused_vars, used_vars} =
case arity do
0 ->
{[], []}
n ->
1..n
|> Enum.map(&{{:var, :"_X#{&1}"}, {:var, :"X#{&1}"}})
|> Enum.unzip()
end
quote_erl(
"""
unquote(name)(unquote(...vars)) ->
Ref = unquote(launch_name)(unquote(...vars)),
receive
{done, Ref} ->
unquote(join_name)(Ref);
{error, Ref} ->
erlang:error(unquote(launch_fail_text))
end.
unquote(launch_name)(unquote(...unused_vars)) ->
erlang:nif_error(unquote(error_text)).
unquote(join_name)(_Ref) ->
erlang:nif_error(unquote(error_text)).
""",
name: name,
launch_name: entrypoint(nif, :launch),
join_name: entrypoint(nif, :join),
vars: used_vars,
unused_vars: unused_vars,
launch_fail_text: ~c"thread for function #{name} failed during launch",
error_text: ~c"nif for function #{name}/#{arity} not bound"
)
end
@impl true
threaded = Path.join(__DIR__, "../templates/threaded.zig.eex")
EEx.function_from_file(:def, :render_zig, threaded, [:assigns])
@impl true
def table_entries(nif) do
launch = entrypoint(nif, :launch)
join = entrypoint(nif, :join)
[
{launch, nif.type.arity, :"@\"#{launch}\"", :synchronous},
{join, 1, :"@\"#{join}\"", :synchronous}
]
end
defp entrypoint(nif, mode) do
:"#{nif.type.name}-#{mode}"
end
@impl true
def resources(nif), do: [{:root, :"ThreadResource_#{nif.type.name}"}]
defp error_prongs(nif) do
nif.type.params
|> Enum.map(&Type.error_prongs(&1, :argument))
|> List.insert_at(0, Type.error_prongs(nif.type.return, :return))
|> List.flatten()
end
end