tricu

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

ffi.php (4758B)


      1 <?php
      2 
      3 declare(strict_types=1);
      4 
      5 namespace Arboricx;
      6 
      7 /**
      8  * FFI wrapper around libarboricx.so.
      9  *
     10  * Loads the shared library and exposes typed wrappers for the C ABI.
     11  */
     12 final class ArboricxFFI
     13 {
     14     private static ?\FFI $ffi = null;
     15 
     16     public static function init(string $libPath): void
     17     {
     18         if (self::$ffi !== null) {
     19             return;
     20         }
     21 
     22         // Nix output layout first, then repo layout.
     23         $candidates = [
     24             __DIR__ . '/../arboricx.h',
     25             __DIR__ . '/../../zig/include/arboricx.h',
     26         ];
     27         $headerRaw = false;
     28         foreach ($candidates as $path) {
     29             if (!is_file($path)) continue;
     30             $headerRaw = file_get_contents($path);
     31             if ($headerRaw !== false) break;
     32         }
     33         if ($headerRaw === false) {
     34             throw new \RuntimeException('Cannot read arboricx.h');
     35         }
     36 
     37         // PHP FFI only parses plain C declarations.
     38         $header = $headerRaw;
     39         $header = preg_replace('/#.*\n/', "\n", $header);
     40         $header = preg_replace('/extern\s+"C"\s*\{/', '', $header);
     41         $header = preg_replace('/^\}\s*$/m', '', $header);
     42         $header = preg_replace('/\n\s*\n+/', "\n", $header);
     43 
     44         self::$ffi = \FFI::cdef($header, $libPath);
     45         if (self::$ffi->arb_abi_version() !== 2) {
     46             throw new \RuntimeException('Arboricx ABI v2 required');
     47         }
     48     }
     49 
     50     public static function ffi(): \FFI
     51     {
     52         if (self::$ffi === null) {
     53             throw new \RuntimeException('ArboricxFFI not initialized. Call ArboricxFFI::init($libPath) first.');
     54         }
     55         return self::$ffi;
     56     }
     57 }
     58 
     59 function ctx_init(string $libPath): \FFI\CData
     60 {
     61     ArboricxFFI::init($libPath);
     62     $ctx = ArboricxFFI::ffi()->arboricx_init();
     63     if ($ctx === null) {
     64         throw new \RuntimeException('arboricx_init failed');
     65     }
     66     return $ctx;
     67 }
     68 
     69 function ctx_free(\FFI\CData $ctx): void
     70 {
     71     ArboricxFFI::ffi()->arboricx_free($ctx);
     72 }
     73 
     74 function owned(string $name, mixed ...$args): int
     75 {
     76     $ffi = ArboricxFFI::ffi();
     77     $out = $ffi->new('arb_handle_t');
     78     $status = $ffi->$name(...[...$args, \FFI::addr($out)]);
     79     if ($status !== 0) throw new \RuntimeException("$name: status $status");
     80     return (int) $out->cdata;
     81 }
     82 
     83 function retain(\FFI\CData $ctx, int $handle): int
     84 {
     85     return owned('arb_retain', $ctx, $handle);
     86 }
     87 
     88 function release(\FFI\CData $ctx, int $handle): void
     89 {
     90     $status = ArboricxFFI::ffi()->arb_release($ctx, $handle);
     91     if ($status !== 0) throw new \RuntimeException("arb_release: status $status");
     92 }
     93 
     94 function collect(\FFI\CData $ctx): void
     95 {
     96     $status = ArboricxFFI::ffi()->arb_collect($ctx);
     97     if ($status !== 0) throw new \RuntimeException("arb_collect: status $status");
     98 }
     99 
    100 function app(\FFI\CData $ctx, int $func, int $arg): int
    101 {
    102     return owned('arb_app', $ctx, $func, $arg);
    103 }
    104 
    105 function reduce(\FFI\CData $ctx, int $root, int $fuel = 1_000_000_000): int
    106 {
    107     return owned('arb_reduce', $ctx, $root, $fuel);
    108 }
    109 
    110 function ofNumber(\FFI\CData $ctx, int $n): int
    111 {
    112     return owned('arb_of_number', $ctx, $n);
    113 }
    114 
    115 function ofString(\FFI\CData $ctx, string $s): int
    116 {
    117     return owned('arb_of_string', $ctx, $s);
    118 }
    119 
    120 function toNumber(\FFI\CData $ctx, int $root): int
    121 {
    122     $out = ArboricxFFI::ffi()->new('uint64_t');
    123     $ok = ArboricxFFI::ffi()->arb_to_number($ctx, $root, \FFI::addr($out));
    124     if ($ok !== 0) {
    125         throw new \RuntimeException('arb_to_number failed');
    126     }
    127     return (int) $out->cdata;
    128 }
    129 
    130 function toString(\FFI\CData $ctx, int $root): string
    131 {
    132     $ptr = ArboricxFFI::ffi()->new('uint8_t*');
    133     $len = ArboricxFFI::ffi()->new('size_t');
    134     $ok = ArboricxFFI::ffi()->arb_to_string($ctx, $root, \FFI::addr($ptr), \FFI::addr($len));
    135     if ($ok !== 0) {
    136         throw new \RuntimeException('arb_to_string failed');
    137     }
    138     $length = (int) $len->cdata;
    139     $result = '';
    140     for ($i = 0; $i < $length; $i++) {
    141         $result .= chr($ptr[$i]);
    142     }
    143     ArboricxFFI::ffi()->arboricx_free_buf($ctx, $ptr, $length);
    144     return $result;
    145 }
    146 
    147 function toBool(\FFI\CData $ctx, int $root): bool
    148 {
    149     $out = ArboricxFFI::ffi()->new('int');
    150     $ok = ArboricxFFI::ffi()->arb_to_bool($ctx, $root, \FFI::addr($out));
    151     if ($ok !== 0) {
    152         throw new \RuntimeException('arb_to_bool failed');
    153     }
    154     return (bool) $out->cdata;
    155 }
    156 
    157 function loadBundleDefault(\FFI\CData $ctx, string $bytes): int
    158 {
    159     $cdata = ArboricxFFI::ffi()->new('uint8_t[' . strlen($bytes) . ']');
    160     for ($i = 0; $i < strlen($bytes); $i++) {
    161         $cdata[$i] = ord($bytes[$i]);
    162     }
    163     $result = owned('arb_load_bundle_default', $ctx, $cdata, strlen($bytes));
    164     if ($result === 0) {
    165         throw new \RuntimeException('arb_load_bundle_default failed');
    166     }
    167     return $result;
    168 }