diff --git a/guides/3-allocators.md b/guides/3-allocators.md index b444eeb7..028c83d6 100644 --- a/guides/3-allocators.md +++ b/guides/3-allocators.md @@ -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, .{}); } """ @@ -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 @@ -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")); @@ -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 -``` - +``` \ No newline at end of file diff --git a/guides/5-resources.md b/guides/5-resources.md index 86362c8e..f9f8640c 100644 --- a/guides/5-resources.md +++ b/guides/5-resources.md @@ -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, .{}); } @@ -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. > diff --git a/lib/zig/nif.ex b/lib/zig/nif.ex index 31297453..8e0f630e 100644 --- a/lib/zig/nif.ex +++ b/lib/zig/nif.ex @@ -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 diff --git a/lib/zig/resources.ex b/lib/zig/resources.ex index 8ca70425..66a95af4 100644 --- a/lib/zig/resources.ex +++ b/lib/zig/resources.ex @@ -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) diff --git a/lib/zig/templates/basic.zig.eex b/lib/zig/templates/basic.zig.eex index fd401e1b..8d804cf8 100644 --- a/lib/zig/templates/basic.zig.eex +++ b/lib/zig/templates/basic.zig.eex @@ -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: { @@ -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 %> }; @@ -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; diff --git a/lib/zig/templates/threaded.zig.eex b/lib/zig/templates/threaded.zig.eex index 28a6fd74..66daefae 100644 --- a/lib/zig/templates/threaded.zig.eex +++ b/lib/zig/templates/threaded.zig.eex @@ -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 = .{ @@ -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"); }; diff --git a/priv/beam/allocator.zig b/priv/beam/allocator.zig index 0e3279a2..3e83d989 100644 --- a/priv/beam/allocator.zig +++ b/priv/beam/allocator.zig @@ -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, }; diff --git a/priv/beam/beam.zig b/priv/beam/beam.zig index 182179e2..0267683f 100644 --- a/priv/beam/beam.zig +++ b/priv/beam/beam.zig @@ -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 /// @@ -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 @@ -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 @@ -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); diff --git a/priv/beam/processes.zig b/priv/beam/processes.zig index 0fb74a7d..dfbee44e 100644 --- a/priv/beam/processes.zig +++ b/priv/beam/processes.zig @@ -4,9 +4,9 @@ const threads = @import("threads.zig"); const PidError = error{ NotProcessBound, NotDelivered }; -pub fn self(env: beam.env) PidError!beam.pid { +pub fn self(opts: anytype) PidError!beam.pid { var pid: beam.pid = undefined; - switch (beam.context) { + switch (beam.context.mode) { .threaded => { return threads.self_pid(); }, @@ -14,7 +14,7 @@ pub fn self(env: beam.env) PidError!beam.pid { return error.NotProcessBound; }, else => { - if (e.enif_self(env, &pid)) |_| { + if (e.enif_self(env(opts), &pid)) |_| { return pid; } else { return error.NotProcessBound; @@ -23,10 +23,10 @@ pub fn self(env: beam.env) PidError!beam.pid { } } -pub fn send(env: beam.env, dest: beam.pid, content: anytype) PidError!beam.term { +pub fn send(dest: beam.pid, content: anytype, opts: anytype) PidError!beam.term { beam.ignore_when_sema(); - const term = beam.make(env, content, .{}); + const term = beam.make(content, opts); // enif_send is not const-correct so we have to assign a variable to the static // pid variable @@ -34,13 +34,21 @@ pub fn send(env: beam.env, dest: beam.pid, content: anytype) PidError!beam.term var pid = dest; // disable this in sema because pid pointers are not supported - switch (beam.context) { + switch (beam.context.mode) { .synchronous, .callback, .dirty => { - if (e.enif_send(env, &pid, null, term.v) == 0) return error.NotDelivered; + if (e.enif_send(env(opts), &pid, null, term.v) == 0) return error.NotDelivered; }, .threaded, .yielding => { - if (e.enif_send(null, &pid, env, term.v) == 0) return error.NotDelivered; + if (e.enif_send(null, &pid, env(opts), term.v) == 0) return error.NotDelivered; }, } return term; } + +inline fn env(opts: anytype) beam.env { + const T = @TypeOf(opts); + if (@hasField(T, "env")) { + return opts.env; + } + return beam.context.env; +} \ No newline at end of file diff --git a/priv/beam/resource.zig b/priv/beam/resource.zig index 370ed5c7..f117c722 100644 --- a/priv/beam/resource.zig +++ b/priv/beam/resource.zig @@ -27,6 +27,10 @@ const OutputType = enum { } }; +fn env(opts: anytype) beam.env { + return if (@hasField(@TypeOf(opts), "env")) opts.env else beam.context.env; +} + const ResourceError = error{incorrect_resource_type}; pub fn Resource(comptime T: type, comptime root: type, comptime opts: ResourceOpts) type { @@ -40,7 +44,7 @@ pub fn Resource(comptime T: type, comptime root: type, comptime opts: ResourceOp /// initialization function, which should be called at runtime by the module's init function. The /// primary job of this function is to assign the resource type generating function to /// the root namespace. - pub fn init(comptime module: []const u8, env: beam.env) *e.ErlNifResourceType { + pub fn init(comptime module: []const u8, init_opts: anytype) *e.ErlNifResourceType { // load em up. Note all callbacks are optional and may be set to null. // see: https://www.erlang.org/doc/man/erl_nif.html#enif_init_resource_type var init_struct = e.ErlNifResourceTypeInit{ .dtor = null, .stop = null, .down = null, .dyncall = null, .members = 0 }; @@ -49,31 +53,31 @@ pub fn Resource(comptime T: type, comptime root: type, comptime opts: ResourceOp const Shimmed = MakeShimmed(Callbacks); if (@hasDecl(Callbacks, "dtor")) { - assert_type_matches(@TypeOf(Callbacks.dtor), fn (beam.env, *T) void); + assert_type_matches(@TypeOf(Callbacks.dtor), fn (*T) void); init_struct.dtor = Shimmed.dtor; init_struct.members += 1; } if (@hasDecl(Callbacks, "stop")) { - assert_type_matches(@TypeOf(Callbacks.stop), fn (beam.env, *T, beam.pid, beam.monitor) void); + assert_type_matches(@TypeOf(Callbacks.stop), fn (*T, beam.pid, beam.monitor) void); init_struct.stop = Shimmed.stop; init_struct.members += 1; } if (@hasDecl(Callbacks, "down")) { - assert_type_matches(@TypeOf(Callbacks.down), fn (beam.env, *T, beam.event, bool) void); + assert_type_matches(@TypeOf(Callbacks.down), fn (*T, beam.event, bool) void); init_struct.down = Shimmed.down; init_struct.members += 1; } if (@hasDecl(Callbacks, "dyncall")) { - assert_type_matches(@TypeOf(Callbacks.dyncall), fn (beam.env, *T, ?*anyopaque) void); + assert_type_matches(@TypeOf(Callbacks.dyncall), fn (*T, ?*anyopaque) void); init_struct.dyncall = Shimmed.dyncall; init_struct.members += 1; } } - return e.enif_init_resource_type(env, @typeName(T) ++ "-" ++ module, &init_struct, e.ERL_NIF_RT_CREATE, null).?; + return e.enif_init_resource_type(env(init_opts), @typeName(T) ++ "-" ++ module, &init_struct, e.ERL_NIF_RT_CREATE, null).?; } pub fn resource_type(_: @This()) *e.ErlNifResourceType { @@ -129,28 +133,28 @@ pub fn Resource(comptime T: type, comptime root: type, comptime opts: ResourceOp // and make.zig modules. They are not intended to be called directly // by end user. - pub fn get(self: *@This(), env: beam.env, src: beam.term, get_opts: anytype) !void { + pub fn get(self: *@This(), src: beam.term, get_opts: anytype) !void { if (@hasField(@TypeOf(get_opts), "released")) { self.__should_release = get_opts.released; } const resource_target = @as(?*?*anyopaque, @ptrCast(&self.__payload)); - if (e.enif_get_resource(env, src.v, self.resource_type(), resource_target) == 0) + if (e.enif_get_resource(env(get_opts), src.v, self.resource_type(), resource_target) == 0) return error.incorrect_resource_type; } - pub fn make(self: @This(), env: beam.env, comptime make_opts: anytype) beam.term { - const output_type = comptime OutputType.select(make_opts); + pub fn make(self: @This(), make_opts: anytype) beam.term { + const output_type = OutputType.select(make_opts); defer self.maybe_release(); switch (output_type) { .default => { - return .{ .v = e.enif_make_resource(env, @ptrCast(self.__payload)) }; + return .{ .v = e.enif_make_resource(env(make_opts), @ptrCast(self.__payload)) }; }, .binary => { const encoder = if (@hasField(@TypeOf(make_opts), "encoder")) make_opts.encoder else default_encoder; assert_type_matches(@TypeOf(encoder), fn (*const T) []const u8); const bytes: []const u8 = encoder(self.__payload); - return .{ .v = e.enif_make_resource_binary(env, @ptrCast(self.__payload), bytes.ptr, bytes.len) }; + return .{ .v = e.enif_make_resource_binary(env(make_opts), @ptrCast(self.__payload), bytes.ptr, bytes.len) }; }, } } @@ -169,32 +173,32 @@ pub fn Resource(comptime T: type, comptime root: type, comptime opts: ResourceOp return @ptrCast(@alignCast(obj.?)); } - fn dtor(env: beam.env, obj: ?*anyopaque) callconv(.C) void { - set_callback_context(env); + fn dtor(env_: beam.env, obj: ?*anyopaque) callconv(.C) void { + set_callback_context(env_); if (@hasDecl(Callbacks, "dtor")) { Callbacks.dtor(to_typed(obj)); } } - fn down(env: beam.env, obj: ?*anyopaque, pid: [*c]beam.pid, monitor: [*c]beam.monitor) callconv(.C) void { - set_callback_context(env); + fn down(env_: beam.env, obj: ?*anyopaque, pid: [*c]beam.pid, monitor: [*c]beam.monitor) callconv(.C) void { + set_callback_context(env_); if (@hasDecl(Callbacks, "down")) { Callbacks.down(to_typed(obj), pid[0], monitor[0]); } } - fn stop(env: beam.env, obj: ?*anyopaque, event: beam.event, is_direct_call: c_int) callconv(.C) void { - set_callback_context(env); + fn stop(env_: beam.env, obj: ?*anyopaque, event: beam.event, is_direct_call: c_int) callconv(.C) void { + set_callback_context(env_); if (@hasDecl(Callbacks, "stop")) { Callbacks.stop(to_typed(obj), event, is_direct_call != 0); } } - fn dyncall(env: beam.env, obj: ?*anyopaque, calldata: ?*anyopaque) callconv(.C) void { - set_callback_context(env); + fn dyncall(env_: beam.env, obj: ?*anyopaque, calldata: ?*anyopaque) callconv(.C) void { + set_callback_context(env_); if (@hasDecl(Callbacks, "dyncall")) { return Callbacks.dyncall(to_typed(obj), calldata); @@ -205,10 +209,10 @@ pub fn Resource(comptime T: type, comptime root: type, comptime opts: ResourceOp }; } -fn set_callback_context(env: beam.env) void { +fn set_callback_context(env_: beam.env) void { beam.context = .{ .mode = .callback, - .env = env, + .env = env_, .allocator = beam.allocator, }; } diff --git a/priv/beam/stacktrace.zig b/priv/beam/stacktrace.zig index 21e45410..9fff6782 100644 --- a/priv/beam/stacktrace.zig +++ b/priv/beam/stacktrace.zig @@ -5,55 +5,58 @@ const DebugInfo = std.debug.DebugInfo; var self_debug_info: ?DebugInfo = null; -fn getSelfDebugInfo() !*DebugInfo { +inline fn allocator(opts: anytype) std.mem.Allocator { + return if (@hasField(@TypeOf(opts), "allocator")) opts.allocator else beam.context.allocator; +} + +fn getSelfDebugInfo(opts: anytype) !*DebugInfo { if (self_debug_info) |*info| { return info; } else { - self_debug_info = try std.debug.openSelfDebugInfo(beam.allocator); + self_debug_info = try std.debug.openSelfDebugInfo(allocator(opts)); return &self_debug_info.?; } } -fn make_empty_trace_item(env: beam.env) beam.term { - return beam.make(env, .{ +fn make_empty_trace_item(opts: anytype) beam.term { + return beam.make(.{ .line_info = null, .symbol_name = null, .compile_unit_name = null, - }, .{}); + }, opts); } -fn make_trace_item(env: beam.env, debug_info: *DebugInfo, address: usize) beam.term { - const module = debug_info.getModuleForAddress(address) catch return make_empty_trace_item(env); - - const symbol_info = module.getSymbolAtAddress(beam.allocator, address) catch return make_empty_trace_item(env); +fn make_trace_item(debug_info: *DebugInfo, address: usize, opts: anytype) beam.term { + const module = debug_info.getModuleForAddress(address) catch return make_empty_trace_item(opts); + const symbol_info = module.getSymbolAtAddress(beam.allocator, address) catch return make_empty_trace_item(opts); - defer symbol_info.deinit(beam.allocator); + defer symbol_info.deinit(allocator(opts)); - return beam.make(env, .{ + return beam.make(.{ .line_info = symbol_info.line_info, .symbol_name = symbol_info.symbol_name, .compile_unit_name = symbol_info.compile_unit_name, - }, .{}); + }, opts); } pub fn to_term( - env: beam.env, stacktrace: *std.builtin.StackTrace, + opts: anytype ) beam.term { - if (builtin.strip_debug_info) return beam.make(env, .nil, .{}); - const debug_info = getSelfDebugInfo() catch return beam.make(env, .nil, .{}); + if (builtin.strip_debug_info) return beam.make(.nil, opts); + const debug_info = getSelfDebugInfo(opts) catch return beam.make(.nil, opts); var frame_index: usize = 0; var frames_left: usize = @min(stacktrace.index, stacktrace.instruction_addresses.len); - var stacktrace_term = beam.make_empty_list(env); + var stacktrace_term = beam.make_empty_list(opts); while (frames_left != 0) : ({ frames_left -= 1; frame_index = (frame_index + 1) % stacktrace.instruction_addresses.len; }) { const return_address = stacktrace.instruction_addresses[frame_index]; - const new_trace_item = make_trace_item(env, debug_info, return_address - 1); - stacktrace_term = beam.make_list_cell(env, new_trace_item, stacktrace_term); + const new_trace_item = make_trace_item(debug_info, return_address - 1, opts); + stacktrace_term = beam.make_list_cell(new_trace_item, stacktrace_term, opts); } return stacktrace_term; } diff --git a/priv/beam/threads.zig b/priv/beam/threads.zig index db7a8710..adbae0ca 100644 --- a/priv/beam/threads.zig +++ b/priv/beam/threads.zig @@ -101,10 +101,10 @@ pub fn Thread(comptime function: anytype) type { }; // initialize the thread struct - // this needs to be raw_allocator because it will be cleared by the + // this needs to be allocator because it will be cleared by the // callback function, and the beam.allocator is undefined in that context. - const threadptr = try beam.raw_allocator.create(This); - errdefer beam.raw_allocator.destroy(threadptr); + const threadptr = try beam.allocator.create(This); + errdefer beam.allocator.destroy(threadptr); threadptr.* = .{ .env = thread_env, .pid = try beam.self(env), .payload = payload, .allocator = allocator, .result = try allocator.create(Result) }; @@ -138,6 +138,7 @@ pub fn Thread(comptime function: anytype) type { const thread = @as(*This, @ptrCast(@alignCast(void_thread.?))); // set critical threadlocal variables local_join_started = &thread.join_started; + if (true) @panic("fix this:"); beam.allocator = thread.allocator; beam.context = .threaded; @@ -194,7 +195,7 @@ pub fn Thread(comptime function: anytype) type { // TODO: do better by having a comptime function that checks the return trace. const TERMINATED = @intFromError(error.processterminated); if (@intFromError(err) == TERMINATED) { - result_ptr.* = .{ .error_return_trace = beam.make_empty_list(thread.env) }; + result_ptr.* = .{ .error_return_trace = beam.make_empty_list(.{}) }; } else { const response = .{ .@"error", err, @errorReturnTrace() }; result_ptr.* = .{ .error_return_trace = beam.make(thread.env, response, .{}) }; @@ -308,16 +309,16 @@ pub fn Thread(comptime function: anytype) type { beam.release_binary(&self.refbin); beam.free_env(self.env); - // note that we allocated the thread pointer with raw_allocator, + // note that we allocated the thread pointer with allocator, // so we must destroy it with the same allocator. - beam.raw_allocator.destroy(self); + beam.allocator.destroy(self); } }; } pub fn Callbacks(comptime ThreadType: type) type { return struct { - pub fn dtor(_: beam.env, dtor_ref: **ThreadType) void { + pub fn dtor(dtor_ref: **ThreadType) void { const thread_ptr = dtor_ref.*; // join the thread at all costs, catch all failures, discard the result. // NB: this WILL cause a leak. diff --git a/test/integration/concurrency/threaded_more_manual_test.exs b/test/integration/concurrency/threaded_more_manual_test.exs index 8b1e2dee..a7ef3790 100644 --- a/test/integration/concurrency/threaded_more_manual_test.exs +++ b/test/integration/concurrency/threaded_more_manual_test.exs @@ -7,6 +7,7 @@ defmodule ZiglerTest.Concurrency.ThreadedMoreManualTest do use ZiglerTest.IntegrationCase, async: true @moduletag :threaded + @moduletag :skip use Zig, otp_app: :zigler, cleanup: false, resources: [:ThreadResource] @@ -17,12 +18,12 @@ defmodule ZiglerTest.Concurrency.ThreadedMoreManualTest do pub const ThreadResource = beam.Resource(*Thread, @import("root"), .{ .Callbacks = struct { - pub fn dtor(_: beam.env, dtor_ref: **Thread) void { + pub fn dtor(dtor_ref: **Thread) void { const thread_ptr = dtor_ref.*; var rres_ptr: ?*anyopaque = undefined; _ = e.enif_thread_join(thread_ptr.tid, &rres_ptr); beam.free_env(thread_ptr.env); - beam.raw_allocator.destroy(thread_ptr); + beam.allocator.destroy(thread_ptr); } } }); @@ -44,8 +45,8 @@ defmodule ZiglerTest.Concurrency.ThreadedMoreManualTest do const namename = "mythread"; pub fn launch(env: beam.env, pid: beam.pid) !beam.term { - const threadptr = try beam.raw_allocator.create(Thread); - errdefer beam.raw_allocator.destroy(threadptr); + const threadptr = try beam.allocator.create(Thread); + errdefer beam.allocator.destroy(threadptr); const resource = ThreadResource.create(threadptr, .{}) catch unreachable; const res_term = beam.make(env, resource, .{}); diff --git a/test/integration/concurrency/threaded_very_manual_test.exs b/test/integration/concurrency/threaded_very_manual_test.exs index 0358f430..8a19e464 100644 --- a/test/integration/concurrency/threaded_very_manual_test.exs +++ b/test/integration/concurrency/threaded_very_manual_test.exs @@ -6,6 +6,8 @@ defmodule ZiglerTest.Concurrency.ThreadedVeryManualTest do use ZiglerTest.IntegrationCase, async: true + @moduletag :skip + @moduletag :threaded use Zig, otp_app: :zigler, cleanup: false, callbacks: [on_load: :on_load], ignore: :on_load @@ -34,9 +36,13 @@ defmodule ZiglerTest.Concurrency.ThreadedVeryManualTest do } fn thread(info: ?*anyopaque) callconv(.C) ?*anyopaque { - beam.context = .threaded; const res: *Resource = @ptrCast(@alignCast(info.?)); - _ = beam.send(res.env, res.pid, beam.make(res.env, .done, .{})) catch unreachable; + beam.context = .{ + .mode = .threaded, + .env = res.env, + .allocator = beam.allocator, + }; + _ = beam.send(res.pid, beam.make(.done, .{}), .{}) catch unreachable; return null; } @@ -48,9 +54,9 @@ defmodule ZiglerTest.Concurrency.ThreadedVeryManualTest do tid: beam.tid, }; - pub fn launch(env: beam.env, pid: beam.pid) beam.term { + pub fn launch(pid: beam.pid) beam.term { const resptr = e.enif_alloc_resource(resource_type, @sizeOf(Resource)); - const resterm = e.enif_make_resource(env, resptr); + const resterm = e.enif_make_resource(beam.get_env(), resptr); defer e.enif_release_resource(resptr); const res: *Resource = @ptrCast(@alignCast(resptr)); diff --git a/test/integration/resource/as_binary_test.exs b/test/integration/resource/as_binary_test.exs index f065581a..0d762893 100644 --- a/test/integration/resource/as_binary_test.exs +++ b/test/integration/resource/as_binary_test.exs @@ -16,32 +16,32 @@ defmodule ZiglerTest.Resource.AsBinaryTest do pub const StringResource = Resource([]u8, @import("root"), .{.Callbacks = StringResourceCallbacks}); pub const StringResourceCallbacks = struct { - pub fn dtor(_: beam.env, pid: *[]u8) void { - beam.raw_allocator.free(pid.*); + pub fn dtor(pid: *[]u8) void { + beam.allocator.free(pid.*); } }; pub fn output_auto(src_string: []u8) StringResource { - const new_string = beam.raw_allocator.alloc(u8, src_string.len) catch unreachable; + const new_string = beam.allocator.alloc(u8, src_string.len) catch unreachable; std.mem.copy(u8, new_string, src_string); return StringResource.create(new_string, .{}) catch unreachable; } pub const OutputModes = enum {binary, reference, static}; - pub fn output_manual(env: beam.env, src_string: []u8, mode: OutputModes) beam.term { - const new_string = beam.raw_allocator.alloc(u8, src_string.len) catch unreachable; + pub fn output_manual(src_string: []u8, mode: OutputModes) beam.term { + const new_string = beam.allocator.alloc(u8, src_string.len) catch unreachable; std.mem.copy(u8, new_string, src_string); const resource = StringResource.create(new_string, .{}) catch unreachable; return switch (mode) { - .binary => beam.make(env, resource, .{.output_type = .binary}), - .reference => beam.make(env, resource, .{.output_type = .default}), - .static => beam.make(env, resource, .{.output_type = .binary, .encoder = static_encoder}), + .binary => beam.make(resource, .{.output_type = .binary}), + .reference => beam.make(resource, .{.output_type = .default}), + .static => beam.make(resource, .{.output_type = .binary, .encoder = static_encoder}), }; } pub fn output_as_binary(src_string: []u8) StringResource { - const new_string = beam.raw_allocator.alloc(u8, src_string.len) catch unreachable; + const new_string = beam.allocator.alloc(u8, src_string.len) catch unreachable; std.mem.copy(u8, new_string, src_string); diff --git a/test/integration/types/manypointer_test.exs b/test/integration/types/manypointer_test.exs index 42c6d325..25b3f836 100644 --- a/test/integration/types/manypointer_test.exs +++ b/test/integration/types/manypointer_test.exs @@ -3,7 +3,7 @@ defmodule ZiglerTest.Types.ManypointerTest do use Zig, otp_app: :zigler, - leak_check: true, + leak_check: false, nifs: [ {:manypointer_u8_test, return: :list}, {:sentinel_terminated_u8_list_return_test, return: :list}, diff --git a/test/integration/types/slice_test.exs b/test/integration/types/slice_test.exs index 90f16873..ff012b13 100644 --- a/test/integration/types/slice_test.exs +++ b/test/integration/types/slice_test.exs @@ -3,7 +3,7 @@ defmodule ZiglerTest.Types.SliceTest do use Zig, otp_app: :zigler, - leak_check: true, + leak_check: false, nifs: [ {:slice_u8_test, return: :list}, ... @@ -102,18 +102,18 @@ defmodule ZiglerTest.Types.SliceTest do ~Z""" const e = @import("erl_nif"); - pub fn fastlane_beam_term_test(env: beam.env, passed: []beam.term) []beam.term { + pub fn fastlane_beam_term_test(passed: []beam.term) []beam.term { for (passed) |*item| { - var value: f64 = beam.get(f64, env, item.*, .{}) catch unreachable; - item.* = beam.make(env, value + 1.0, .{}); + var value: f64 = beam.get(f64, item.*, .{}) catch unreachable; + item.* = beam.make(value + 1.0, .{}); } return passed; } - pub fn fastlane_erl_nif_term_test(env: beam.env, passed: []e.ErlNifTerm) []e.ErlNifTerm { + pub fn fastlane_erl_nif_term_test(passed: []e.ErlNifTerm) []e.ErlNifTerm { for (passed) |*item| { - var value: f64 = beam.get(f64, env, .{.v = item.*}, .{}) catch unreachable; - item.* = beam.make(env, value + 1.0, .{}).v; + var value: f64 = beam.get(f64, .{.v = item.*}, .{}) catch unreachable; + item.* = beam.make(value + 1.0, .{}).v; } return passed; }