lib.js (9715B)
1 /** 2 * lib.js — FFI wrapper around libarboricx.so via koffi. 3 * 4 * Exports low-level C ABI bindings and high-level helpers. 5 */ 6 7 import { existsSync } from 'node:fs'; 8 import { dirname, join, resolve } from 'node:path'; 9 import { fileURLToPath } from 'node:url'; 10 import koffi from 'koffi'; 11 12 const __dirname = dirname(fileURLToPath(import.meta.url)); 13 14 koffi.opaque('arb_ctx_t'); 15 16 // ── Library discovery ─────────────────────────────────────────────────────── 17 18 export function findLib() { 19 const env = process.env.ARBORICX_LIB; 20 if (env) { 21 if (existsSync(env)) return env; 22 throw new Error(`ARBORICX_LIB set but file not found: ${env}`); 23 } 24 25 const candidates = [ 26 resolve(__dirname, 'libarboricx.so'), 27 'libarboricx.so', 28 './libarboricx.so', 29 '/usr/local/lib/libarboricx.so', 30 '/usr/lib/libarboricx.so', 31 ]; 32 33 for (const p of candidates) { 34 if (existsSync(p)) return p; 35 } 36 37 throw new Error('libarboricx.so not found. Set ARBORICX_LIB to its full path.'); 38 } 39 40 // ── FFI setup ─────────────────────────────────────────────────────────────── 41 42 let _lib = null; 43 let _libPath = null; 44 45 function ensureLib() { 46 if (_lib) return _lib; 47 const path = findLib(); 48 _lib = koffi.load(path); 49 _libPath = path; 50 return _lib; 51 } 52 53 export function loadLib(path) { 54 if (_lib && _libPath === path) return; 55 _lib = koffi.load(path); 56 _libPath = path; 57 } 58 59 function getLib() { 60 if (_lib) return _lib; 61 return ensureLib(); 62 } 63 64 // ── Context lifecycle ─────────────────────────────────────────────────────── 65 66 export function init(libPath) { 67 if (libPath) loadLib(libPath); 68 const lib = getLib(); 69 if (lib.func('uint32_t arb_abi_version(void)')() !== 2) throw new Error('Arboricx ABI v2 required'); 70 const ctx = lib.func('arb_ctx_t *arboricx_init(void)')(); 71 if (!ctx) throw new Error('arboricx_init failed'); 72 return ctx; 73 } 74 75 export function free(ctx) { 76 getLib().func('void arboricx_free(arb_ctx_t *ctx)')(ctx); 77 } 78 79 // Each handle returned below is owned until release() or context teardown. 80 // Handles are always bigint; converting one to Number can lose identity bits. 81 function owned(signature, ...args) { 82 const out = [0n]; 83 const declaration = signature.replace(/^uint64_t /, 'int ').replace(/\)$/, ', _Out_ uint64_t *out)'); 84 const status = getLib().func(declaration)(...args, out); 85 if (status !== 0) throw new Error(`${signature.split('(')[0]}: status ${status}`); 86 return BigInt(out[0]); 87 } 88 89 export function retain(ctx, handle) { 90 return owned('uint64_t arb_retain(arb_ctx_t *ctx, uint64_t handle)', ctx, handle); 91 } 92 export function release(ctx, handle) { 93 const status = getLib().func('int arb_release(arb_ctx_t *ctx, uint64_t handle)')(ctx, handle); 94 if (status !== 0) throw new Error(`arb_release: status ${status}`); 95 } 96 export function collect(ctx) { 97 const status = getLib().func('int arb_collect(arb_ctx_t *ctx)')(ctx); 98 if (status !== 0) throw new Error(`arb_collect: status ${status}`); 99 } 100 101 // ── Bundle loading ────────────────────────────────────────────────────────── 102 103 export function loadBundle(ctx, bytes, name) { 104 const result = owned('uint64_t arb_load_bundle(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len, const char *name)', ctx, bytes, bytes.length, name); 105 return result; 106 } 107 108 export function loadBundleDefault(ctx, bytes) { 109 const result = owned('uint64_t arb_load_bundle_default(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len)', ctx, bytes, bytes.length); 110 return result; 111 } 112 113 // ── Reduction ─────────────────────────────────────────────────────────────── 114 115 export function reduce(ctx, root, fuel = 1_000_000_000n) { 116 return owned('uint64_t arb_reduce(arb_ctx_t *ctx, uint64_t root, uint64_t fuel)', ctx, root, BigInt(fuel)); 117 } 118 119 // ── Tree construction ─────────────────────────────────────────────────────── 120 121 export function leaf(ctx) { 122 return owned('uint64_t arb_leaf(arb_ctx_t *ctx)', ctx); 123 } 124 125 export function stem(ctx, child) { 126 return owned('uint64_t arb_stem(arb_ctx_t *ctx, uint64_t child)', ctx, child); 127 } 128 129 export function fork(ctx, left, right) { 130 return owned('uint64_t arb_fork(arb_ctx_t *ctx, uint64_t left, uint64_t right)', ctx, left, right); 131 } 132 133 export function app(ctx, func, arg) { 134 return owned('uint64_t arb_app(arb_ctx_t *ctx, uint64_t func, uint64_t arg)', ctx, func, arg); 135 } 136 137 // ── Codec constructors ────────────────────────────────────────────────────── 138 139 export function ofNumber(ctx, n) { 140 const big = typeof n === 'bigint' ? n : BigInt(n); 141 return owned('uint64_t arb_of_number(arb_ctx_t *ctx, uint64_t n)', ctx, big); 142 } 143 144 export function ofString(ctx, s) { 145 return owned('uint64_t arb_of_string(arb_ctx_t *ctx, const char *s)', ctx, s); 146 } 147 148 export function ofBytes(ctx, bytes) { 149 return owned('uint64_t arb_of_bytes(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len)', ctx, bytes, bytes.length); 150 } 151 152 export function ofList(ctx, items) { 153 const arr = new BigUint64Array(items.map(BigInt)); 154 return owned('uint64_t arb_of_list(arb_ctx_t *ctx, _In_ uint64_t *items, size_t len)', ctx, arr, arr.length); 155 } 156 157 // ── Codec destructors ─────────────────────────────────────────────────────── 158 159 export function toNumber(ctx, root) { 160 const out = [0]; 161 const ok = getLib().func('int arb_to_number(arb_ctx_t *ctx, uint64_t root, _Out_ uint64_t *out)')(ctx, root, out); 162 if (ok !== 0) throw new Error('arb_to_number failed'); 163 return typeof out[0] === 'bigint' ? Number(out[0]) : out[0]; 164 } 165 166 export function toString(ctx, root) { 167 const ptrOut = [null]; 168 const lenOut = [0]; 169 const ok = getLib().func('int arb_to_string(arb_ctx_t *ctx, uint64_t root, _Out_ uint8_t **out_ptr, _Out_ size_t *out_len)')(ctx, root, ptrOut, lenOut); 170 if (ok !== 0) throw new Error('arb_to_string failed'); 171 172 const bytes = koffi.decode(ptrOut[0], 'uint8_t', lenOut[0]); 173 const str = Buffer.from(bytes).toString('utf-8'); 174 getLib().func('void arboricx_free_buf(arb_ctx_t *ctx, uint8_t *ptr, size_t len)')(ctx, ptrOut[0], lenOut[0]); 175 return str; 176 } 177 178 export function toBytes(ctx, root) { 179 const ptrOut = [null]; 180 const lenOut = [0]; 181 const ok = getLib().func('int arb_to_bytes(arb_ctx_t *ctx, uint64_t root, _Out_ uint8_t **out_ptr, _Out_ size_t *out_len)')(ctx, root, ptrOut, lenOut); 182 if (ok !== 0) throw new Error('arb_to_bytes failed'); 183 184 const bytes = Buffer.from(koffi.decode(ptrOut[0], 'uint8_t', lenOut[0])); 185 getLib().func('void arboricx_free_buf(arb_ctx_t *ctx, uint8_t *ptr, size_t len)')(ctx, ptrOut[0], lenOut[0]); 186 return bytes; 187 } 188 189 export function toBool(ctx, root) { 190 const out = [0]; 191 const ok = getLib().func('int arb_to_bool(arb_ctx_t *ctx, uint64_t root, _Out_ int *out)')(ctx, root, out); 192 if (ok !== 0) throw new Error('arb_to_bool failed'); 193 return out[0] !== 0; 194 } 195 196 // ── Result unwrapping ─────────────────────────────────────────────────────── 197 198 export function unwrapResult(ctx, root) { 199 const outOk = [0]; 200 const outValue = [0]; 201 const outRest = [0]; 202 const ok = getLib().func('int arb_unwrap_result(arb_ctx_t *ctx, uint64_t root, _Out_ int *out_ok, _Out_ uint64_t *out_value, _Out_ uint64_t *out_rest)')(ctx, root, outOk, outValue, outRest); 203 if (ok !== 0) throw new Error('arb_unwrap_result failed'); 204 return { ok: outOk[0] !== 0, value: BigInt(outValue[0]), rest: BigInt(outRest[0]) }; 205 } 206 207 export function unwrapHostValue(ctx, root) { 208 const outTag = [0n]; 209 const outPayload = [0]; 210 const ok = getLib().func('int arb_unwrap_host_value(arb_ctx_t *ctx, uint64_t root, _Out_ uint64_t *out_tag, _Out_ uint64_t *out_payload)')(ctx, root, outTag, outPayload); 211 if (ok !== 0) throw new Error('arb_unwrap_host_value failed'); 212 return { tag: outTag[0], payload: BigInt(outPayload[0]) }; 213 } 214 215 // ── Kernel ────────────────────────────────────────────────────────────────── 216 217 export function kernelRoot(ctx) { 218 return owned('uint64_t arb_kernel_root(arb_ctx_t *ctx)', ctx); 219 } 220 221 // ── High-level helpers ────────────────────────────────────────────────────── 222 223 export function decode(ctx, root) { 224 try { 225 return toBool(ctx, root) ? 'true' : 'false'; 226 } catch { 227 try { 228 return toString(ctx, root); 229 } catch { 230 try { 231 return String(toNumber(ctx, root)); 232 } catch { 233 throw new Error('could not decode result'); 234 } 235 } 236 } 237 } 238 239 export function decodeType(ctx, root) { 240 try { toBool(ctx, root); return 'bool'; } catch {} 241 try { toString(ctx, root); return 'string'; } catch {} 242 try { toNumber(ctx, root); return 'number'; } catch {} 243 return 'unknown (raw tree)'; 244 }