diff --git a/lib/zig/error_prong.ex b/lib/zig/error_prong.ex index 0608bba5..ae8c58ee 100644 --- a/lib/zig/error_prong.ex +++ b/lib/zig/error_prong.ex @@ -4,7 +4,7 @@ defmodule Zig.ErrorProng do # default parameter errors handling. - def argument_error_prong(:elixir, _) do + def argument_error_prong(:elixir) do quote do :error, {:argument_error, index, error_lines} -> new_stacktrace = @@ -61,7 +61,7 @@ defmodule Zig.ErrorProng do end end - def argument_error_prong(:erlang, _) do + def argument_error_prong(:erlang) do [ "error:{error, badarg, _ExtraStacktrace}:Stacktrace -> erlang:raise(error, badarg, Stacktrace)" ] diff --git a/lib/zig/nif.ex b/lib/zig/nif.ex index c8d7dad3..7e4b1c92 100644 --- a/lib/zig/nif.ex +++ b/lib/zig/nif.ex @@ -161,6 +161,18 @@ defmodule Zig.Nif do # end # end + # COMMON TOOLS + # generates AST for parameters. + @spec elixir_parameters(arity, used :: boolean) :: [Macro.t] + def elixir_parameters(0, _), do: [] + def elixir_parameters(arity, true), do: Enum.map(1..arity, &{:"arg#{&1}", [], Elixir}) + def elixir_parameters(arity, false), do: Enum.map(1..arity, &{:"_arg#{&1}", [], Elixir}) + + @spec erlang_parameters(arity, used :: boolean) :: [{:var, atom()}] + def erlang_parameters(0, _), do: [] + def erlang_parameters(arity, true), do: Enum.map(1..arity, &{:var, :"X#{&1}"}) + def erlang_parameters(arity, false), do: Enum.map(1..arity, &{:var, :"_X#{&1}"}) + # Access behaviour guards @impl true def get_and_update(_, _, _), do: raise("you should not update a function") diff --git a/lib/zig/nif/_basic.ex b/lib/zig/nif/_basic.ex index 5e3a48e6..d8c6e5f4 100644 --- a/lib/zig/nif/_basic.ex +++ b/lib/zig/nif/_basic.ex @@ -16,14 +16,28 @@ defmodule Zig.Nif.Basic do alias Zig.Nif.DirtyIo alias Zig.Nif.Synchronous alias Zig.Type + alias Zig.Type.Integer import Zig.QuoteErl # marshalling setup + # + # for a basic function, a marshalling function is required unless the function ONLY + # has term or erl_nif_term parameters, because those parameters may emit `argumenterror` + # which needs to be trapped and converted. + # + # returning a value cannot error so they do not need to be marshalled, except for + # integers of size greater than 64 bits. + + defp marshals_param?(:term), do: false + defp marshals_param?(:erl_nif_term), do: false + defp marshals_param?(_), do: true + + defp marshals_return?(%Integer{bits: bits}), do: bits > 64 + defp marshals_return?(_), do: false defp needs_marshal?(nif) do - Enum.any?(nif.signature.params, &Type.marshals_param?/1) or - Type.marshals_return?(nif.signature.return) + Enum.any?(nif.signature.params, &marshals_param?/1) or marshals_return?(nif.signature.return) end defp marshal_name(nif), do: :"marshalled-#{nif.name}" @@ -33,26 +47,16 @@ defmodule Zig.Nif.Basic do end def render_elixir(%{signature: signature} = nif) do - {empty_params, used_params} = - case signature.arity do - 0 -> - {[], []} - - n -> - 1..n - |> Enum.map(&{{:"_arg#{&1}", [], Elixir}, {:"arg#{&1}", [], Elixir}}) - |> Enum.unzip() - end - error_text = "nif for function #{signature.name}/#{signature.arity} not bound" def_or_defp = if nif.export, do: :def, else: :defp if needs_marshal?(nif) do - render_elixir_marshalled(nif, def_or_defp, empty_params, used_params, error_text) + render_elixir_marshalled(nif, def_or_defp, signature.arity, error_text) else + unused_params = Nif.elixir_parameters(signature.arity, false) quote context: Elixir do - unquote(def_or_defp)(unquote(signature.name)(unquote_splicing(empty_params))) do + unquote(def_or_defp)(unquote(signature.name)(unquote_splicing(unused_params))) do :erlang.nif_error(unquote(error_text)) end end @@ -62,10 +66,13 @@ defmodule Zig.Nif.Basic do defp render_elixir_marshalled( %{signature: signature} = nif, def_or_defp, - empty_params, - used_params, + arity, error_text ) do + + used_params = Nif.elixir_parameters(signature.arity, true) + unused_params = Nif.elixir_parameters(signature.arity, false) + marshal_name = marshal_name(nif) marshal_params = @@ -74,7 +81,7 @@ defmodule Zig.Nif.Basic do |> Enum.with_index() |> Enum.flat_map(fn {{param_type, param}, index} -> List.wrap( - if Type.marshals_param?(param_type) do + if marshals_param?(param_type) do Type.marshal_param(param_type, param, index, :elixir) end ) @@ -86,20 +93,35 @@ defmodule Zig.Nif.Basic do end marshal_return = - if Type.marshals_return?(signature.return) do + if marshals_return?(signature.return) do Type.marshal_return(signature.return, return, :elixir) else return end + function_code = [do: quote do + unquote_splicing(marshal_params) + return = unquote(marshal_name)(unquote_splicing(used_params)) + unquote(marshal_return) + end] + + argument_error_prong = List.wrap(if true do + Zig.ErrorProng.argument_error_prong(:elixir) + end) + + error_return_prong = [] + + error_prongs = case {argument_error_prong, error_return_prong} do + {[], []} -> [] + _ -> [catch: argument_error_prong ++ error_return_prong] + end + + function_block = function_code ++ error_prongs + quote do - unquote(def_or_defp)(unquote(nif.name)(unquote_splicing(used_params))) do - unquote_splicing(marshal_params) - return = unquote(marshal_name)(unquote_splicing(used_params)) - unquote(marshal_return) - end + unquote(def_or_defp)(unquote(nif.name)(unquote_splicing(used_params)), unquote(function_block)) - defp unquote(marshal_name)(unquote_splicing(empty_params)) do + defp unquote(marshal_name)(unquote_splicing(unused_params)) do :erlang.nif_error(unquote(error_text)) end end @@ -124,7 +146,7 @@ defmodule Zig.Nif.Basic do type.params |> Enum.zip(used_vars) |> Enum.map_reduce([], fn {param_type, {:var, var}}, so_far -> - if Type.marshals_param?(param_type) do + if marshals_param?(param_type) do {{:var, :"#{var}_m"}, [so_far, Type.marshal_param(param_type, var, nil, :erlang)]} else {{:var, var}, so_far} @@ -132,7 +154,7 @@ defmodule Zig.Nif.Basic do end) result_code = - if Type.marshals_param?(type.return) do + if marshals_param?(type.return) do Type.marshal_return(type.return, :Result, :erlang) else "Result" diff --git a/lib/zig/templates/basic.zig.eex b/lib/zig/templates/basic.zig.eex index 77a98bed..ae6a3a37 100644 --- a/lib/zig/templates/basic.zig.eex +++ b/lib/zig/templates/basic.zig.eex @@ -22,7 +22,7 @@ fn <%= @name %>(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm) callc var error_index: u8 = undefined; const payload = beam.payload.build(nif.<%= @name %>, argc, args, &error_index, payload_opts) catch |err| { - return e.enif_raise_exception(env, beam.make(.{err, error_index, beam.make_empty_list(.{})}, .{}).v); + return e.enif_raise_exception(env, beam.make(.{err, error_index, error_info}, .{}).v); }; // defer beam.payload.cleanup(payload, cleanup_opts); diff --git a/lib/zig/type.ex b/lib/zig/type.ex index 4c767157..5c5b2cfa 100644 --- a/lib/zig/type.ex +++ b/lib/zig/type.ex @@ -14,19 +14,14 @@ defprotocol Zig.Type do alias Zig.Type.Resource @type t :: - Bool.t() | Enum.t() | Float.t() | Integer.t() | Struct.t() | :env | :pid | :port | :term - - @spec marshals_param?(t) :: boolean - @doc "beam-side type conversions that might be necessary to get an elixir parameter into a zig parameter" - def marshals_param?(type) + Array.t() | Bool.t() | Cpointer.t() | Error.t() | Zig.Type.Enum.t() | Float.t() | + Integer.t() | ManyPointer.t() | Optional.t() | Slice.t() | Struct.t() | :void | + :anyopaque_pointer | :env | :pid | :port | :term | :erl_nif_term | :erl_nif_binary | + :erl_nif_event | :erl_nif_binary_pointer | :stacktrace @spec marshal_param(t, Macro.t(), non_neg_integer, :elixir | :erlang) :: Macro.t() def marshal_param(type, variable, index, platform) - @spec marshals_return?(t) :: boolean - @doc "beam-side type conversions that might be necessary to get a zig return into an elixir return" - def marshals_return?(type) - @spec marshal_return(t, Macro.t(), Elixir | :erlang) :: Macro.t() def marshal_return(type, variable, platform) @@ -230,17 +225,12 @@ after def _default_payload_options, do: ".{.error_info = &error_info}," def _default_return, do: "break :result_block beam.make(result, .{}).v;" + def _default_marshal, do: [] end defimpl Zig.Type, for: Atom do alias Zig.Type - def marshals_param?(_), do: false - def marshals_return?(_), do: false - - def marshal_param(_, _, _, _), do: raise("unreachable") - def marshal_return(_, _, _), do: raise("unreachable") - def return_allowed?(type), do: type in ~w(term erl_nif_term pid void)a def render_return(:void), do: "_ = result; break :result_block beam.make(.ok, .{}).v;" diff --git a/lib/zig/type/bool.ex b/lib/zig/type/bool.ex index 1e91ae55..89504f06 100644 --- a/lib/zig/type/bool.ex +++ b/lib/zig/type/bool.ex @@ -13,8 +13,5 @@ defmodule Zig.Type.Bool do def render_payload_options(type, index, _), do: Type._default_payload_options() def render_return(type), do: Type._default_return() - def marshals_param?(_), do: false - def marshals_return?(_), do: false - def spec(_, _, _), do: Type.spec(:boolean) end diff --git a/lib/zig/type/cpointer.ex b/lib/zig/type/cpointer.ex index ef91434d..2889c097 100644 --- a/lib/zig/type/cpointer.ex +++ b/lib/zig/type/cpointer.ex @@ -24,9 +24,6 @@ defmodule Zig.Type.Cpointer do end end - def marshals_param?(_), do: false - def marshals_return?(_), do: false - def render_payload_options(type, index, _), do: Type._default_payload_options() def render_return(type), do: Type._default_return() diff --git a/lib/zig/type/enum.ex b/lib/zig/type/enum.ex index 4467fa78..76ab5ef4 100644 --- a/lib/zig/type/enum.ex +++ b/lib/zig/type/enum.ex @@ -16,11 +16,11 @@ defmodule Zig.Type.Enum do ~s(%Zig.Type.Enum{name: "#{enum.name}", tags: #{Kernel.inspect(enum.tags, opts)}}) end + def return_allowed?(_), do: true - - def marshals_param?(_), do: false - def marshals_return?(_), do: false - + + def marshal_param(_, _, _, _), do: Type._default_marshal() + def marshal_return(_, _, _), do: Type._default_marshal() def render_payload_options(type, index, _), do: Type._default_payload_options() def render_return(type), do: Type._default_return() diff --git a/lib/zig/type/float.ex b/lib/zig/type/float.ex index 0cce2116..16a56418 100644 --- a/lib/zig/type/float.ex +++ b/lib/zig/type/float.ex @@ -22,8 +22,6 @@ defmodule Zig.Type.Float do def spec(_, _, _), do: Type.spec(:float) def return_allowed?(_), do: true - def marshals_param?(_), do: false - def marshals_return?(_), do: false def render_payload_options(type, index, _), do: Type._default_payload_options() def render_return(type), do: Type._default_return() end diff --git a/lib/zig/type/integer.ex b/lib/zig/type/integer.ex index 90496276..2b7f92c0 100644 --- a/lib/zig/type/integer.ex +++ b/lib/zig/type/integer.ex @@ -51,9 +51,6 @@ defmodule Zig.Type.Integer do concat(["~t(", to_string(type), ")"]) end - def marshals_param?(%{bits: bits}), do: bits > 64 - def marshals_return?(%{bits: bits}), do: bits > 64 - def marshal_param(type, variable, index, :elixir) do marshal_param_elixir(type, variable, index) end @@ -224,9 +221,6 @@ defmodule Zig.Type.Integer do def render_return(type), do: Type._default_return() - def marshals_param?(%{bits: bits}), do: bits > 64 - def marshals_return?(%{bits: bits}), do: bits > 64 - def render_payload_options(type, index, _), do: Type._default_payload_options() def render_return(type), do: Type._default_return() end diff --git a/lib/zig/type/manypointer.ex b/lib/zig/type/manypointer.ex index 4880c5f9..98ec66ed 100644 --- a/lib/zig/type/manypointer.ex +++ b/lib/zig/type/manypointer.ex @@ -26,8 +26,6 @@ defmodule Zig.Type.Manypointer do def return_allowed?(pointer), do: pointer.has_sentinel? and Type.return_allowed?(pointer.child) def missing_size?(_), do: true - def marshals_param?(_), do: false - def marshals_return?(_), do: false def render_payload_options(type, index, _), do: Type._default_payload_options() def render_return(type), do: Type._default_return() diff --git a/lib/zig/type/optional.ex b/lib/zig/type/optional.ex index 4b00fd0e..796ec3f5 100644 --- a/lib/zig/type/optional.ex +++ b/lib/zig/type/optional.ex @@ -11,9 +11,6 @@ defmodule Zig.Type.Optional do def return_allowed?(optional), do: Type.return_allowed?(optional.child) - def marshals_param?(_), do: false - def marshals_return?(_), do: false - def render_payload_options(type, index, _), do: Type._default_payload_options() def render_return(type), do: Type._default_return() diff --git a/lib/zig/type/slice.ex b/lib/zig/type/slice.ex index 271a7f6a..b872b676 100644 --- a/lib/zig/type/slice.ex +++ b/lib/zig/type/slice.ex @@ -81,8 +81,6 @@ defmodule Zig.Type.Slice do # ETC def return_allowed?(slice), do: Type.return_allowed?(slice.child) - def marshals_param?(_), do: false - def marshals_return?(_), do: false def render_payload_options(type, index, _), do: Type._default_payload_options() def render_return(type), do: Type._default_return() diff --git a/lib/zig/type/struct.ex b/lib/zig/type/struct.ex index 67c39311..7b6e37d9 100644 --- a/lib/zig/type/struct.ex +++ b/lib/zig/type/struct.ex @@ -119,8 +119,6 @@ defmodule Zig.Type.Struct do |> Enum.all?() end - def marshals_param?(_), do: false - def marshals_return?(_), do: false def render_payload_options(type, index, _), do: Type._default_payload_options() def render_return(type), do: Type._default_return() end diff --git a/priv/beam/get.zig b/priv/beam/get.zig index bba134d5..ec9abb21 100644 --- a/priv/beam/get.zig +++ b/priv/beam/get.zig @@ -726,6 +726,7 @@ inline fn error_line(msg: anytype, opts: anytype) void { }; @compileError(error_msg); } + opts.error_info.v = e.enif_make_list_cell(options.env(opts), beam.make(msg, opts).v, opts.error_info.v); } } diff --git a/test/integration/types/enum_test.exs b/test/integration/types/enum_test.exs index 05922b38..6a99f038 100644 --- a/test/integration/types/enum_test.exs +++ b/test/integration/types/enum_test.exs @@ -37,7 +37,7 @@ defmodule ZiglerTest.Types.EnumTest do test "if you try to use an invalid number" do assert_raise ArgumentError, - "errors were found at the given arguments:\n\n * 1st argument: \n\n note: not an integer value for EnumType (should be one of `[0, 1]`)\n expected: 0 | 1 | :foo | :bar (for `EnumType`)\n got: `42`\n", + "errors were found at the given arguments:\n\n * 1st argument: \n\n note: not an integer value for EnumType (should be one of `[0, 1]`)\n expected: integer (for `u1`)\n got: `42`\n note: out of bounds (0..1)\n expected: 0 | 1 | :foo | :bar (for `EnumType`)\n got: `42`\n", fn -> untagged_swap(42) end end end