lib.js (8846B)
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 const ctx = lib.func('arb_ctx_t *arboricx_init(void)')(); 70 if (!ctx) throw new Error('arboricx_init failed'); 71 return ctx; 72 } 73 74 export function free(ctx) { 75 getLib().func('void arboricx_free(arb_ctx_t *ctx)')(ctx); 76 } 77 78 // ── Bundle loading ────────────────────────────────────────────────────────── 79 80 export function loadBundle(ctx, bytes, name) { 81 const result = getLib().func('uint32_t arb_load_bundle(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len, const char *name)')(ctx, bytes, bytes.length, name); 82 if (result === 0) throw new Error(`arb_load_bundle failed for export "${name}"`); 83 return result; 84 } 85 86 export function loadBundleDefault(ctx, bytes) { 87 const result = getLib().func('uint32_t arb_load_bundle_default(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len)')(ctx, bytes, bytes.length); 88 if (result === 0) throw new Error('arb_load_bundle_default failed'); 89 return result; 90 } 91 92 // ── Reduction ─────────────────────────────────────────────────────────────── 93 94 export function reduce(ctx, root, fuel = 1_000_000_000n) { 95 const f = getLib().func('uint32_t arb_reduce(arb_ctx_t *ctx, uint32_t root, uint64_t fuel)'); 96 return f(ctx, root, typeof fuel === 'bigint' ? fuel : BigInt(fuel)); 97 } 98 99 // ── Tree construction ─────────────────────────────────────────────────────── 100 101 export function leaf(ctx) { 102 return getLib().func('uint32_t arb_leaf(arb_ctx_t *ctx)')(ctx); 103 } 104 105 export function stem(ctx, child) { 106 return getLib().func('uint32_t arb_stem(arb_ctx_t *ctx, uint32_t child)')(ctx, child); 107 } 108 109 export function fork(ctx, left, right) { 110 return getLib().func('uint32_t arb_fork(arb_ctx_t *ctx, uint32_t left, uint32_t right)')(ctx, left, right); 111 } 112 113 export function app(ctx, func, arg) { 114 return getLib().func('uint32_t arb_app(arb_ctx_t *ctx, uint32_t func, uint32_t arg)')(ctx, func, arg); 115 } 116 117 // ── Codec constructors ────────────────────────────────────────────────────── 118 119 export function ofNumber(ctx, n) { 120 const big = typeof n === 'bigint' ? n : BigInt(n); 121 return getLib().func('uint32_t arb_of_number(arb_ctx_t *ctx, uint64_t n)')(ctx, big); 122 } 123 124 export function ofString(ctx, s) { 125 return getLib().func('uint32_t arb_of_string(arb_ctx_t *ctx, const char *s)')(ctx, s); 126 } 127 128 export function ofBytes(ctx, bytes) { 129 return getLib().func('uint32_t arb_of_bytes(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len)')(ctx, bytes, bytes.length); 130 } 131 132 export function ofList(ctx, items) { 133 const arr = new Uint32Array(items); 134 return getLib().func('uint32_t arb_of_list(arb_ctx_t *ctx, _In_ uint32_t *items, size_t len)')(ctx, arr, arr.length); 135 } 136 137 // ── Codec destructors ─────────────────────────────────────────────────────── 138 139 export function toNumber(ctx, root) { 140 const out = [0]; 141 const ok = getLib().func('int arb_to_number(arb_ctx_t *ctx, uint32_t root, _Out_ uint64_t *out)')(ctx, root, out); 142 if (!ok) throw new Error('arb_to_number failed'); 143 return typeof out[0] === 'bigint' ? Number(out[0]) : out[0]; 144 } 145 146 export function toString(ctx, root) { 147 const ptrOut = [null]; 148 const lenOut = [0]; 149 const ok = getLib().func('int arb_to_string(arb_ctx_t *ctx, uint32_t root, _Out_ uint8_t **out_ptr, _Out_ size_t *out_len)')(ctx, root, ptrOut, lenOut); 150 if (!ok) throw new Error('arb_to_string failed'); 151 152 const bytes = koffi.decode(ptrOut[0], 'uint8_t', lenOut[0]); 153 const str = Buffer.from(bytes).toString('utf-8'); 154 getLib().func('void arboricx_free_buf(arb_ctx_t *ctx, uint8_t *ptr, size_t len)')(ctx, ptrOut[0], lenOut[0]); 155 return str; 156 } 157 158 export function toBytes(ctx, root) { 159 const ptrOut = [null]; 160 const lenOut = [0]; 161 const ok = getLib().func('int arb_to_bytes(arb_ctx_t *ctx, uint32_t root, _Out_ uint8_t **out_ptr, _Out_ size_t *out_len)')(ctx, root, ptrOut, lenOut); 162 if (!ok) throw new Error('arb_to_bytes failed'); 163 164 const bytes = Buffer.from(koffi.decode(ptrOut[0], 'uint8_t', lenOut[0])); 165 getLib().func('void arboricx_free_buf(arb_ctx_t *ctx, uint8_t *ptr, size_t len)')(ctx, ptrOut[0], lenOut[0]); 166 return bytes; 167 } 168 169 export function toBool(ctx, root) { 170 const out = [0]; 171 const ok = getLib().func('int arb_to_bool(arb_ctx_t *ctx, uint32_t root, _Out_ int *out)')(ctx, root, out); 172 if (!ok) throw new Error('arb_to_bool failed'); 173 return out[0] !== 0; 174 } 175 176 // ── Result unwrapping ─────────────────────────────────────────────────────── 177 178 export function unwrapResult(ctx, root) { 179 const outOk = [0]; 180 const outValue = [0]; 181 const outRest = [0]; 182 const ok = getLib().func('int arb_unwrap_result(arb_ctx_t *ctx, uint32_t root, _Out_ int *out_ok, _Out_ uint32_t *out_value, _Out_ uint32_t *out_rest)')(ctx, root, outOk, outValue, outRest); 183 if (!ok) throw new Error('arb_unwrap_result failed'); 184 return { ok: outOk[0] !== 0, value: outValue[0], rest: outRest[0] }; 185 } 186 187 export function unwrapHostValue(ctx, root) { 188 const outTag = [0n]; 189 const outPayload = [0]; 190 const ok = getLib().func('int arb_unwrap_host_value(arb_ctx_t *ctx, uint32_t root, _Out_ uint64_t *out_tag, _Out_ uint32_t *out_payload)')(ctx, root, outTag, outPayload); 191 if (!ok) throw new Error('arb_unwrap_host_value failed'); 192 return { tag: outTag[0], payload: outPayload[0] }; 193 } 194 195 // ── Kernel ────────────────────────────────────────────────────────────────── 196 197 export function kernelRoot(ctx) { 198 return getLib().func('uint32_t arb_kernel_root(arb_ctx_t *ctx)')(ctx); 199 } 200 201 // ── High-level helpers ────────────────────────────────────────────────────── 202 203 export function decode(ctx, root) { 204 try { 205 return toBool(ctx, root) ? 'true' : 'false'; 206 } catch { 207 try { 208 return toString(ctx, root); 209 } catch { 210 try { 211 return String(toNumber(ctx, root)); 212 } catch { 213 throw new Error('could not decode result'); 214 } 215 } 216 } 217 } 218 219 export function decodeType(ctx, root) { 220 try { toBool(ctx, root); return 'bool'; } catch {} 221 try { toString(ctx, root); return 'string'; } catch {} 222 try { toNumber(ctx, root); return 'number'; } catch {} 223 return 'unknown (raw tree)'; 224 }