tricu

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

c_abi.zig (15707B)


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