Current section
Files
Jump to
Current section
Files
priv/beam/make.zig
const beam = @import("beam.zig");
const e = @import("erl_nif");
const std = @import("std");
const resource = @import("resource.zig");
const options = @import("options.zig");
pub fn make(value: anytype, opts: anytype) beam.term {
const T = @TypeOf(value);
// passthrough on beam.term, no work needed.
if (T == beam.term) {
options.assert_default(T, opts);
return value;
}
if (T == beam.pid) {
options.assert_default(T, opts);
return make_pid(value, opts);
}
// special case on stacktrace
if (T == *std.builtin.StackTrace) {
options.assert_default(T, opts);
return beam.make_stacktrace(value, opts);
}
if (T == beam.port) @compileError("you cannot convert a port into a term");
switch (@typeInfo(T)) {
.Array => return make_array(value, opts),
.Pointer => return make_pointer(value, opts),
.Int => return make_int(value, opts),
.Struct => return make_struct(value, opts),
.EnumLiteral => return make_enum_literal(value, opts),
.Enum => return make_enum(value, opts),
.ErrorSet => return make_error(value, opts),
.Null => return make_null(opts),
.Float => return make_float(value, opts),
.Bool => return make_bool(value, opts),
.Optional => return make_optional(value, opts),
.ComptimeInt => return make_comptime_int(value, opts),
.ComptimeFloat => return make_comptime_float(value, opts),
.Void => return make_enum(.ok, opts),
else => {
@compileError("unusable type " ++ @typeName(T) ++ " encountered");
},
}
}
pub fn make_pid(pid: beam.pid, opts: anytype) beam.term {
options.assert_default(beam.pid, opts);
// for some reason, the macro for this is super shitty. See:
// https://github.com/erlang/otp/blob/a78b8e0769bba69ba1245cce850b226bdf6940fa/erts/emulator/beam/erl_nif_api_funcs.h#L649
// so we have to use secret hidden implementation, which is
// that the pid struct just wraps beam.term directly =(
// note that we also have just disable this in the sema step,
// because sema shims pid with a bogus extern struct, which
// can't have pid.
beam.ignore_when_sema();
// super bogus and super sketchy code:
// this code will fail to work if the OTP team ever changes
// their internal pid representation away from this!
return .{ .v = @bitCast(pid.pid) };
}
pub fn make_null(opts: anytype) beam.term {
return .{ .v = e.enif_make_atom(options.env(opts), "nil") };
}
fn make_array(value: anytype, opts: anytype) beam.term {
return make_array_from_pointer(@TypeOf(value), &value, opts);
}
fn make_pointer(value: anytype, opts: anytype) beam.term {
const pointer = @typeInfo(@TypeOf(value)).Pointer;
switch (pointer.size) {
.One => return make_mut(value, opts),
.Many => return make_manypointer(value, opts),
.Slice => return make_slice(value, opts),
.C => return make_cpointer(value, opts),
}
}
fn make_optional(value: anytype, opts: anytype) beam.term {
return if (value) |unwrapped| make(unwrapped, opts) else make_into_atom("nil", opts);
}
fn make_int(value: anytype, opts: anytype) beam.term {
options.assert_default(@TypeOf(value), opts);
const int = @typeInfo(@TypeOf(value)).Int;
switch (int.signedness) {
.signed => switch (int.bits) {
0 => return .{ .v = e.enif_make_int(options.env(opts), 0) },
1...32 => return .{ .v = e.enif_make_int(options.env(opts), @as(i32, @intCast(value))) },
33...64 => return .{ .v = e.enif_make_int64(options.env(opts), @as(i64, @intCast(value))) },
else => {},
},
.unsigned => switch (int.bits) {
0 => return .{ .v = e.enif_make_int(options.env(opts), 0) },
1...32 => return .{ .v = e.enif_make_uint(options.env(opts), @as(u32, @intCast(value))) },
33...64 => return .{ .v = e.enif_make_uint64(options.env(opts), @as(u64, @intCast(value))) },
else => {
const Bigger = std.meta.Int(.unsigned, comptime try std.math.ceilPowerOfTwo(u16, int.bits));
const buf_size = @sizeOf(Bigger);
var result: e.ErlNifTerm = undefined;
var intermediate = @as(Bigger, @intCast(value));
var buf = e.enif_make_new_binary(options.env(opts), buf_size, &result);
// transfer content.
@memcpy(buf[0..buf_size], @as([*]u8, @ptrCast(&intermediate))[0..buf_size]);
return .{ .v = result };
},
},
}
unreachable;
}
fn make_comptime_int(value: anytype, opts: anytype) beam.term {
options.assert_default(@TypeOf(value), opts);
if (value < std.math.minInt(i64)) {
@compileError("directly making a value less than min(i64) is not supported");
}
if (value < std.math.minInt(i32)) {
return make_int(@as(i64, value), opts);
}
if (value < 0) {
return make_int(@as(i32, value), opts);
}
if (value <= std.math.maxInt(u32)) {
return make_int(@as(u32, value), opts);
}
if (value <= std.math.maxInt(u64)) {
return make_int(@as(u64, value), opts);
}
@compileError("directly making a value greater than max(u64) is not supported");
}
pub fn make_comptime_float(comptime float: comptime_float, opts: anytype) beam.term {
options.assert_default(comptime_float, opts);
return beam.make(@as(f64, @floatCast(float)), opts);
}
const EMPTY_TUPLE_LIST = [_]beam.term{};
fn make_struct(value: anytype, opts: anytype) beam.term {
const struct_info = @typeInfo(@TypeOf(value)).Struct;
const env = options.env(opts);
if (struct_info.is_tuple) {
if (value.len > 16_777_215) {
@compileError("The tuple size is too large for the erlang virtual machine");
}
var tuple_list: [value.len]e.ErlNifTerm = undefined;
inline for (&tuple_list, 0..) |*tuple_item, index| {
const tuple_term = value[index];
if (@TypeOf(tuple_term) == beam.term) {
tuple_item.* = tuple_term.v;
} else {
tuple_item.* = make(tuple_term, opts).v;
}
}
return .{ .v = e.enif_make_tuple_from_array(env, &tuple_list, value.len) };
} else if (resource.MaybeUnwrap(struct_info)) |_| {
return value.make(opts);
} else {
return switch (options.output(opts)) {
.binary => make_struct_binary(value, opts),
.default => if (struct_info.layout == .@"packed") make_struct_binary(value, opts) else make_struct_map(value, opts),
else => make_struct_map(value, opts),
};
}
}
fn make_struct_map(value: anytype, opts: anytype) beam.term {
const struct_info = @typeInfo(@TypeOf(value)).Struct;
const env = options.env(opts);
const fields = struct_info.fields;
var result: e.ErlNifTerm = undefined;
var keys: [fields.len]e.ErlNifTerm = undefined;
var vals: [fields.len]e.ErlNifTerm = undefined;
inline for (fields, 0..) |field, index| {
// this needs to be implemented.
const map_child_as = if (@hasField(@TypeOf(opts), "as")) options.map_child(opts.as, field.name) else .default;
const child_opts = .{ .env = env, .as = map_child_as };
if (field.name.len > 255) {
@compileError("the length of the struct field name is too large for the erlang virtual machine");
}
keys[index] = e.enif_make_atom_len(env, field.name.ptr, field.name.len);
vals[index] = make(@field(value, field.name), child_opts).v;
}
_ = e.enif_make_map_from_arrays(env, &keys, &vals, fields.len, &result);
if (options.output(opts) == .elixir_struct) {
// TODO: disambiguate between elixir module names and erlang module names.
const modulename = std.fmt.comptimePrint("Elixir{}", .{opts.as});
const dunder_struct = make_into_atom("__struct__", opts);
const module_atom = make_into_atom(modulename, opts);
_ = e.enif_make_map_put(env, result, dunder_struct.v, module_atom.v, &result);
}
return .{ .v = result };
}
fn make_struct_binary(value: anytype, opts: anytype) beam.term {
const T = @TypeOf(value);
const struct_info = @typeInfo(T).Struct;
switch (struct_info.layout) {
.@"packed", .@"extern" => {},
else => @compileError("only packed and extern structs can be output as binary"),
}
const binary_size = @sizeOf(T);
var result: beam.term = undefined;
const buf: *T = @ptrCast(@alignCast(e.enif_make_new_binary(options.env(opts), binary_size, &result.v)));
buf.* = value;
return result;
}
fn make_enum_literal(value: anytype, opts: anytype) beam.term {
options.assert_default(@TypeOf(value), opts);
const tag_name = @tagName(value);
if (tag_name.len > 255) {
@compileError("the name of this enum literal is too large for the erlang virtual machine");
}
return make_into_atom(tag_name, opts);
}
fn make_enum(value: anytype, opts: anytype) beam.term {
const new_opts = .{ .env = options.env(opts), .as = .default };
switch (options.output(opts)) {
.default => return make_into_atom(@tagName(value), new_opts),
.integer => return make_int(@intFromEnum(value), new_opts),
else => |output| {
const msg = std.fmt.comptimePrint("the 'as' field for an enum must be `.default` or `.integer`, got {}.", .{output});
@compileError(msg);
},
}
}
fn make_error(value: anytype, opts: anytype) beam.term {
options.assert_default(@TypeOf(value), opts);
const tag_name = @errorName(value);
return make_into_atom(tag_name, opts);
}
fn make_float(value: anytype, opts: anytype) beam.term {
options.assert_default(@TypeOf(value), opts);
const floatval = @as(f64, @floatCast(value));
if (std.math.isNan(value)) return make_enum(.NaN, opts);
if (std.math.isPositiveInf(value)) return make_enum(.infinity, opts);
if (std.math.isNegativeInf(value)) return make_enum(.neg_infinity, opts);
return .{ .v = e.enif_make_double(options.env(opts), floatval) };
}
fn make_bool(value: bool, opts: anytype) beam.term {
options.assert_default(bool, opts);
return make_into_atom(if (value) "true" else "false", opts);
}
fn make_mut(value_ptr: anytype, opts: anytype) beam.term {
const child = @TypeOf(value_ptr.*);
switch (@typeInfo(child)) {
.Array => return make_array_from_pointer(child, value_ptr, opts),
.Struct => return make_struct(value_ptr.*, opts),
else => @compileError("this type is unsupported"),
}
}
fn make_array_from_pointer(comptime T: type, array_ptr: anytype, opts: anytype) beam.term {
const array_info = @typeInfo(T).Array;
const Child = array_info.child;
// u8 arrays (sentinel terminated or otherwise) are treated as
// strings.
if (Child == u8 and options.output(opts) != .list) {
return make_binary(array_ptr[0..], opts);
}
if (options.output(opts) == .binary) {
// u8 arrays are by default marshalled into binaries.
return make_binary(array_ptr[0..], opts);
}
const env = options.env(opts);
switch (array_info.child) {
beam.term => {
// since beam.term is guaranteed to be a packed struct of only
// e.ErlNifTerm, this is always guaranteed to work.
const ptr = @as([*]const e.ErlNifTerm, @ptrCast(array_ptr));
return .{ .v = e.enif_make_list_from_array(env, ptr, array_info.len) };
},
e.ErlNifTerm => {
return .{ .v = e.enif_make_list_from_array(env, array_ptr, array_info.len) };
},
// the general case is build the list backwards.
else => {
const list_child_as = if (@hasField(@TypeOf(opts), "as")) options.list_child(opts.as) else .default;
const child_opts = .{ .env = env, .as = list_child_as };
var tail = e.enif_make_list_from_array(env, null, 0);
if (array_info.len != 0) {
var index: usize = array_info.len;
while (index > 0) : (index -= 1) {
tail = e.enif_make_list_cell(env, make(array_ptr[index - 1], child_opts).v, tail);
}
}
return .{ .v = tail };
},
}
}
pub fn make_manypointer(manypointer: anytype, opts: anytype) beam.term {
const pointer = @typeInfo(@TypeOf(manypointer)).Pointer;
if (pointer.sentinel) |_| {
const len = std.mem.len(manypointer);
return make_slice(manypointer[0..len], opts);
} else {
@compileError("it's not possible to create a manypointer without a sentinel");
}
}
fn make_cpointer(cpointer: anytype, opts: anytype) beam.term {
const pointer = @typeInfo(@TypeOf(cpointer)).Pointer;
const Child = pointer.child;
if (@hasField(@TypeOf(opts), "length")) {
return make(cpointer[0..options.length(opts).?], opts);
} else {
if (cpointer) |_| {
// the following two types have inferrable sentinels
if (Child == u8) {
return make(@as([*:0]u8, @ptrCast(cpointer)), opts);
}
if (@typeInfo(Child) == .Pointer) {
return make(@as([*:null]Child, @ptrCast(cpointer)), opts);
}
switch (@typeInfo(Child)) {
.Struct => return make(@as(*Child, @ptrCast(cpointer)), opts),
else => @compileError("this is not supported"),
}
} else {
return make(.nil, .{.env = options.env(opts)});
}
}
}
pub fn make_slice(slice: anytype, opts: anytype) beam.term {
const T = @TypeOf(slice);
const slice_info = @typeInfo(T).Pointer;
const Child = slice_info.child;
// u8 slices default to binary and must be opt-in to get charlists out.
if (Child == u8 and options.output(opts) != .list) {
return make_binary(slice, opts);
}
if (options.output(opts) == .binary) {
return make_binary(slice, opts);
}
const env = options.env(opts);
switch (T) {
[]beam.term => {
// since beam.term is guaranteed to be a packed struct of only
// e.ErlNifTerm, this is always guaranteed to work.
const ptr = @as([*]const e.ErlNifTerm, @ptrCast(slice.ptr));
return .{ .v = e.enif_make_list_from_array(env, ptr, @as(c_uint, @intCast(slice.len))) };
},
[]e.ErlNifTerm => {
return .{ .v = e.enif_make_list_from_array(env, slice.ptr, @as(c_uint, @intCast(slice.len))) };
},
else => {
const list_child_as = if (@hasField(@TypeOf(opts), "as")) options.list_child(opts.as) else .default;
const child_opts = .{ .env = env, .as = list_child_as };
var tail = e.enif_make_list_from_array(env, null, 0);
if (slice.len != 0) {
var index = slice.len;
while (index > 0) : (index -= 1) {
tail = e.enif_make_list_cell(env, make(slice[index - 1], child_opts).v, tail);
}
}
return .{ .v = tail };
},
}
}
pub fn make_into_atom(atom_string: []const u8, opts: anytype) beam.term {
return .{ .v = e.enif_make_atom_len(options.env(opts), atom_string.ptr, atom_string.len) };
}
fn make_binary(content: anytype, opts: anytype) beam.term {
const T = @TypeOf(content);
switch (@typeInfo(T)) {
.Pointer => |P| {
const Child = P.child;
switch (P.size) {
.Slice => {
const binary_size = @sizeOf(Child) * content.len;
const u8buf = @as([*]const u8, @ptrCast(content.ptr));
return make_binary_from_u8_slice(u8buf[0..binary_size], opts);
},
// it is possible that this is a const pointer to an array in memory.
.One => {
if (@typeInfo(Child) != .Array) {
@compileError("make_binary is only supported for array and slice pointers");
}
const binary_size = @sizeOf(@typeInfo(Child).Array.child) * content.len;
const u8buf = @as([*]const u8, @ptrCast(content));
return make_binary_from_u8_slice(u8buf[0..binary_size], opts);
},
else => @compileError("make_binary is only supported for array and slice pointers"),
}
},
.Array => |A| {
const binary_size = @sizeOf(A.child) * content.len;
const u8buf = @as([*]const u8, @ptrCast(&content));
return make_binary_from_u8_slice(u8buf[0..binary_size], opts);
},
else => @compileError("make_binary is only supported for slices and arrays"),
}
}
fn make_binary_from_u8_slice(slice: []const u8, opts: anytype) beam.term {
var result: beam.term = undefined;
var buf = e.enif_make_new_binary(options.env(opts), slice.len, &result.v);
@memcpy(buf[0..slice.len], slice);
return result;
}
pub fn make_empty_list(opts: anytype) beam.term {
return .{ .v = e.enif_make_list_from_array(options.env(opts), null, 0) };
}
pub fn make_list_cell(head: anytype, tail: beam.term, opts: anytype) beam.term {
return .{ .v = e.enif_make_list_cell(options.env(opts), make(head, opts).v, tail.v) };
}
pub fn make_error_pair(payload: anytype, opts: anytype) beam.term {
return make(.{ .@"error", make(payload, opts) }, opts);
}
pub fn make_error_atom(opts: anytype) beam.term {
return make(.@"error", opts);
}
pub fn make_ref(opts: anytype) beam.term {
return .{ .v = e.enif_make_ref(options.env(opts)) };
}