Skip to content

Commit

Permalink
Wrap NIFs with result module (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
jackalcooper authored Apr 28, 2024
1 parent 8e79887 commit 3091f5c
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 59 deletions.
3 changes: 3 additions & 0 deletions kinda_example/lib/kinda_example_code_gen.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ defmodule KindaExample.CodeGen do
[
%KindDecl{
module_name: KindaExample.NIF.CInt
},
%KindDecl{
module_name: KindaExample.NIF.StrInt
}
]
end
Expand Down
19 changes: 19 additions & 0 deletions kinda_example/test/kinda_example_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,24 @@ defmodule KindaExampleTest do
assert 3 ==
KindaExample.NIF.kinda_example_add(1, 2)
|> KindaExample.Native.to_term()

assert catch_error(KindaExample.NIF.kinda_example_add(1, "2")) ==
:failToFetchArgumentResource
end

test "custom make" do
assert KindaExample.Native.forward(
KindaExample.NIF.CInt,
:make,
[100]
)
|> KindaExample.NIF."Elixir.KindaExample.NIF.CInt.primitive"() == 100

assert catch_error(KindaExample.NIF."Elixir.KindaExample.NIF.StrInt.make"(1)) ==
:FunctionClauseError

assert KindaExample.NIF."Elixir.KindaExample.NIF.StrInt.make"("1")
|> KindaExample.NIF."Elixir.KindaExample.NIF.CInt.primitive"() ==
1
end
end
2 changes: 1 addition & 1 deletion src/beam.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,7 @@ pub fn make_exception(env_: env, exception_module: []const u8, err: anyerror, er
const debug_info = std.debug.getSelfDebugInfo() catch return make_nil(env_);

var frame_index: usize = 0;
var frames_left: usize = std.math.min(trace.index, trace.instruction_addresses.len);
var frames_left: usize = @min(trace.index, trace.instruction_addresses.len);
var ert = e.enif_make_list(env_, 0);

// currently macos has a bug where the error return trace is sometimes bogus.
Expand Down
22 changes: 16 additions & 6 deletions src/example/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,27 @@ const capi = @import("prelude.zig");
const root_module = "Elixir.KindaExample.NIF";
const Kinds = struct {
const CInt = kinda.ResourceKind(c_int, root_module ++ ".CInt");
const All = .{CInt};
const StrInt = kinda.ResourceKind(extern struct {
i: c_int = 0,
const Error = error{failToMakeInt};
fn make(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
var s: beam.binary = try beam.get_binary(env, args[0]);
const integer = try std.fmt.parseInt(i32, s.data[0..s.size], 10);
return CInt.resource.make(env, integer) catch return Error.failToMakeInt;
}
pub const maker = .{ make, 1 };
}, root_module ++ ".StrInt");
const All = .{ CInt, StrInt };
fn open(env: beam.env) void {
inline for (All) |k| {
k.open_all(env);
}
inline for (All) |k| {
k.open_all(env);
}
}
};

const all_nifs = .{
kinda.NIFFunc(Kinds.All, capi, "kinda_example_add", .{ .nif_name = "Elixir.KindaExample.NIF.kinda_example_add"}),
} ++ Kinds.CInt.nifs;
kinda.NIFFunc(Kinds.All, capi, "kinda_example_add", .{ .nif_name = "Elixir.KindaExample.NIF.kinda_example_add" }),
} ++ Kinds.CInt.nifs ++ Kinds.StrInt.nifs;
pub export var nifs: [all_nifs.len]e.ErlNifFunc = all_nifs;

const entry = e.ErlNifEntry{
Expand Down
107 changes: 55 additions & 52 deletions src/kinda.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const beam = @import("beam");
const e = @import("erl_nif");
const std = @import("std");
const print = @import("std").debug.print;
pub const result = @import("result.zig");

// a function to make a resource term from a u8 slice.
const OpaqueMaker: type = fn (beam.env, []u8) beam.term;
Expand All @@ -26,6 +27,7 @@ pub const Internal = struct {
pub const OpaqueStruct: type = ResourceKind(OpaqueStructType, "Kinda.Internal.OpaqueStruct");
};

pub const numOfNIFsPerKind = 10;
pub fn ResourceKind(comptime ElementType: type, comptime module_name_: anytype) type {
return struct {
pub const module_name = module_name_;
Expand Down Expand Up @@ -68,6 +70,7 @@ pub fn ResourceKind(comptime ElementType: type, comptime module_name_: anytype)
pub const Array = struct {
pub const module_name = module_name_ ++ ".Array";
pub const T = ArrayType;
const Error = error{ failToFetchResourceForArray, failToMakeResourceForOpaqueArray };
pub const resource = struct {
pub var t: beam.resource_type = undefined;
pub const name = @typeName(ArrayType);
Expand All @@ -79,61 +82,64 @@ pub fn ResourceKind(comptime ElementType: type, comptime module_name_: anytype)
}
};
// get the array adress as a opaque array
pub fn as_opaque(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
pub fn as_opaque(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
var array_ptr: ArrayType = @This().resource.fetch(env, args[0]) catch
return beam.make_error_binary(env, "fail to fetch resource for array, expected: " ++ @typeName(ArrayType));
return Error.failToFetchResourceForArray;
return Internal.OpaqueArray.resource.make(env, @ptrCast(array_ptr)) catch
return beam.make_error_binary(env, "fail to make resource for opaque arra");
return Error.failToMakeResourceForOpaqueArray;
}
};
fn ptr(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
return beam.get_resource_ptr_from_term(T, env, @This().resource.t, Ptr.resource.t, args[0]) catch return beam.make_error_binary(env, "fail to create ptr " ++ @typeName(T));
const PtrError = error{ failToMakePtrResource, failToFetchPtrResource, failToMakeResourceForOpaquePtr, failToMakeArrayResource, failToMakeMutableArrayResource };
fn ptr(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
return beam.get_resource_ptr_from_term(T, env, @This().resource.t, Ptr.resource.t, args[0]) catch return PtrError.failToMakePtrResource;
}
fn ptr_to_opaque(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
const typed_ptr: Ptr.T = Ptr.resource.fetch(env, args[0]) catch return beam.make_error_binary(env, "fail to fetch resource for ptr, expected: " ++ @typeName(PtrType));
return Internal.OpaquePtr.resource.make(env, @ptrCast(typed_ptr)) catch return beam.make_error_binary(env, "fail to make resource for: " ++ @typeName(Internal.OpaquePtr.T));
fn ptr_to_opaque(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
const typed_ptr: Ptr.T = Ptr.resource.fetch(env, args[0]) catch return PtrError.failToFetchPtrResource;
return Internal.OpaquePtr.resource.make(env, @ptrCast(typed_ptr)) catch return PtrError.failToMakeResourceForOpaquePtr;
}
pub fn opaque_ptr(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
const ptr_to_resource_memory: Ptr.T = beam.fetch_resource_ptr(T, env, @This().resource.t, args[0]) catch return beam.make_error_binary(env, "fail to create ptr " ++ @typeName(T));
return Internal.OpaquePtr.resource.make(env, @ptrCast(ptr_to_resource_memory)) catch return beam.make_error_binary(env, "fail to make resource for: " ++ @typeName(Internal.OpaquePtr.T));
pub fn opaque_ptr(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
const ptr_to_resource_memory: Ptr.T = beam.fetch_resource_ptr(T, env, @This().resource.t, args[0]) catch return PtrError.failToFetchPtrResource;
return Internal.OpaquePtr.resource.make(env, @ptrCast(ptr_to_resource_memory)) catch return PtrError.failToMakeResourceForOpaquePtr;
}
// the returned term owns the memory of the array.
fn array(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
return beam.get_resource_array(T, env, @This().resource.t, Array.resource.t, args[0]) catch return beam.make_error_binary(env, "fail to create array " ++ @typeName(T));
fn array(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
return beam.get_resource_array(T, env, @This().resource.t, Array.resource.t, args[0]) catch return PtrError.failToMakeArrayResource;
}
// the returned term owns the memory of the array.
// TODO: mut array should be a dedicated resource type without reusing Ptr.resource.t
fn mut_array(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
return beam.get_resource_array(T, env, @This().resource.t, Ptr.resource.t, args[0]) catch return beam.make_error_binary(env, "fail to create mut array " ++ @typeName(T));
fn mut_array(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
return beam.get_resource_array(T, env, @This().resource.t, Ptr.resource.t, args[0]) catch PtrError.failToMakeMutableArrayResource;
}
fn primitive(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
const v = resource.fetch(env, args[0]) catch return beam.make_error_binary(env, "fail to extract pritimive from " ++ @typeName(T));
return beam.make(T, env, v) catch return beam.make_error_binary(env, "fail to create primitive " ++ @typeName(T));
const PrimitiveError = error{ failToFetchPrimitive, failToCreatePrimitive };
fn primitive(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
const v = resource.fetch(env, args[0]) catch return PrimitiveError.failToFetchPrimitive;
return beam.make(T, env, v) catch return PrimitiveError.failToCreatePrimitive;
}
fn dump(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
const v: T = resource.fetch(env, args[0]) catch return beam.make_error_binary(env, "fail to fetch " ++ @typeName(T));
fn dump(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
const v: T = resource.fetch(env, args[0]) catch return PrimitiveError.failToFetchPrimitive;
print("{?}\n", .{v});
return beam.make_ok(env);
}
fn append_to_struct(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
const v = resource.fetch(env, args[0]) catch return beam.make_error_binary(env, "fail to extract pritimive from " ++ @typeName(T));
return beam.make(T, env, v) catch return beam.make_error_binary(env, "fail to create primitive " ++ @typeName(T));
fn append_to_struct(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
const v = resource.fetch(env, args[0]) catch return PrimitiveError.failToFetchPrimitive;
return beam.make(T, env, v) catch return PrimitiveError.failToCreatePrimitive;
}
fn make(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
const v = beam.get(T, env, args[0]) catch return beam.make_error_binary(env, "Fail to fetch primitive to make a resource. Expected type: " ++ @typeName(T));
return resource.make(env, v) catch return beam.make_error_binary(env, "fail to create " ++ @typeName(T));
fn make(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
const v = beam.get(T, env, args[0]) catch return PrimitiveError.failToFetchPrimitive;
return resource.make(env, v) catch return PrimitiveError.failToCreatePrimitive;
}
fn make_from_opaque_ptr(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
const OpaquePtrError = error{ failToFetchResourceOpaquePtr, failToFetchOffset, failToAllocateMemoryForTupleSlice, failToMakeResourceForExtractedObject, failToMakeObjectSize };
fn make_from_opaque_ptr(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
const ptr_to_read: Internal.OpaquePtr.T = Internal.OpaquePtr.resource.fetch(env, args[0]) catch
return beam.make_error_binary(env, "fail to fetch resource opaque ptr to read, expect" ++ @typeName(Internal.OpaquePtr.T));
return OpaquePtrError.failToFetchResourceOpaquePtr;
const offset: Internal.USize.T = Internal.USize.resource.fetch(env, args[1]) catch
return beam.make_error_binary(env, "fail to fetch resource for offset, expected: " ++ @typeName(Internal.USize.T));
return OpaquePtrError.failToFetchOffset;
const ptr_int = @intFromPtr(ptr_to_read) + offset;
const obj_ptr: *ElementType = @ptrFromInt(ptr_int);
var tuple_slice: []beam.term = beam.allocator.alloc(beam.term, 2) catch return beam.make_error_binary(env, "fail to allocate memory for tuple slice");
var tuple_slice: []beam.term = beam.allocator.alloc(beam.term, 2) catch return OpaquePtrError.failToAllocateMemoryForTupleSlice;
defer beam.allocator.free(tuple_slice);
tuple_slice[0] = resource.make(env, obj_ptr.*) catch return beam.make_error_binary(env, "fail to create resource for extract object");
tuple_slice[1] = beam.make(Internal.USize.T, env, @sizeOf(ElementType)) catch return beam.make_error_binary(env, "fail to create resource for size of object");
tuple_slice[0] = resource.make(env, obj_ptr.*) catch return OpaquePtrError.failToMakeResourceForExtractedObject;
tuple_slice[1] = beam.make(Internal.USize.T, env, @sizeOf(ElementType)) catch return OpaquePtrError.failToMakeObjectSize;
return beam.make_tuple(env, tuple_slice);
}
const maker = if (@typeInfo(ElementType) == .Struct and @hasDecl(ElementType, "maker"))
Expand All @@ -148,17 +154,17 @@ pub fn ResourceKind(comptime ElementType: type, comptime module_name_: anytype)
ElementType.nifs
else
.{};
pub const nifs = .{
e.ErlNifFunc{ .name = module_name ++ ".ptr", .arity = 1, .fptr = ptr_maker, .flags = 0 },
e.ErlNifFunc{ .name = module_name ++ ".ptr_to_opaque", .arity = 1, .fptr = ptr_to_opaque, .flags = 0 },
e.ErlNifFunc{ .name = module_name ++ ".opaque_ptr", .arity = 1, .fptr = opaque_ptr, .flags = 0 },
e.ErlNifFunc{ .name = module_name ++ ".array", .arity = 1, .fptr = array, .flags = 0 },
e.ErlNifFunc{ .name = module_name ++ ".mut_array", .arity = 1, .fptr = mut_array, .flags = 0 },
e.ErlNifFunc{ .name = module_name ++ ".primitive", .arity = 1, .fptr = primitive, .flags = 0 },
e.ErlNifFunc{ .name = module_name ++ ".make", .arity = maker[1], .fptr = maker[0], .flags = 0 },
e.ErlNifFunc{ .name = module_name ++ ".dump", .arity = 1, .fptr = dump, .flags = 0 },
e.ErlNifFunc{ .name = module_name ++ ".make_from_opaque_ptr", .arity = 2, .fptr = make_from_opaque_ptr, .flags = 0 },
e.ErlNifFunc{ .name = module_name ++ ".array_as_opaque", .arity = 1, .fptr = @This().Array.as_opaque, .flags = 0 },
pub const nifs: [numOfNIFsPerKind + @typeInfo(@TypeOf(extra_nifs)).Struct.fields.len]e.ErlNifFunc = .{
result.nif(module_name ++ ".ptr", 1, ptr_maker).entry,
result.nif(module_name ++ ".ptr_to_opaque", 1, ptr_to_opaque).entry,
result.nif(module_name ++ ".opaque_ptr", 1, opaque_ptr).entry,
result.nif(module_name ++ ".array", 1, array).entry,
result.nif(module_name ++ ".mut_array", 1, mut_array).entry,
result.nif(module_name ++ ".primitive", 1, primitive).entry,
result.nif(module_name ++ ".make", maker[1], maker[0]).entry,
result.nif(module_name ++ ".dump", 1, dump).entry,
result.nif(module_name ++ ".make_from_opaque_ptr", 2, make_from_opaque_ptr).entry,
result.nif(module_name ++ ".array_as_opaque", 1, @This().Array.as_opaque).entry,
} ++ extra_nifs;
pub fn open(env: beam.env) void {
const dtor = if (@typeInfo(ElementType) == .Struct and @hasDecl(ElementType, "destroy"))
Expand Down Expand Up @@ -270,31 +276,28 @@ pub fn NIFFunc(comptime Kinds: anytype, c: anytype, comptime name: anytype, attr
else => @compileError("too many args"),
};
}
fn ok_nif(env: beam.env, _: c_int, _: [*c]const beam.term) callconv(.C) beam.term {
return beam.make_ok(env);
}
const numOfNIFsPerKind = 10;
fn nif(env: beam.env, _: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
fn nif(env: beam.env, _: c_int, args: [*c]const beam.term) !beam.term {
const Error = error{ failToMakeResourceForReturnType, failToAllocateMemoryForTupleSlice, failToFetchArgumentResource };
var c_args: VariadicArgs() = undefined;
inline for (FTI.params, args, 0..) |p, arg, i| {
const ArgKind = getKind(p.type.?);
c_args[i] = ArgKind.resource.fetch(env, arg) catch return beam.make_error_binary(env, "when calling function: " ++ name ++ ", fail to fetch arg resource, expect: " ++ @typeName(ArgKind));
c_args[i] = ArgKind.resource.fetch(env, arg) catch return Error.failToFetchArgumentResource;
}
const rt = FTI.return_type.?;
if (rt == void) {
variadic_call(c_args);
return beam.make_ok(env);
} else {
const RetKind = getKind(rt);
var tuple_slice: []beam.term = beam.allocator.alloc(beam.term, 3) catch return beam.make_error_binary(env, "fail to allocate memory for tuple slice");
var tuple_slice: []beam.term = beam.allocator.alloc(beam.term, 3) catch return Error.failToAllocateMemoryForTupleSlice;
defer beam.allocator.free(tuple_slice);
tuple_slice[0] = beam.make_atom(env, "kind");
tuple_slice[1] = beam.make_atom(env, RetKind.module_name);
const ret = RetKind.resource.make(env, variadic_call(c_args)) catch return beam.make_error_binary(env, "fail to make resource for return type: " ++ @typeName(RetKind.T));
const ret = RetKind.resource.make(env, variadic_call(c_args)) catch return Error.failToMakeResourceForReturnType;
tuple_slice[2] = ret;
return beam.make_tuple(env, tuple_slice);
}
}
const entry = e.ErlNifFunc{ .name = attrs.nif_name orelse name, .arity = FTI.params.len, .fptr = nif, .flags = flags };
const entry = result.nif_with_flags(attrs.nif_name orelse name, FTI.params.len, nif, flags).entry;
}).entry;
}
24 changes: 24 additions & 0 deletions src/result.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const beam = @import("beam");
const e = @import("erl_nif");
const std = @import("std");

pub fn nif_with_flags(comptime name: [*c]const u8, comptime arity: usize, comptime f: anytype, comptime flags: u32) type {
return struct {
fn exported(env: beam.env, n: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
if (f(env, n, args)) |r| {
return r;
} else |err| {
return e.enif_raise_exception(env, beam.make_atom(env, @errorName(err)));
}
}
pub const entry = e.ErlNifFunc{ .name = name, .arity = arity, .fptr = exported, .flags = flags };
};
}

pub fn nif(comptime name: [*c]const u8, comptime arity: usize, comptime f: anytype) type {
return nif_with_flags(name, arity, f, 0);
}

pub fn wrap(comptime f: anytype) fn (env: beam.env, n: c_int, args: [*c]const beam.term) callconv(.C) beam.term {
return nif("", 0, f).exported;
}

0 comments on commit 3091f5c

Please sign in to comment.