Skip to content

Commit

Permalink
more opts sequence resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
ityonemo committed Mar 24, 2024
1 parent 491503e commit 2348751
Show file tree
Hide file tree
Showing 17 changed files with 176 additions and 175 deletions.
95 changes: 47 additions & 48 deletions guides/3-allocators.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,28 @@ datastructures are all allocator-agnostic.
Zigler ships with three primary allocators, though you can certainly build
allocator strategies *on top* of those allocators.

## Raw Beam Allocator
## Basic Allocator

The first allocator is `raw_allocator`. This allocator wraps the
[nif allocator](https://www.erlang.org/doc/man/erl_nif.html#enif_alloc)
provided by the BEAM. You should use this allocator over `malloc` because it
often saves a syscall by using existing preallocated memory pools, because
it allows the VM to track how much memory your NIF is using, and possibly
gives better memory placement to avoid cache misses in your execution
thread.
The first allocator is `allocator`, which we will call `basic allocator`.
This allocator wraps the [nif allocator](https://www.erlang.org/doc/man/erl_nif.html#enif_alloc)
provided by the BEAM in the zig allocator interface. You should generally
use this allocator over `malloc` because it often saves a syscall by using
existing preallocated memory pools, because it allows the VM to track how
much memory your NIF is using, and possibly gives better memory placement
to avoid cache misses in your execution thread.

```elixir
~Z"""
const beam = @import("beam");
pub fn allocate_raw(env: beam.env, count: usize) !beam.term {
var slice = try beam.raw_allocator.alloc(u16, count);
defer beam.raw_allocator.free(slice);
pub fn allocate_raw(count: usize) !beam.term {
var slice = try beam.allocator.alloc(u16, count);
defer beam.allocator.free(slice);
for (slice, 0..) |*entry, index| {
entry.* = @intCast(index);
}
return beam.make(env, slice, .{});
return beam.make(slice, .{});
}
"""

Expand All @@ -36,9 +36,9 @@ test "raw allocator" do
end
```

> ### raw allocator limitations {: .warning }
> ### allocator limitations {: .warning }
>
> because the raw allocator directly wraps the beam allocator, according to
> because the basic allocator directly wraps the beam allocator, according to
> the documentation:
>
> The returned pointer is suitably aligned for any built-in type that
Expand All @@ -60,11 +60,11 @@ end
var global_zigler: []u8 = undefined;
pub fn zigler_alloc() !void {
global_zigler = try beam.raw_allocator.alloc(u8, 1_000_000);
global_zigler = try beam.allocator.alloc(u8, 1_000_000);
}
pub fn zigler_free() void {
beam.raw_allocator.free(global_zigler);
beam.allocator.free(global_zigler);
}
const c_stdlib = @cImport(@cInclude("stdlib.h"));
Expand Down Expand Up @@ -152,65 +152,64 @@ test "leak checks with general purpose allocator" do
end
```

## beam.allocator
## Custom allocators

Zigler provides a `threadlocal` variable: `beam.allocator`. This is set on entry
into the nif and defaults to `beam.raw_allocator`
Because zigler's allocators conform to zig's allocator interface, you can use
any composed allocator in the standard library or any composable allocator
from an imported zig package.

```elixir
~Z"""
pub fn basic(env: beam.env) !beam.term {
const slice = try beam.allocator.alloc(u16, 4);
defer beam.allocator.free(slice);
pub fn with_arena() !beam.term {
const std = @import("std");
var arena = std.heap.ArenaAllocator.init(beam.allocator);
defer arena.deinit();
const allocator = arena.allocator();
const slice = try allocator.alloc(u16, 4);
defer allocator.free(slice);
for (slice, 0..) |*item, index| {
item.* = @intCast(index);
}
return beam.make(env, slice, .{});
return beam.make(slice, .{});
}
"""

test "leak checks with allocator" do
assert [0, 1, 2, 3] = basic()
test "arena allocator" do
assert [0, 1, 2, 3] == with_arena()
end
```

> ### Raw nifs {: .warning }
>
> For raw nifs, `beam.allocator` is not set, and may retain a value from an
> arbitrary previous nif invocation. Consider usage of `beam.allocator` in a
> raw nif to be undefined unless it is set in the raw nif.
## Custom allocators
### Custom allocators in `beam.get`

Because zigler's allocators conform to zig's allocator interface, you can use
any composed allocator in the standard library or any composable allocator
from an imported zig package.
If you choose to use a custom allocator, you may use it in the `beam.get`
functions to instantiate data where it's the allocator's responsibility to
free it at the end.

```elixir
~Z"""
const std = @import("std");
pub fn arena_sum(array: beam.term) !u64 {
const std = @import("std");
pub fn with_arena(env: beam.env) !beam.term {
var arena = std.heap.ArenaAllocator.init(beam.allocator);
defer arena.deinit();
const arena_allocator = arena.allocator();
const allocator = arena.allocator();
const slice = try beam.get([]u64, array, .{.allocator = arena_allocator});
const slice = try allocator.alloc(u16, 4);
defer allocator.free(slice);
var total: u64 = 0;
for (slice, 0..) |*item, index| {
item.* = @intCast(index);
}
for (slice) |item| { total += item; }
return beam.make(env, slice, .{});
return total;
}
"""

test "arena allocator" do
assert [0, 1, 2, 3] == with_arena()
test "arena sum" do
assert 6 == arena_sum([1, 2, 3])
end
```

```
9 changes: 4 additions & 5 deletions guides/5-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,13 @@ The following functions are supported in the Callbacks, and are all optional.
pub const PointerResource = beam.Resource(*MyStruct, root, .{.Callbacks = PointerResourceCallbacks});
pub const PointerResourceCallbacks = struct {
pub fn dtor(env: beam.env, s: **MyStruct) void {
_ = env;
beam.raw_allocator.destroy(s.*);
pub fn dtor(s: **MyStruct) void {
beam.allocator.destroy(s.*);
}
};
pub fn create_pointer_resource(number: u64) !PointerResource {
const new_struct = try beam.raw_allocator.create(MyStruct);
const new_struct = try beam.allocator.create(MyStruct);
new_struct.payload = number;
return PointerResource.create(new_struct, .{});
}
Expand All @@ -200,7 +199,7 @@ end

> ### pointer allocation strategy {: .warning }
>
> It is strongly recommended to use [`beam.raw_allocator`](beam.html#raw_allocator)
> It is strongly recommended to use [`beam.allocator`](beam.html#allocator)
> for your pointer payload allocators, as [`beam.allocator`](beam.html#allocator)
> is undefined in the callback context.
>
Expand Down
2 changes: 1 addition & 1 deletion lib/zig/nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ defmodule Zig.Nif do
def maybe_catch(%Error{}) do
"""
catch |err| {
return beam.raise_with_error_return(env, err, @errorReturnTrace()).v;
return beam.raise_with_error_return(err, @errorReturnTrace(), .{}).v;
}
"""
end
Expand Down
4 changes: 2 additions & 2 deletions lib/zig/resources.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ defmodule Zig.Resources do
end

def init_resource_type({:root, resource}, module) do
"typeFor#{resource} = #{resource}.init(\"#{module}\", env);"
"typeFor#{resource} = #{resource}.init(\"#{module}\", .{.env = env});"
end

def init_resource_type(resource, module) when is_atom(resource) do
"typeFor#{resource} = #{call_for(resource)}.init(\"#{module}\", env);"
"typeFor#{resource} = #{call_for(resource)}.init(\"#{module}\", .{.env = env});"
end

@builtins ~w(isize usize c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong c_longdouble f16 f32 f64 f80 f128 bool)
Expand Down
23 changes: 11 additions & 12 deletions lib/zig/templates/basic.zig.eex
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
<% needs_make? = Enum.any?(@type.params, &Type.needs_make?/1) %>

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

beam.context = .{
.env = env,
.mode = .<%= context(@concurrency) %>,
// TODO: make this be configurable
.allocator = beam.allocator
.allocator = allocator
};

<%= if needs_make? do %>
var error_info = beam.make_empty_list(env);
<% end %>
<%# TODO: set up a proper allocator variable. %>
<%= if @leak_check do %>
var gpa = beam.make_general_purpose_allocator_instance();
// set the threadlocal beam.allocator
beam.allocator = gpa.allocator();
<% else %>
beam.allocator = beam.raw_allocator;
var error_info = beam.make_empty_list(.{});
<% end %>

const outer_result = get_result: {
Expand All @@ -41,7 +40,7 @@ fn <%= @type.name %>(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm)
<%= if needs_make? do %>
return e.enif_raise_exception(env, beam.make(.{err, error_index, error_info}, .{}).v);
<% else %>
return e.enif_raise_exception(env, beam.make(.{err, error_index, beam.make_empty_list(env)}, .{}).v);
return e.enif_raise_exception(env, beam.make(.{err, error_index, beam.make_empty_list(.{})}, .{}).v);
<% end %>
};

Expand All @@ -61,7 +60,7 @@ fn <%= @type.name %>(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm)
.ok =>
return outer_result,
.leak =>
return beam.raise_elixir_exception("RuntimeError", .{.message = "memory leak detected in function `<%= @type.name %>/<%= @type.arity %>`"}).v
return beam.raise_elixir_exception("RuntimeError", .{.message = "memory leak detected in function `<%= @type.name %>/<%= @type.arity %>`"}, .{}).v,
}
<% else %>
return outer_result;
Expand Down
9 changes: 4 additions & 5 deletions lib/zig/templates/threaded.zig.eex
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ pub const ThreadResource_<%= @type.name %> = beam.Resource(*Thread_<%= @type.nam
<% needs_make? = Enum.any?(@type.params, &Type.needs_make?/1) %>

fn @"<%= @type.name %>-launch"(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm) callconv(.C) e.ErlNifTerm {
// always use the raw_allocator, we can set it to managed allocation elsewhere.
beam.allocator = beam.raw_allocator;
// always use the allocator, we can set it to managed allocation elsewhere.

<%= for {param_type, index} <- Nif.indexed_parameters(@type.params), Type.missing_size?(param_type) do %>
var size<%= index %>:usize = undefined;
<% end %>

<%= if needs_make? do %>
var error_info = beam.make_empty_list(env);
var error_info = beam.make_empty_list(.{});
<% end %>

const payload_opts = .{
Expand All @@ -36,8 +35,8 @@ fn @"<%= @type.name %>-launch"(env: beam.env, argc: c_int, args: [*c] const e.Er

fn @"<%= @type.name %>-join"(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm) callconv(.C) e.ErlNifTerm {
_ = argc;
// set beam.allocator due to reentry
beam.allocator = beam.raw_allocator;
// set context due to reentry
@panic("not implemented yet");
const thread_rsrc = beam.get(ThreadResource_<%= @type.name %>, env, .{.v = args[0]}, .{}) catch {
@panic("error getting thread resource");
};
Expand Down
2 changes: 1 addition & 1 deletion priv/beam/allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const Allocator = std.mem.Allocator;

pub const MAX_ALIGN = 8;

pub const raw_allocator = Allocator{
pub const allocator = Allocator{
.ptr = undefined,
.vtable = &raw_beam_allocator_vtable,
};
Expand Down
24 changes: 3 additions & 21 deletions priv/beam/beam.zig
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ const stacktrace = @import("stacktrace.zig");
/// <!-- ignore -->
pub const payload = @import("payload.zig");


/// <!-- topic: Term Management; args: dest_type, _, source_term, options -->
/// converts BEAM [`term`](#term) dynamic types into static zig types
///
Expand Down Expand Up @@ -381,7 +380,7 @@ pub const payload = @import("payload.zig");
/// information that gets propagated on failure to convert. If omitted, the code
/// to produce these errors will get optimized out.
/// - `keep` (`bool`, only applies to references):
/// if `true` (default) the refcount will be increased on the term as a result of
/// if `true` (default) the refcount will be increased on the term as a result of
/// performing the `get` operation.
/// - `size` (`* usize`, only applies to manypointer or `[*c]`):
/// optional in-out parameter to retrieve the size of the slice
Expand Down Expand Up @@ -969,24 +968,7 @@ pub const general_purpose_allocator = allocator_.general_purpose_allocator;
/// wraps [`e.enif_alloc`](https://www.erlang.org/doc/man/erl_nif.html#enif_alloc)
/// and [`e.enif_free`](https://www.erlang.org/doc/man/erl_nif.html#enif_free)
/// into the zig standard library allocator interface.
pub const raw_allocator = allocator_.raw_allocator;

/// stores the allocator strategy for the currently running nif.
///
/// this variable is threadlocal, so that each called NIF can set it as a
/// global variable and not pass it around.
///
/// > #### allocator starts undefined {: .warning }
/// >
/// > This threadlocal is set to `undefined` because of architectural
/// > differences: we cannot trust loaded dynamic libraries to properly set
/// > this on thread creation. Each function is responsible for setting
/// > allocator correctly whenever execution control is returned to it.
/// >
/// > `raw` function calls do *not* set the allocator and
/// > must either set it themselves or always use a specific allocator
/// > strategy in its function calls.
pub threadlocal var allocator: std.mem.Allocator = undefined;
pub const allocator = allocator_.allocator;

///////////////////////////////////////////////////////////////////////////////
// resources
Expand Down Expand Up @@ -1197,7 +1179,7 @@ pub fn raise_elixir_exception(comptime module: []const u8, data: anytype, opts:
break :name "Elixir." ++ module;
};
var exception: e.ErlNifTerm = undefined;
const initial = make(env_, data, .{});
const initial = make(data, .{});

_ = e.enif_make_map_put(env_, initial.v, make_into_atom("__struct__", opts).v, make_into_atom(name, opts).v, &exception);
_ = e.enif_make_map_put(env_, exception, make_into_atom("__exception__", opts).v, make(true, opts).v, &exception);
Expand Down
Loading

0 comments on commit 2348751

Please sign in to comment.