tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

c_abi.zig (15933B)


      1 const std = @import("std");
      2 const wasm = @import("builtin").target.cpu.arch == .wasm32;
      3 const tree = @import("tree.zig");
      4 const Arena = @import("arena.zig").Arena;
      5 const reduce = @import("reduce.zig");
      6 const codecs = @import("codecs.zig");
      7 const kernel = @import("kernel.zig");
      8 const bundle = @import("bundle.zig");
      9 const io_driver = @import("io_driver.zig");
     10 const handles = @import("handles.zig");
     11 const Handle = handles.Handle;
     12 
     13 // Keep these values synchronized with include/arboricx.h.
     14 const Status = enum(c_int) {
     15     ok = 0,
     16     invalid_handle = 1,
     17     out_of_memory = 2,
     18     fuel_exhausted = 3,
     19     type_mismatch = 4,
     20     invalid_data = 5,
     21     io_error = 6,
     22     handle_exhausted = 7,
     23 };
     24 fn status(err: anyerror) Status {
     25     return switch (err) {
     26         error.InvalidHandle => .invalid_handle,
     27         error.OutOfMemory => .out_of_memory,
     28         error.FuelExhausted => .fuel_exhausted,
     29         error.HandleExhausted => .handle_exhausted,
     30         else => .invalid_data,
     31     };
     32 }
     33 
     34 pub const ArbCtx = struct {
     35     gpa: std.mem.Allocator,
     36     arena: Arena,
     37     kernel_root: u32,
     38     handles: handles.Table = .{},
     39 
     40     fn prepare(self: *ArbCtx) !void {
     41         self.arena.roots.clearRetainingCapacity();
     42         try self.arena.roots.append(self.gpa, self.kernel_root);
     43         try self.handles.appendRoots(&self.arena);
     44     }
     45     fn publish(self: *ArbCtx, node: u32, out: *Handle) Status {
     46         const handle = self.handles.create(self.gpa, node) catch |err| return status(err);
     47         out.* = handle;
     48         return .ok;
     49     }
     50     // Multi-output operations publish atomically: never leak half a pair.
     51     fn publishPair(self: *ArbCtx, a: u32, b: u32, out_a: *Handle, out_b: *Handle) Status {
     52         const ha = self.handles.create(self.gpa, a) catch |err| return status(err);
     53         const hb = self.handles.create(self.gpa, b) catch |err| {
     54             self.handles.release(ha) catch unreachable;
     55             return status(err);
     56         };
     57         out_a.* = ha;
     58         out_b.* = hb;
     59         return .ok;
     60     }
     61 };
     62 
     63 export fn arb_abi_version() u32 {
     64     return 2;
     65 }
     66 export fn arboricx_init() ?*ArbCtx {
     67     const gpa = if (wasm) std.heap.wasm_allocator else std.heap.smp_allocator;
     68     const ptr = gpa.create(ArbCtx) catch return null;
     69     ptr.* = .{ .gpa = gpa, .arena = Arena.init(gpa), .kernel_root = undefined };
     70     ptr.kernel_root = kernel.loadKernel(&ptr.arena) catch {
     71         ptr.arena.deinit();
     72         gpa.destroy(ptr);
     73         return null;
     74     };
     75     ptr.arena.gc_enabled = true;
     76     return ptr;
     77 }
     78 export fn arboricx_free(ctx: *ArbCtx) void {
     79     ctx.handles.deinit(ctx.gpa);
     80     ctx.arena.deinit();
     81     ctx.gpa.destroy(ctx);
     82 }
     83 export fn arboricx_free_buf(ctx: *ArbCtx, ptr: [*]u8, len: usize) void {
     84     ctx.gpa.free(ptr[0..len]);
     85 }
     86 export fn arb_release(ctx: *ArbCtx, handle: Handle) Status {
     87     ctx.handles.release(handle) catch |err| return status(err);
     88     return .ok;
     89 }
     90 export fn arb_retain(ctx: *ArbCtx, handle: Handle, out: *Handle) Status {
     91     const node = ctx.handles.get(handle) catch |err| return status(err);
     92     return ctx.publish(node, out);
     93 }
     94 export fn arb_set_gc_policy(ctx: *ArbCtx, minimum: u32, multiplier: u32) Status {
     95     ctx.arena.setGcPolicy(minimum, multiplier) catch |err| return status(err);
     96     return .ok;
     97 }
     98 export fn arb_collect(ctx: *ArbCtx) Status {
     99     ctx.prepare() catch |err| return status(err);
    100     ctx.arena.collect(&.{}) catch |err| return status(err);
    101     return .ok;
    102 }
    103 export fn arb_leaf(ctx: *ArbCtx, out: *Handle) Status {
    104     return ctx.publish(ctx.arena.alloc(.leaf) catch |err| return status(err), out);
    105 }
    106 export fn arb_stem(ctx: *ArbCtx, child: Handle, out: *Handle) Status {
    107     const child_node = ctx.handles.get(child) catch |err| return status(err);
    108     const node = ctx.arena.alloc(.{ .stem = .{ .child = child_node } }) catch |err| return status(err);
    109     return ctx.publish(node, out);
    110 }
    111 export fn arb_fork(ctx: *ArbCtx, left: Handle, right: Handle, out: *Handle) Status {
    112     const left_node = ctx.handles.get(left) catch |err| return status(err);
    113     const right_node = ctx.handles.get(right) catch |err| return status(err);
    114     const node = ctx.arena.alloc(.{ .fork = .{ .left = left_node, .right = right_node } }) catch |err| return status(err);
    115     return ctx.publish(node, out);
    116 }
    117 export fn arb_app(ctx: *ArbCtx, func: Handle, arg: Handle, out: *Handle) Status {
    118     const func_node = ctx.handles.get(func) catch |err| return status(err);
    119     const arg_node = ctx.handles.get(arg) catch |err| return status(err);
    120     const node = ctx.arena.alloc(.{ .app = .{ .func = func_node, .arg = arg_node } }) catch |err| return status(err);
    121     return ctx.publish(node, out);
    122 }
    123 export fn arb_is_leaf(ctx: *ArbCtx, root: Handle, out: *c_int) Status {
    124     const node = ctx.handles.get(root) catch |err| return status(err);
    125     out.* = if (ctx.arena.get(node).* == .leaf) 1 else 0;
    126     return .ok;
    127 }
    128 export fn arb_is_stem(ctx: *ArbCtx, root: Handle, out: *c_int) Status {
    129     const node = ctx.handles.get(root) catch |err| return status(err);
    130     out.* = if (ctx.arena.get(node).* == .stem) 1 else 0;
    131     return .ok;
    132 }
    133 export fn arb_is_fork(ctx: *ArbCtx, root: Handle, out: *c_int) Status {
    134     const node = ctx.handles.get(root) catch |err| return status(err);
    135     out.* = if (ctx.arena.get(node).* == .fork) 1 else 0;
    136     return .ok;
    137 }
    138 export fn arb_is_app(ctx: *ArbCtx, root: Handle, out: *c_int) Status {
    139     const node = ctx.handles.get(root) catch |err| return status(err);
    140     out.* = if (ctx.arena.get(node).* == .app) 1 else 0;
    141     return .ok;
    142 }
    143 export fn arb_get_stem_child(ctx: *ArbCtx, root: Handle, out_child: *Handle) Status {
    144     const idx = ctx.handles.get(root) catch |err| return status(err);
    145     const node = ctx.arena.get(idx).*;
    146     if (node != .stem) return .type_mismatch;
    147     return ctx.publish(node.stem.child, out_child);
    148 }
    149 export fn arb_get_fork_children(ctx: *ArbCtx, root: Handle, out_left: *Handle, out_right: *Handle) Status {
    150     const idx = ctx.handles.get(root) catch |err| return status(err);
    151     const node = ctx.arena.get(idx).*;
    152     if (node != .fork) return .type_mismatch;
    153     return ctx.publishPair(node.fork.left, node.fork.right, out_left, out_right);
    154 }
    155 export fn arb_get_app_func_arg(ctx: *ArbCtx, root: Handle, out_func: *Handle, out_arg: *Handle) Status {
    156     const idx = ctx.handles.get(root) catch |err| return status(err);
    157     const node = ctx.arena.get(idx).*;
    158     if (node != .app) return .type_mismatch;
    159     return ctx.publishPair(node.app.func, node.app.arg, out_func, out_arg);
    160 }
    161 export fn arb_reduce(ctx: *ArbCtx, root: Handle, fuel: u64, out: *Handle) Status {
    162     const node = ctx.handles.get(root) catch |err| return status(err);
    163     ctx.prepare() catch |err| return status(err);
    164     const result = reduce.reduce(node, &ctx.arena, fuel) catch |err| return status(err);
    165     return ctx.publish(result, out);
    166 }
    167 export fn arb_of_number(ctx: *ArbCtx, n: u64, out: *Handle) Status {
    168     return ctx.publish(codecs.ofNumber(&ctx.arena, n) catch |err| return status(err), out);
    169 }
    170 export fn arb_of_string(ctx: *ArbCtx, s: [*:0]const u8, out: *Handle) Status {
    171     return ctx.publish(codecs.ofString(&ctx.arena, std.mem.sliceTo(s, 0)) catch |err| return status(err), out);
    172 }
    173 export fn arb_of_bytes(ctx: *ArbCtx, bytes: [*]const u8, len: usize, out: *Handle) Status {
    174     return ctx.publish(codecs.ofBytes(&ctx.arena, bytes[0..len]) catch |err| return status(err), out);
    175 }
    176 export fn arb_of_list(ctx: *ArbCtx, items: [*]const Handle, len: usize, out: *Handle) Status {
    177     const nodes = ctx.gpa.alloc(u32, len) catch |err| return status(err);
    178     defer ctx.gpa.free(nodes);
    179     for (items[0..len], nodes) |h, *node| node.* = ctx.handles.get(h) catch |err| return status(err);
    180     return ctx.publish(codecs.ofList(&ctx.arena, nodes) catch |err| return status(err), out);
    181 }
    182 export fn arb_to_number(ctx: *ArbCtx, root: Handle, out: *u64) Status {
    183     const node = ctx.handles.get(root) catch |err| return status(err);
    184     ctx.prepare() catch |err| return status(err);
    185     const value = (codecs.toNumber(&ctx.arena, node) catch |err| return status(err)) orelse return .type_mismatch;
    186     out.* = value;
    187     return .ok;
    188 }
    189 export fn arb_to_bool(ctx: *ArbCtx, root: Handle, out: *c_int) Status {
    190     const node = ctx.handles.get(root) catch |err| return status(err);
    191     ctx.prepare() catch |err| return status(err);
    192     const value = (codecs.toBool(&ctx.arena, node) catch |err| return status(err)) orelse return .type_mismatch;
    193     out.* = if (value) 1 else 0;
    194     return .ok;
    195 }
    196 export fn arb_to_string(ctx: *ArbCtx, root: Handle, out_ptr: **u8, out_len: *usize) Status {
    197     const node = ctx.handles.get(root) catch |err| return status(err);
    198     ctx.prepare() catch |err| return status(err);
    199     const value = (codecs.toString(&ctx.arena, node) catch |err| return status(err)) orelse return .type_mismatch;
    200     out_ptr.* = @ptrCast(value.ptr);
    201     out_len.* = value.len;
    202     return .ok;
    203 }
    204 export fn arb_to_bytes(ctx: *ArbCtx, root: Handle, out_ptr: **u8, out_len: *usize) Status {
    205     return arb_to_string(ctx, root, out_ptr, out_len);
    206 }
    207 export fn arb_unwrap_result(ctx: *ArbCtx, root: Handle, out_ok: *c_int, out_value: *Handle, out_rest: *Handle) Status {
    208     const node = ctx.handles.get(root) catch |err| return status(err);
    209     ctx.prepare() catch |err| return status(err);
    210     const r = (codecs.unwrapResult(&ctx.arena, node) catch |err| return status(err)) orelse return .type_mismatch;
    211     const result = ctx.publishPair(r.value, r.rest, out_value, out_rest);
    212     if (result == .ok) out_ok.* = if (r.ok) 1 else 0;
    213     return result;
    214 }
    215 export fn arb_unwrap_host_value(ctx: *ArbCtx, root: Handle, out_tag: *u64, out_payload: *Handle) Status {
    216     const node = ctx.handles.get(root) catch |err| return status(err);
    217     ctx.prepare() catch |err| return status(err);
    218     const hv = (codecs.unwrapHostValue(&ctx.arena, node) catch |err| return status(err)) orelse return .type_mismatch;
    219     const result = ctx.publish(hv.payload, out_payload);
    220     if (result == .ok) out_tag.* = hv.tag;
    221     return result;
    222 }
    223 pub const arb_io_perms_t = extern struct { allow_read_all: c_int, allow_write_all: c_int };
    224 export fn arb_run_io(ctx: *ArbCtx, program: Handle, perms: ?*const arb_io_perms_t, out: *Handle) Status {
    225     if (wasm) return .io_error; // Native IO is unavailable in the browser prototype.
    226     const node = ctx.handles.get(program) catch |err| return status(err);
    227     ctx.prepare() catch |err| return status(err);
    228     const zig_perms = if (perms) |p| io_driver.IOPerms{
    229         .allow_read_all = p.allow_read_all != 0,
    230         .allow_write_all = p.allow_write_all != 0,
    231     } else io_driver.IOPerms{};
    232     const result = io_driver.runIO(ctx.gpa, &ctx.arena, node, zig_perms) catch |err| return switch (err) {
    233         error.OutOfMemory => .out_of_memory,
    234         error.FuelExhausted => .fuel_exhausted,
    235         else => .io_error,
    236     };
    237     return ctx.publish(result, out);
    238 }
    239 export fn arb_kernel_root(ctx: *ArbCtx, out: *Handle) Status {
    240     return ctx.publish(ctx.kernel_root, out);
    241 }
    242 export fn arb_load_bundle(ctx: *ArbCtx, bytes: [*]const u8, len: usize, name: [*:0]const u8, out: *Handle) Status {
    243     return ctx.publish(bundle.loadBundleExport(&ctx.arena, bytes[0..len], std.mem.sliceTo(name, 0)) catch |err| return status(err), out);
    244 }
    245 export fn arb_load_bundle_default(ctx: *ArbCtx, bytes: [*]const u8, len: usize, out: *Handle) Status {
    246     return ctx.publish(bundle.loadBundleDefaultRoot(&ctx.arena, bytes[0..len]) catch |err| return status(err), out);
    247 }
    248 
    249 // WASM-only host scratch buffers; not additions to the native C ABI.
    250 comptime {
    251     if (wasm) {
    252         @export(&hostAlloc, .{ .name = "arb_host_alloc" });
    253         @export(&hostFree, .{ .name = "arb_host_free" });
    254         @export(&hostOfText, .{ .name = "arb_host_of_text" });
    255         @export(&hostToText, .{ .name = "arb_host_to_text" });
    256         @export(&hostPreview, .{ .name = "arb_host_preview" });
    257         @export(&hostReduceUnbounded, .{ .name = "arb_host_reduce_unbounded" });
    258         @export(&hostSnapshotUnbounded, .{ .name = "arb_host_snapshot_unbounded" });
    259         @export(&hostPreviewUnbounded, .{ .name = "arb_host_preview_unbounded" });
    260         @export(&hostStats, .{ .name = "arb_host_stats" });
    261     }
    262 }
    263 // 8-byte alignment permits u64 ABI output parameters.
    264 fn hostAlloc(len: usize) callconv(.c) ?[*]align(8) u8 {
    265     const allocator = if (wasm) std.heap.wasm_allocator else std.heap.smp_allocator;
    266     const bytes = allocator.alignedAlloc(u8, .@"8", len) catch return null;
    267     return bytes.ptr;
    268 }
    269 fn hostFree(ptr: [*]align(8) u8, len: usize) callconv(.c) void {
    270     const allocator = if (wasm) std.heap.wasm_allocator else std.heap.smp_allocator;
    271     allocator.free(ptr[0..len]);
    272 }
    273 
    274 // Browser text is a list of Unicode scalars, never a list of UTF-8 bytes.
    275 fn hostOfText(ctx: *ArbCtx, ptr: [*]const u8, len: usize, limit: usize, out: *Handle) callconv(.c) Status {
    276     const view = std.unicode.Utf8View.init(ptr[0..len]) catch return .invalid_data;
    277     var iter = view.iterator();
    278     var count: usize = 0;
    279     while (iter.nextCodepoint()) |_| {
    280         if (count == limit) return .invalid_data;
    281         count += 1;
    282     }
    283     return ctx.publish(@import("source_text.zig").fromUtf8(&ctx.arena, ptr[0..len]) catch |err| return status(err), out);
    284 }
    285 fn hostToText(ctx: *ArbCtx, root: Handle, limit: usize, out_ptr: **u8, out_len: *usize) callconv(.c) Status {
    286     const node = ctx.handles.get(root) catch |err| return status(err);
    287     ctx.prepare() catch |err| return status(err);
    288     const bytes = @import("source_text.zig").toUtf8UnboundedWork(&ctx.arena, node, limit) catch |err| return status(err);
    289     out_ptr.* = @ptrCast(bytes.ptr);
    290     out_len.* = bytes.len;
    291     return .ok;
    292 }
    293 // Diagnostic counts, not byte/RSS limits; useful for repeated-session checks.
    294 fn hostStats(ctx: *ArbCtx, slots: *u32, owned: *u32, nodes: *u32, live: *u32) callconv(.c) Status {
    295     slots.* = @intCast(ctx.handles.slots.items.len);
    296     owned.* = 0;
    297     for (ctx.handles.slots.items) |slot| {
    298         if (slot.node != null) owned.* += 1;
    299     }
    300     nodes.* = ctx.arena.len();
    301     live.* = @intCast(ctx.arena.last_live);
    302     return .ok;
    303 }
    304 
    305 fn hostPreview(ctx: *ArbCtx, root: Handle, limit: u32, fuel: u64, out_ptr: **u8, out_len: *usize) callconv(.c) Status {
    306     const node = ctx.handles.get(root) catch |err| return status(err);
    307     ctx.prepare() catch |err| return status(err);
    308     const bytes = @import("preview.zig").capture(&ctx.arena, node, limit, fuel) catch |err| return status(err);
    309     out_ptr.* = @ptrCast(bytes.ptr);
    310     out_len.* = bytes.len;
    311     return .ok;
    312 }
    313 
    314 // Browser work is cancellable by Worker termination, not an implicit fuel cap.
    315 fn hostReduceUnbounded(ctx: *ArbCtx, root: Handle, out: *Handle) callconv(.c) Status {
    316     const node = ctx.handles.get(root) catch |err| return status(err);
    317     ctx.prepare() catch |err| return status(err);
    318     const result = reduce.reduceUnbounded(node, &ctx.arena) catch |err| return status(err);
    319     return ctx.publish(result, out);
    320 }
    321 fn hostPreviewUnbounded(ctx: *ArbCtx, root: Handle, limit: u32, out_ptr: **u8, out_len: *usize) callconv(.c) Status {
    322     const node = ctx.handles.get(root) catch |err| return status(err);
    323     ctx.prepare() catch |err| return status(err);
    324     const bytes = @import("preview.zig").captureUnboundedWork(&ctx.arena, node, limit) catch |err| return status(err);
    325     out_ptr.* = @ptrCast(bytes.ptr);
    326     out_len.* = bytes.len;
    327     return .ok;
    328 }
    329 
    330 // Full bounded result transport, distinct from the 256-node preview API.
    331 fn hostSnapshotUnbounded(ctx: *ArbCtx, root: Handle, limit: u32, out_ptr: **u8, out_len: *usize) callconv(.c) Status {
    332     const node = ctx.handles.get(root) catch |err| return status(err);
    333     ctx.prepare() catch |err| return status(err);
    334     const bytes = @import("preview.zig").snapshotUnboundedWork(&ctx.arena, node, limit) catch |err| return status(err);
    335     out_ptr.* = @ptrCast(bytes.ptr);
    336     out_len.* = bytes.len;
    337     return .ok;
    338 }