Skip to content

Commit

Permalink
basics on on_load functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
ityonemo committed May 18, 2024
1 parent 0800776 commit 7c94902
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 225 deletions.
14 changes: 13 additions & 1 deletion lib/zig/_module.ex
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,21 @@ defmodule Zig.Module do
nif = Path.join(__DIR__, "templates/module.zig.eex")
EEx.function_from_file(:def, :render_zig, nif, [:assigns])

on_load = Path.join(__DIR__, "templates/on_load.zig.eex")
EEx.function_from_file(:def, :render_on_load, on_load, [:assigns])

def render_elixir(module, zig_code) do
module_name = "#{module.module}"

on_load_code =
if {:__on_load__, 0} in Module.definitions_in(module.module) do
quote do
__on_load__()
end
else
0
end

load_nif_fn =
quote do
def __load_nifs__ do
Expand All @@ -190,7 +202,7 @@ defmodule Zig.Module do
|> Path.join("lib")
|> Path.join(unquote(module_name))
|> String.to_charlist()
|> :erlang.load_nif(0)
|> :erlang.load_nif(unquote(on_load_code))
|> case do
:ok ->
Logger.debug("loaded module at #{unquote(module_name)}")
Expand Down
82 changes: 44 additions & 38 deletions lib/zig/_nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,36 @@ defmodule Zig.Nif do
alias Zig.Type.Function

@typep raw :: %__MODULE__{
name: atom,
export: boolean,
concurrency: Concurrency.t(),
line: integer,
file: Path.t(),
signature: Function.t(),
params: integer,
return: Return.t(),
leak_check: boolean(),
alias: nil | atom,
doc: nil | String.t(),
spec: Macro.t(),
raw: :term | :erl_nif_term
}
name: atom,
export: boolean,
concurrency: Concurrency.t(),
line: integer,
file: Path.t(),
signature: Function.t(),
params: integer,
return: Return.t(),
leak_check: boolean(),
alias: nil | atom,
doc: nil | String.t(),
spec: Macro.t(),
raw: :term | :erl_nif_term
}

@typep specified :: %__MODULE__{
name: atom,
export: boolean,
concurrency: Concurrency.t(),
line: integer,
file: Path.t(),
signature: Function.t(),
params: %{optional(integer) => Parameter.t()},
return: Return.t(),
leak_check: boolean(),
alias: nil | atom,
doc: nil | String.t(),
spec: Macro.t(),
raw: nil
}
name: atom,
export: boolean,
concurrency: Concurrency.t(),
line: integer,
file: Path.t(),
signature: Function.t(),
params: %{optional(integer) => Parameter.t()},
return: Return.t(),
leak_check: boolean(),
alias: nil | atom,
doc: nil | String.t(),
spec: Macro.t(),
raw: nil
}

@type t :: raw | specified

Expand Down Expand Up @@ -116,16 +116,17 @@ defmodule Zig.Nif do
false ->
quote do
end

_ ->
nif.spec
|> List.wrap
|> List.wrap()
|> Enum.map(fn spec ->

nil
end)

{_, list} when is_list(list) ->
Enum.map(list, fn spec ->
quote do
quote do
@spec unquote(spec)
end
end)
Expand All @@ -142,13 +143,14 @@ defmodule Zig.Nif do

def render_elixir_spec(%{raw: t, params: arities} = nif) when not is_nil(t) do
Enum.map(arities, fn arity ->
param_spec = case arity do
0 -> []
arity -> Enum.map(0..arity - 1, fn _ -> {:term, [], []} end)
end

param_spec =
case arity do
0 -> []
arity -> Enum.map(0..(arity - 1), fn _ -> {:term, [], []} end)
end

return_spec = Type.render_elixir_spec(nif.return.type, :return, nif.return)

quote do
unquote(nif.name)(unquote_splicing(param_spec)) :: unquote(return_spec)
end
Expand Down Expand Up @@ -196,7 +198,11 @@ defmodule Zig.Nif do
|> Enum.flat_map(fn
{function, fptr, concurrency} ->
flags = Map.fetch!(@flags, concurrency)
Enum.map(arities(nif), &~s(.{.name="#{function}", .arity=#{&1}, .fptr=#{fptr}, .flags=#{flags}}))

Enum.map(
arities(nif),
&~s(.{.name="#{function}", .arity=#{&1}, .fptr=#{fptr}, .flags=#{flags}})
)
end)
end

Expand Down
5 changes: 2 additions & 3 deletions lib/zig/nif/_basic.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ defmodule Zig.Nif.Basic do
def render_elixir(%{raw: raw} = nif) when not is_nil(raw) do
Enum.map(nif.params, fn arity ->
unused_params = Nif.elixir_parameters(arity, false)

quote do
unquote(style(nif))(unquote(nif.name)(unquote_splicing(unused_params))) do
:erlang.nif_error(unquote(error_text(nif, nif.params)))
Expand All @@ -81,9 +82,7 @@ defmodule Zig.Nif.Basic do
end
end

defp render_elixir_marshalled(
%{signature: %{arity: arity, return: return}} = nif
) do
defp render_elixir_marshalled(%{signature: %{arity: arity, return: return}} = nif) do
used_params_ast = Nif.elixir_parameters(arity, true)
unused_params_ast = Nif.elixir_parameters(arity, false)

Expand Down
151 changes: 42 additions & 109 deletions lib/zig/sema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ defmodule Zig.Sema do
alias Zig.Type.Integer
alias Zig.Type.Manypointer

@enforce_keys [:functions, :types, :decls]
@enforce_keys [:functions, :types, :decls, :callbacks]
defstruct @enforce_keys

@type t :: %__MODULE__{
functions: [Function.t()],
types: keyword(Type.t()),
decls: keyword(Type.t())
decls: keyword(Type.t()),
callbacks: [Function.t()]
}

# PHASE 1: SEMA EXECUTION
Expand All @@ -31,35 +32,62 @@ defmodule Zig.Sema do
|> Zig.Command.run_sema!(module)
|> Jason.decode!()
|> tap(&maybe_dump(&1, module))
|> validate_callbacks(module)
|> reject_ignored(module)
|> assign_callbacks(module)
|> integrate_sema(module)
|> then(&Map.replace!(module, :sema, &1))
rescue
e in Zig.CompileError ->
reraise Zig.CompileError.resolve(e, module), __STACKTRACE__
end

defp validate_callbacks(sema, _module) do
IO.warn("not implemented yet")
defp assign_callbacks(sema, module) do
sema
|> Map.put("callbacks", [])
|> then(fn sema -> Enum.reduce(module.callbacks, sema, &move_callback/2) end)
end

defp move_callback({type, name}, %{"functions" => functions, "callbacks" => callbacks} = sema) do
{new_functions, new_callback} = find_remove(functions, [], "#{name}", type)
%{sema | "functions" => new_functions, "callbacks" => [{type, new_callback} | callbacks]}
end

defp find_remove([%{"name" => name} = callback | rest], so_far, name, _type),
do: {Enum.reverse(so_far, rest), callback}

defp find_remove([other | rest], so_far, name, type),
do: find_remove(rest, [other | so_far], name, type)

defp find_remove([], _, name, type) do
raise CompileError, description: "#{type} callback #{name} not found"
end

# removes "ignored" and "callback" functions from the semantic analysis.
defp reject_ignored(json, module) do
ignored = Enum.map(module.ignore, &"#{&1}") ++ Enum.map(module.callbacks, &"#{elem(&1, 1)}")
ignored = Enum.map(module.ignore, &"#{&1}")

Map.update!(json, "functions", fn
functions ->
Enum.reject(functions, &(&1["name"] in ignored))
end)
end

defp integrate_sema(%{"functions" => functions, "types" => types, "decls" => decls}, module) do
defp integrate_sema(
%{
"functions" => functions,
"types" => types,
"decls" => decls,
"callbacks" => callbacks
},
module
) do
%__MODULE__{
functions: Enum.map(functions, &Function.from_json(&1, module.module)),
types: Enum.map(types, &type_from_json(&1, module.module)),
decls: Enum.map(decls, &const_from_json/1)
decls: Enum.map(decls, &const_from_json/1),
callbacks: Enum.map(callbacks, fn
{type, json} -> {type, Function.from_json(json, module.module)}
end)
}
end

Expand Down Expand Up @@ -144,11 +172,12 @@ defmodule Zig.Sema do
opts
)
when t in ~w[term erl_nif_term]a do
arities = case Keyword.fetch(opts, :arity) do
{:ok, arity} when arity in 0..63 -> arities(arity)
{:ok, {:.., _, _} = range} -> arities(range)
{:ok, list} when is_list(list) -> Enum.flat_map(list, &arities/1)
end
arities =
case Keyword.fetch(opts, :arity) do
{:ok, arity} when arity in 0..63 -> arities(arity)
{:ok, {:.., _, _} = range} -> arities(range)
{:ok, list} when is_list(list) -> Enum.flat_map(list, &arities/1)
end

%{nif | signature: sema, raw: t, params: arities, return: Return.new(t)}
end
Expand Down Expand Up @@ -211,100 +240,4 @@ defmodule Zig.Sema do
line: nif.line
end
end

# defp adjust_raw(function, opts) do
# case {function, opts[:raw]} do
# {_, nil} ->
# function
#
# # use this to identify that the function is a raw call. `child` could be
# # either :term or :erl_nif_term.
# {%{params: [:env, _, %Manypointer{child: child}]}, integer} when is_integer(integer) ->
# %{function | params: List.duplicate(child, integer), arity: integer}
#
# {_, {:c, integer}} when is_integer(integer) ->
# %{function | params: List.duplicate(:erl_nif_term, integer), arity: integer}
# end
# end
#
#

#
# defp validate_usable!(function, types, opts) do
# with :ok <- validate_args(function, types),
# :ok <- validate_return(function.return),
# :ok <- validate_struct_pub(function.return, types, "return", function.name) do
# :ok
# else
# {:error, msg} ->
# file_path =
# opts
# |> Keyword.fetch!(:mod_file)
# |> Path.relative_to_cwd()
#
# %{location: {raw_line, _}} =
# opts
# |> Keyword.fetch!(:parsed)
# |> find_function(function.name)
#
# {file, line} =
# opts
# |> Keyword.fetch!(:manifest)
# |> Manifest.resolve(file_path, raw_line)
#
# raise CompileError,
# description: msg,
# file: file,
# line: line
# end
# end
#
# defp find_function(%{code: code}, function) do
# Enum.find(code, fn
# %Zig.Parser.Function{name: ^function} -> true
# %Zig.Parser.Const{name: ^function} -> true
# _ -> false
# end)
# end
#
# defp validate_args(function, types) do
# function.params
# |> Enum.with_index(1)
# |> Enum.reduce_while(:ok, fn
# {type, index}, :ok ->
# case validate_struct_pub(type, types, "argument #{index}", function.name) do
# :ok ->
# {:cont, :ok}
#
# error = {:error, _msg} ->
# {:halt, error}
# end
# end)
# end
#
# # explicitly, all return values that are permitted
# defp validate_return(type) do
# if Type.return_allowed?(type) do
# :ok
# else
# {:error, "functions returning #{type} cannot be nifs"}
# end
# end
#
# defp validate_struct_pub(%Struct{name: name}, types, location, function) do
# Enum.reduce_while(
# types,
# {:error, "struct `#{name}` (#{location} of function `#{function}`) is not a `pub` struct"},
# fn
# %{name: type_name}, error = {:error, _} ->
# if Atom.to_string(type_name) == name do
# {:halt, :ok}
# else
# {:cont, error}
# end
# end
# )
# end
#
# defp validate_struct_pub(_, _, _, _), do: :ok
end
2 changes: 1 addition & 1 deletion lib/zig/templates/module.zig.eex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const threads = beam.threads;
fn load(env: beam.env, priv_data: [*c]?*anyopaque, load_info: e.ErlNifTerm) callconv(.C) c_int {
load_resources(env);
<%= if @callbacks[:on_load] do %>
return nif.<%= @callbacks[:on_load] %>(env, priv_data, .{.v = load_info});
<%= render_on_load(assigns) %>
<% else %>
_ = priv_data;
_ = load_info;
Expand Down
Loading

0 comments on commit 7c94902

Please sign in to comment.