Current section
Files
Jump to
Current section
Files
lib/quickbeam/.Elixir.QuickBEAM.Native.zig
const types = @import("types.zig");
const worker = @import("worker.zig");
const std = types.std;
const beam = @import("beam");
const e = types.e;
const gpa = types.gpa;
const RuntimeData = types.RuntimeData;
const enqueue = types.enqueue;
// ──────────────────── Resource ────────────────────
pub const RuntimeResource = beam.Resource(*RuntimeData, @import("root"), .{
.Callbacks = struct {
pub fn dtor(ptr: **RuntimeData) void {
const data = ptr.*;
data.shutting_down.store(true, .release);
data.sync_slots_mutex.lock();
var it = data.sync_slots.valueIterator();
while (it.next()) |slot| {
slot.*.ok = false;
slot.*.result_json = "runtime shutting down";
slot.*.done.set();
}
data.sync_slots_mutex.unlock();
enqueue(data, .{ .stop = {} });
if (data.thread) |t_| t_.join();
gpa.destroy(data);
}
},
});
// ──────────────────── NIF entry points ────────────────────
fn get_map_uint(env: *e.ErlNifEnv, map: e.ErlNifTerm, key: [:0]const u8) ?usize {
var key_atom: e.ErlNifTerm = undefined;
if (e.enif_make_existing_atom_len(env, key.ptr, key.len, &key_atom, e.ERL_NIF_LATIN1) == 0) return null;
var val: e.ErlNifTerm = undefined;
if (e.enif_get_map_value(env, map, key_atom, &val) == 0) return null;
var result: u64 = undefined;
if (e.enif_get_uint64(env, val, &result) == 0) return null;
return @intCast(result);
}
pub fn start_runtime(owner_pid: beam.pid, opts: beam.term) !RuntimeResource {
const data = try gpa.create(RuntimeData);
data.* = .{
.mutex = .{},
.cond = .{},
.queue_head = null,
.queue_tail = null,
.stopped = false,
.thread = null,
};
const env = beam.context.env orelse return error.NoEnv;
if (get_map_uint(env, opts.v, "memory_limit")) |v| {
data.memory_limit = v;
}
if (get_map_uint(env, opts.v, "max_stack_size")) |v| {
data.max_stack_size = v;
}
const res = try RuntimeResource.create(data, .{});
const min_thread_stack = 2 * 1024 * 1024;
const thread_stack = @max(data.max_stack_size + min_thread_stack, min_thread_stack);
data.thread = std.Thread.spawn(.{ .stack_size = thread_stack }, worker.worker_main, .{ data, owner_pid }) catch {
gpa.destroy(data);
return error.ThreadSpawn;
};
return res;
}
pub fn eval(resource: RuntimeResource, code: []const u8, timeout_ms: u64) beam.term {
const data = resource.unpack();
const env = beam.context.env orelse return beam.make(.{ .@"error", "no env" }, .{});
var caller_pid: beam.pid = undefined;
_ = e.enif_self(env, &caller_pid);
const ref_env = beam.alloc_env();
const ref_term = e.enif_make_ref(ref_env);
const code_copy = gpa.dupe(u8, code) catch return beam.make(.{ .@"error", "OOM" }, .{});
enqueue(data, .{ .eval = .{
.code = code_copy,
.caller_pid = caller_pid,
.ref_env = ref_env,
.ref_term = ref_term,
.timeout_ns = if (timeout_ms > 0) timeout_ms * 1_000_000 else 0,
} });
return beam.term{ .v = e.enif_make_copy(env, ref_term) };
}
pub fn compile(resource: RuntimeResource, code: []const u8) beam.term {
const data = resource.unpack();
const env = beam.context.env orelse return beam.make(.{ .@"error", "no env" }, .{});
var caller_pid: beam.pid = undefined;
_ = e.enif_self(env, &caller_pid);
const ref_env = beam.alloc_env();
const ref_term = e.enif_make_ref(ref_env);
const code_copy = gpa.dupe(u8, code) catch return beam.make(.{ .@"error", "OOM" }, .{});
enqueue(data, .{ .compile = .{
.code = code_copy,
.caller_pid = caller_pid,
.ref_env = ref_env,
.ref_term = ref_term,
} });
return beam.term{ .v = e.enif_make_copy(env, ref_term) };
}
pub fn load_bytecode(resource: RuntimeResource, bytecode: []const u8) beam.term {
const data = resource.unpack();
const env = beam.context.env orelse return beam.make(.{ .@"error", "no env" }, .{});
var caller_pid: beam.pid = undefined;
_ = e.enif_self(env, &caller_pid);
const ref_env = beam.alloc_env();
const ref_term = e.enif_make_ref(ref_env);
const code_copy = gpa.dupe(u8, bytecode) catch return beam.make(.{ .@"error", "OOM" }, .{});
enqueue(data, .{ .load_bytecode = .{
.code = code_copy,
.caller_pid = caller_pid,
.ref_env = ref_env,
.ref_term = ref_term,
} });
return beam.term{ .v = e.enif_make_copy(env, ref_term) };
}
pub fn call_function(resource: RuntimeResource, name: []const u8, args: beam.term, timeout_ms: u64) beam.term {
const data = resource.unpack();
const env = beam.context.env orelse return beam.make(.{ .@"error", "no env" }, .{});
var caller_pid: beam.pid = undefined;
_ = e.enif_self(env, &caller_pid);
const ref_env = beam.alloc_env();
const ref_term = e.enif_make_ref(ref_env);
const args_env = beam.alloc_env();
const args_copy = e.enif_make_copy(args_env, args.v);
const name_copy = gpa.dupe(u8, name) catch return beam.make(.{ .@"error", "OOM" }, .{});
enqueue(data, .{ .call_fn = .{
.name = name_copy,
.args_env = args_env,
.args_term = args_copy,
.caller_pid = caller_pid,
.ref_env = ref_env,
.ref_term = ref_term,
.timeout_ns = if (timeout_ms > 0) timeout_ms * 1_000_000 else 0,
} });
return beam.term{ .v = e.enif_make_copy(env, ref_term) };
}
pub fn load_module(resource: RuntimeResource, name: []const u8, code: []const u8) beam.term {
const data = resource.unpack();
const env = beam.context.env orelse return beam.make(.{ .@"error", "no env" }, .{});
var caller_pid: beam.pid = undefined;
_ = e.enif_self(env, &caller_pid);
const ref_env = beam.alloc_env();
const ref_term = e.enif_make_ref(ref_env);
const name_copy = gpa.dupe(u8, name) catch return beam.make(.{ .@"error", "OOM" }, .{});
const code_copy = gpa.dupe(u8, code) catch { gpa.free(name_copy); return beam.make(.{ .@"error", "OOM" }, .{}); };
enqueue(data, .{ .load_module = .{
.name = name_copy,
.code = code_copy,
.caller_pid = caller_pid,
.ref_env = ref_env,
.ref_term = ref_term,
} });
return beam.term{ .v = e.enif_make_copy(env, ref_term) };
}
pub fn reset_runtime(resource: RuntimeResource) beam.term {
const data = resource.unpack();
const env = beam.context.env orelse return beam.make(.{ .@"error", "no env" }, .{});
var caller_pid: beam.pid = undefined;
_ = e.enif_self(env, &caller_pid);
const ref_env = beam.alloc_env();
const ref_term = e.enif_make_ref(ref_env);
enqueue(data, .{ .reset = .{
.code = "",
.caller_pid = caller_pid,
.ref_env = ref_env,
.ref_term = ref_term,
} });
return beam.term{ .v = e.enif_make_copy(env, ref_term) };
}
pub fn stop_runtime(resource: RuntimeResource) beam.term {
const data = resource.unpack();
data.shutting_down.store(true, .release);
data.sync_slots_mutex.lock();
var it = data.sync_slots.valueIterator();
while (it.next()) |slot| {
slot.*.ok = false;
slot.*.result_json = "runtime shutting down";
slot.*.done.set();
}
data.sync_slots_mutex.unlock();
enqueue(data, .{ .stop = {} });
if (data.thread) |th| {
th.join();
data.thread = null;
}
return beam.make(.ok, .{});
}
pub fn resolve_call(resource: RuntimeResource, call_id: u64, value_json: []const u8) beam.term {
const data = resource.unpack();
data.sync_slots_mutex.lock();
const slot = data.sync_slots.get(call_id);
data.sync_slots_mutex.unlock();
if (slot) |s| {
s.result_json = gpa.dupe(u8, value_json) catch "";
s.ok = true;
s.done.set();
return beam.make(.ok, .{});
}
const json_copy = gpa.dupe(u8, value_json) catch return beam.make(.ok, .{});
enqueue(data, .{ .resolve_call = .{ .id = call_id, .json = json_copy } });
return beam.make(.ok, .{});
}
pub fn reject_call(resource: RuntimeResource, call_id: u64, reason: []const u8) beam.term {
const data = resource.unpack();
data.sync_slots_mutex.lock();
const slot = data.sync_slots.get(call_id);
data.sync_slots_mutex.unlock();
if (slot) |s| {
s.result_json = gpa.dupe(u8, reason) catch "";
s.ok = false;
s.done.set();
return beam.make(.ok, .{});
}
const reason_copy = gpa.dupe(u8, reason) catch return beam.make(.ok, .{});
enqueue(data, .{ .reject_call = .{ .id = call_id, .json = reason_copy } });
return beam.make(.ok, .{});
}
pub fn resolve_call_term(resource: RuntimeResource, call_id: u64, value: beam.term) beam.term {
const data = resource.unpack();
data.sync_slots_mutex.lock();
const slot = data.sync_slots.get(call_id);
data.sync_slots_mutex.unlock();
if (slot) |s| {
const term_env = beam.alloc_env();
s.result_env = term_env;
s.result_term = e.enif_make_copy(term_env, value.v);
s.ok = true;
s.done.set();
return beam.make(.ok, .{});
}
const msg_env = beam.alloc_env();
const copied = e.enif_make_copy(msg_env, value.v);
enqueue(data, .{ .resolve_call_term = .{ .id = call_id, .env = msg_env, .term = copied, .ok = true } });
return beam.make(.ok, .{});
}
pub fn reject_call_term(resource: RuntimeResource, call_id: u64, reason: []const u8) beam.term {
const data = resource.unpack();
data.sync_slots_mutex.lock();
const slot = data.sync_slots.get(call_id);
data.sync_slots_mutex.unlock();
if (slot) |s| {
const term_env = beam.alloc_env();
s.result_env = term_env;
s.result_term = beam.make(reason, .{ .env = term_env }).v;
s.ok = false;
s.done.set();
return beam.make(.ok, .{});
}
const reason_copy = gpa.dupe(u8, reason) catch return beam.make(.ok, .{});
enqueue(data, .{ .reject_call = .{ .id = call_id, .json = reason_copy } });
return beam.make(.ok, .{});
}
pub fn memory_usage(resource: RuntimeResource) beam.term {
const data = resource.unpack();
const env = beam.context.env orelse return beam.make(.{ .@"error", "no env" }, .{});
var caller_pid: beam.pid = undefined;
_ = e.enif_self(env, &caller_pid);
const ref_env = beam.alloc_env();
const ref_term = e.enif_make_ref(ref_env);
enqueue(data, .{ .memory_usage = .{
.caller_pid = caller_pid,
.ref_env = ref_env,
.ref_term = ref_term,
} });
return beam.term{ .v = e.enif_make_copy(env, ref_term) };
}
pub fn dom_find(resource: RuntimeResource, selector: []const u8) beam.term {
return dom_op(resource, .find, selector, "");
}
pub fn dom_find_all(resource: RuntimeResource, selector: []const u8) beam.term {
return dom_op(resource, .find_all, selector, "");
}
pub fn dom_text(resource: RuntimeResource, selector: []const u8) beam.term {
return dom_op(resource, .text, selector, "");
}
pub fn dom_attr(resource: RuntimeResource, selector: []const u8, attr_name: []const u8) beam.term {
return dom_op(resource, .attr, selector, attr_name);
}
pub fn dom_html(resource: RuntimeResource) beam.term {
return dom_op(resource, .html, "", "");
}
fn dom_op(resource: RuntimeResource, op: types.DomOp, selector: []const u8, attr_name: []const u8) beam.term {
const data = resource.unpack();
const env = beam.context.env orelse return beam.make(.{ .@"error", "no env" }, .{});
var caller_pid: beam.pid = undefined;
_ = e.enif_self(env, &caller_pid);
const ref_env = beam.alloc_env();
const ref_term = e.enif_make_ref(ref_env);
const sel_copy = gpa.dupe(u8, selector) catch return beam.make(.{ .@"error", "OOM" }, .{});
const attr_copy = gpa.dupe(u8, attr_name) catch { gpa.free(sel_copy); return beam.make(.{ .@"error", "OOM" }, .{}); };
enqueue(data, .{ .dom_op = .{
.op = op,
.selector = sel_copy,
.attr_name = attr_copy,
.caller_pid = caller_pid,
.ref_env = ref_env,
.ref_term = ref_term,
} });
return beam.term{ .v = e.enif_make_copy(env, ref_term) };
}
pub fn send_message(resource: RuntimeResource, message: beam.term) beam.term {
const msg_env = beam.alloc_env();
const copied = e.enif_make_copy(msg_env, message.v);
enqueue(resource.unpack(), .{ .send_message = .{ .env = msg_env, .term = copied } });
return beam.make(.ok, .{});
}
pub fn define_global(resource: RuntimeResource, name: []const u8, value: beam.term) beam.term {
const val_env = beam.alloc_env();
const copied = e.enif_make_copy(val_env, value.v);
const name_copy = types.gpa.dupe(u8, name) catch return beam.make(.{ .@"error", "enomem" }, .{});
enqueue(resource.unpack(), .{ .define_global = .{ .name = name_copy, .env = val_env, .term = copied } });
return beam.make(.ok, .{});
}