Skip to content

KarpelesLab/goro

Repository files navigation

Goro

CI Coverage Status Go Reference

PHP engine implemented in pure Go. Feature-complete for PHP 8.5 language features.

Why?

PHP is a nice language but is having trouble keeping up with modern languages. This implementation makes a number of things possible:

  • Usage of goroutines, go channels, etc from within PHP
  • Better caching of compiled code by allowing sharing of compiled or live objects (classes, objects, etc) between running PHP scripts
  • Use Go's memory management within PHP
  • Ability to run functions or code sandboxed (including filesystem via fs.FS) to limit security risks
  • Easily call the PHP engine from Go to execute pieces of PHP code (user provided or legacy)

Install

go install github.com/KarpelesLab/goro/sapi/php-cli@latest

Status

Goro passes ~11,864 of 12,121 tests (~97.9%) from the PHP 8.5.5 test suite (~170 failures, 87 skipped in CI). PHP memory_limit enforcement (128MB default). Includes PCRE2 via gopcre2, IANA timezones via gotz, and 10 extensions (session, xml, curl, gd, sockets, zlib, mysqli, sqlite3, bz2).

Remaining test failures by area

Area Failures Notes
ext/date 51 DatePeriod serialization format, date_parse edge cases, DST fallback transitions
attributes 20 Reflection __toString formatting, delayed target validation, AST printing
exceptions 9 __toString error location, variance autoload, stream wrappers
closures 9 Closure const expressions, binding edge cases
clone 6 AST printing, clone-with edge cases
exit 5 exit() in custom SAPIs, disabling exit
constexpr 5 Constant expression edge cases (new in defaults, array unpack)
asymmetric_visibility 5 Static props, nested variations, indirect modification
assert 5 assert() callback exceptions, ??= in assert, AST pretty-printer
ext/mbstring 4 Encoding conversion edge cases
constants 4 Constant evaluation edge cases
ext/hash 3 PHP serialization format edge cases
ext/gmp 3 GMP unserialize with references
Other ~75 Scattered across ~40 areas (≤3 failures each): reference tracking, object ID ordering, warning ordering, etc.

SAPIs

SAPI Status
CLI (php-cli) Working
CGI (php-cgi) Working
FPM (php-fpm) Working
HTTP handler (php-httpd) Working
Test runner (php-test) Working

Extensions

Extension Functions Pass Rate Notes
standard 527+ ~70% Core functions, arrays, strings, files, math, output buffering, streams
ctype 11 100% Complete
json 5 98% json_encode, json_decode, json_validate, error handling
pcre 11 67% preg_match, preg_replace, preg_split, preg_grep — PCRE2 via gopcre2 (backreferences, lookahead)
hash 14 94% hash, hash_hmac, hash_file, hash_pbkdf2, hash_hkdf, incremental
gmp 49 96% Arithmetic, division, modular, bitwise, primes, GCD/LCM, factorial, operator overloading, import/export
mbstring 49 97% strlen, substr, strpos, strtolower/upper, convert_encoding, detect_encoding, check_encoding
date 48 89% date, time, strtotime, mktime, DateTime, DateTimeImmutable, DateInterval, DatePeriod, DateTimeZone, sunrise/sunset
openssl 16 AES/DES/RSA/ECDSA encryption, signing, key generation via Go crypto
bz2 2 Compress (gobzip2) and decompress (Go stdlib)
zlib 22 gzcompress/gzencode/gzdeflate, gzip file ops, stream filters, compress.zlib://
session 19 session_start/id/destroy, file-based storage, $_SESSION superglobal
xml 18 SimpleXMLElement class, xml_parser_create/parse, simplexml_load_string/file
curl 13 CurlHandle class, curl_init/setopt/exec/getinfo via Go net/http
sockets 25 Socket class, socket_create/bind/listen/accept/connect, stream_socket_*, fsockopen
mysqli 25 mysqli/mysqli_result/mysqli_stmt classes, prepared statements, transactions via go-sql-driver/mysql
sqlite3 20+ SQLite3/SQLite3Result/SQLite3Stmt classes, prepared statements via glebarez/go-sqlite (pure Go)
spl 40+ 82% ArrayObject, ArrayIterator, SplFileObject, SplFixedArray, SplHeap, SplObjectStorage, iterators
reflection 8 classes 75% ReflectionClass (with __toString), ReflectionMethod, ReflectionProperty, ReflectionFunction, ReflectionParameter, ReflectionAttribute
gd 60+ GdImage class, imagecreate/truecolor, drawing (lines, rectangles, ellipses, polygons, arcs, fill), text (TTF, built-in fonts), PNG/JPEG/GIF/BMP I/O, copy/resize/resample/rotate/crop/flip/scale, filters, convolution — pure Go via gogd
getimagesize 100% 16 image formats (JPEG, PNG, GIF, BMP, WebP, AVIF, HEIF, TIFF, PSD, etc.)

Not yet implemented

Extension Notes
PDO Planned via database/sql (MySQL + SQLite drivers already available)
iconv Planned via golang.org/x/text/transform
intl Internationalization (ICU)
Phar PHP archive format

Bytecode VM (experimental)

A stack-based bytecode VM runs in parallel to the AST tree-walking executor. It's opt-in and falls back to the AST per-function on any unsupported construct.

Enable it with the GORO_VM=1 environment variable:

GORO_VM=1 php-cli script.php

The VM emitter currently handles:

  • Scalar literals (int, float, string, bool, null) — including the case-insensitive constants true/false/null.
  • Variable read/write, with a per-frame slot cache so reads skip the FuncContext hashtable entirely. Functions with no extract/compact/$$x/global/static/$GLOBALS use further perform slot-only writes (skip the hashtable mirror) for a sizeable perf win on write-heavy loops.
  • Arithmetic / bitwise / shift / comparison / concat / unary (-, ~, !); plain and compound assignment (=, +=, …); pre/post ++/--.
  • Short-circuit && / || and ?? (null coalesce, simple-variable LHS only).
  • if / elseif / else, while, for, foreach (value form, array + object iteration), break / continue (single level), return, throw, try / catch (multi-type union, multi-clause, destructor-during-catch-bind chained correctly), try/finally (delegated to the AST runner so finally runs on every exit path).
  • String interpolation ("hello $name", "v={$x}") lowered to a chain of OP_CONCAT.
  • Array literals ([…], including keyed k => v); $a[$k] read; $a[$k] = v and $a[] = v writes (with auto-vivification from null/false and string-offset semantics).
  • Object instantiation (new Cls(args…)), property read ($obj->prop), method call ($obj->m(args…)) with full PHP visibility checks (private/protected) and __call fallback. $this outside object context throws the correct Error. Class const / static prop / Foo::class (Foo::CONST, self::method, Foo::$bar, Foo::class) are AST-delegated for full CompileDelayed / visibility / LSB semantics.
  • Builtin and user-defined function calls (positional args). Calls to by-ref builtins (end, sort, array_walk, array_push, …) fall back to AST so the by-ref binding works.
  • Inline closures (function() { … }, fn() => …) with use captures, $this binding, and arrow auto-capture. Indirect calls ($f(), [$obj, 'method']()) resolve the callable at runtime via compiler.ResolveCallable and forward the implicit $this.

Out of scope (falls back to AST per-function via ErrUnsupported):

  • By-ref returns and by-ref parameters on user-defined functions (the VM passes pre-evaluated ZVals; the AST passes Runnables and binds Writables).
  • Generators (yield).
  • $obj->prop = v and other property writes (deferred until a public WriteValue helper exists).
  • Nullsafe chains ($obj?->...).
  • Spread (...$arr) and named arguments.
  • Dynamic names ($$x, $obj->{$x}, new $cls()).
  • Multi-level break N / continue N.
  • Type-hinted return values (the AST coerces; the VM doesn't yet).
  • User-defined constants (PHP_INT_MAX, MYAPP_FOO, …).
  • List destructure, anonymous classes, extract/compact/$$x and similar locals-introspecting builtins (those force slot-only off; currently we just compile-time bail when the body uses them).

Functions matching any of the above run as AST as before — the engine silently picks the right backend per-function, with no behaviour change.

Bench wins (vs. AST baseline, per-iter):

Benchmark AST VM Δ
Arithmetic 58M ns 27M ns -54%
ArrayOps 11M ns 8M ns -27%
Fibonacci 26M ns 21M ns -19%
StringConcat 13M ns 11M ns -18%
FunctionCalls 18M ns 15M ns -14%

Larger gains require either an unboxed value type, slot-only writes (skipping the hashtable mirror) for slot-safe functions, or register-based opcodes. The 64-bit instruction format already has room for the last one.

Architecture

Process

A process object is typically created once per runtime environment. It caches compiled code and holds global stream wrapper resources, persistent connections, and runtime cache.

Global

When a request is received or script execution is requested, a new Global context is created. It contains runtime state: global variables, declared functions, classes, constants, output buffers, and memory limits.

Context

Context is a local scope (e.g., within a running function). Global has a root context, and each function call creates a new context to separate variable scope.

Contributing

See development.md for details on writing extensions.

Writing an extension: create a directory in ext/, write functions with magic comment prefixes, run make buildext to generate bindings, and add the extension import to each SAPI's main.go.

Releases

No releases published

Sponsor this project

 

Packages

 
 
 

Contributors