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 }