<% needs_make? = @params |> Map.values |> Enum.any?(&Type.needs_make?(&1.type)) %>
<% needs_size? = @params |> Enum.any?(fn {_index, param} -> Parameter.needs_size?(param) end) %>

fn <%=@name %>(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm) callconv(.c) e.ErlNifTerm {
    <%= cond do %>
    <% allocator = @allocator -> %>
    const allocator = nif.<%= allocator %>();
    <% @leak_check -> %>
    var gpa = beam.make_debug_allocator_instance();
    const allocator = gpa.allocator();
    <% :else -> %>
    const allocator = beam.allocator;
    <% end %>

    beam.context = .{
        .env = env,
        .mode = .<%= context(@concurrency) %>,
        .allocator = allocator,
        .io = beam.io.get(allocator),
    };

    <%= if needs_make? do %>
    var error_info: ?beam.term = null;
    <% end %>

    <%= if needs_size? do %>
    var payload_sizes: struct {
        <%= for {index, param} <- @params, Parameter.needs_size?(param) do %>
        arg<%= index %>: ?usize = null,
        <% end %>
    } = .{};
    <% end %>

    <%= for {index, param} <- @params, param.in_out do %>
    var arg<%= index %>: <%= Parameter.render_in_out_type(param) %> = undefined;
    <% end %>

    const return_value = execution_block: {
        const payload_opts = .{
            <%= for {index, param} <- @params, do: Parameter.render_payload_options(param, index) %>
        };

        var error_index: u8 = undefined;
        const payload = beam.payload.build(nif.<%= @alias || @name %>, argc, args, &error_index, payload_opts) catch |err| {
            if (err == error.OutOfMemory) return e.enif_raise_exception(env, beam.make(.{err, error_index}, .{}).v);

            <%= if needs_make? do %>
                return e.enif_raise_exception(env, beam.make(.{err, error_index, error_info orelse beam.make_empty_list(.{})}, .{}).v);
            <% else %>
                unreachable;
            <% end %>
        };

        defer {
            beam.payload.cleanup(payload, .{
            <%= for {index, param} <- @params, do: Parameter.render_cleanup(param, index) %>
            });
        }

        const result = @call(.auto, nif.<%= @alias || @name %>, payload) <%= Nif.maybe_catch(@return.type) %>;

        <%= Return.render_return(@return) %>
    };

    <%= if @leak_check do %>
    if (gpa.deinit() == .leak) {
        return e.enif_raise_exception(env, beam.make(.{
            .__struct__ = .@"Elixir.RuntimeError",
            .message = "memory leak detected in function `<%= inspect @module %>.<%= @alias || @name %>/<%= map_size(@params) %>`",
            .__exception__ = true
        }, .{}).v);
    }
    <% end %>

    return return_value;
}
