← All docs

The Runtime

Hand-written assembly routines for strings, arrays, generators, fibers, system calls, exceptions, and I/O.

Source: src/codegen_support/runtime/mod.rs, emitters.rs, diagnostics.rs, data/, strings/, arrays/, buffers/, callables/, exceptions.rs, exceptions/, io/, objects/, spl/, system/, pointers/, zval/, fibers/, generators/, plus the eval hooks eval_bridge.rs / eval_scope.rs

The runtime is a collection of hand-written assembly routines that handle operations too complex for inline code generation. When the code generator needs to convert an integer to a string or concatenate two strings, it emits a bl __rt_itoa or bl __rt_concat — a call to a runtime routine.

These routines end up in every compiled binary. In the CLI flow they are usually pre-assembled into the cached runtime object rather than textually appended to each user .s file, but they are still part of the final executable rather than an external shared dependency.

Eval execution paths

eval() is an optional hybrid boundary rather than part of the ordinary shared runtime. Eligible literal fragments are parsed at compile time and lowered directly to EIR. Some literal fragments use only the core eval-scope helpers, while dynamic or unsupported fragments link the elephc_magician static interpreter bridge.

The bridge shares elephc’s boxed Mixed cells, copy-on-write containers, generated class/callable metadata, diagnostics, exceptions, and target-aware ABI helpers. It is embedded only when the final EIR module requires the eval_bridge runtime feature; the presence of a fully native literal eval() call does not force it into the binary.

Magician’s base archive does not reference PCRE2. When static detection or --with-regex enables the regex runtime, generated eval setup registers the managed PCRE2 shim as an opaque provider; otherwise regex builtins are absent from dynamic eval dispatch.

The compile-time planner, scope/context lifecycle, eval-specific EIR instructions, parse cache, ownership rules, and bridge linking contract are documented in Eval Runtime Architecture. PHP-visible syntax, supported statements and builtins, reflection behavior, safety, and limitations live in Eval.

Why a runtime?

Some operations can’t be done with a few inline instructions:

  • Integer to string (itoa): Requires a loop that divides by 10, extracts digits, and writes them right-to-left
  • String concatenation: Needs to copy bytes from two source strings into a buffer
  • Array operations: Require heap allocation, bounds checking, and element copying

These are 20-50+ instructions each. Inlining them at every call site would bloat the binary. Instead, they’re emitted once and called with bl.

Naming convention

All runtime routines start with __rt_:

__rt_itoa          integer → string
__rt_resource_to_string resource → "Resource id #N"
__rt_ftoa          float → string
__rt_concat        string + string → string
__rt_str_eq        string == string → bool
__rt_array_new     allocate a new array
__rt_throw_current throw the active exception
__rt_build_argv    build $argv from C strings

Diagnostic routines

Source: src/codegen_support/runtime/diagnostics.rs

These helpers implement PHP’s @ error-suppression operator and the runtime warning channel. The suppression depth lives in _rt_diag_suppression; while it is non-zero, suppressible warnings are silently dropped instead of written to stderr. They are emitted before any PHP-visible helper so the rest of the runtime can report warnings through a single path.

RoutineWhat it doesInputOutput
__rt_diag_push_suppressionEnter one nested @ suppression scope (increment _rt_diag_suppression)
__rt_diag_pop_suppressionLeave one @ suppression scope, clamped against underflow
__rt_diag_warningWrite a runtime warning string to stderr unless suppression is activex1/x2 = message string

String routines

Source: src/codegen_support/runtime/strings/

__rt_itoa — Integer to string

File: strings/itoa.rs

Converts a signed 64-bit integer in x0 to a decimal string.

Input: x0 = integer value Output: x1 = pointer to string, x2 = length

Algorithm:

  1. Check for negative → set flag, negate
  2. Check for zero → output “0” directly
  3. Loop: divide by 10 (udiv + msub), convert remainder to ASCII digit (+ 48), store right-to-left
  4. Prepend ’-’ if negative
  5. Update concat buffer offset

The digits are written right-to-left because division gives us the least significant digit first. The result is written into the concat buffer.

__rt_resource_to_string — Resource to string

File: strings/resource_to_string.rs

Formats the native resource payload used by stream handles as PHP’s display string (Resource id #N). The helper keeps resources distinct from integers when boxing into mixed, while still letting the I/O runtime pass the underlying file descriptor to stream syscalls.

Input: x0 = native resource payload Output: x1 = pointer to string, x2 = length

__rt_resource_write_stdout uses the same display form for echo / print without exposing the raw file descriptor as an integer.

__rt_ftoa — Float to string (precision = 14)

File: strings/ftoa.rs

Converts a double-precision float in d0 to the decimal string PHP produces for echo, (string), string interpolation, concatenation and print_r() — that is, zend_gcvt(value, 14, '.', 'E'). It formats the value with snprintf("%.14G", …) into a stack scratch buffer, then copies the bytes into the concat buffer applying the two fixups where C’s %G differs from zend_gcvt: exponential form always keeps a mantissa fraction (1.0E+300, not 1E+300) and the exponent is written without zero padding (1.0E-7, not 1E-07). NAN is emitted unsigned, since glibc renders a negative quiet NaN as -NAN and PHP never does. INF / -INF pass through unchanged.

Input: d0 = float value Output: x1 = pointer to string, x2 = length

__rt_ftoa_repr — Float to string (serialize_precision = -1)

File: strings/ftoa.rs

The rendering var_dump() uses: the shortest decimal string that round-trips back to the same double, with an uppercase E, a d.d mantissa in exponential form, an unpadded exponent, and no trailing .0 for integer-valued floats. The plain/exponential boundary is zend_gcvt’s for ndigit = 17 (decpt < -3 || decpt > 17), which keeps var_dump(1e16) as 10000000000000000 where echo 1e16 is already 1.0E+16. Finite values are handed to the shared __rt_json_ftoa shortest-round-trip formatter (the one json_encode/serialize use) with 'E' as the exponent marker; this helper only owns the INF / -INF / NAN spellings.

Input: d0 = float value Output: x1 = pointer to string, x2 = length

__rt_concat — String concatenation

File: strings/concat.rs

Concatenates two strings by copying both into the concat buffer.

Input: x1/x2 = left string (ptr/len), x3/x4 = right string (ptr/len) Output: x1 = pointer to result, x2 = total length

Algorithm:

  1. Get current position in concat buffer
  2. Copy left string bytes (byte-by-byte loop)
  3. Copy right string bytes
  4. Update buffer offset
  5. Return pointer to start of result + total length

__rt_atoi — String to integer

File: strings/atoi.rs

Parses a decimal string into a 64-bit integer. Handles optional leading - sign.

Input: x1 = string pointer, x2 = length Output: x0 = integer value

__rt_str_eq — String equality

File: strings/str_eq.rs

Compares two strings byte-by-byte.

Input: x1/x2 = first string, x3/x4 = second string Output: x0 = 1 if equal, 0 if not

Algorithm:

  1. Compare lengths — if different, return 0 immediately
  2. Loop: compare byte by byte
  3. If all bytes match, return 1

Other string routines

Each routine follows the same pattern — inputs in registers, output in standard result registers:

RoutineWhat it doesInputOutput
__rt_strcopyCopy string into concat bufferx1/x2x1/x2
__rt_php_num_scanClip a C string to PHP’s leading numeric run (_is_numeric_string_ex grammar) in place and report whether the whole string was numeric. Runs between __rt_cstr and libc so strtod/strtoll never see hexadecimal, INF/NAN or underscore forms PHP does not acceptx0 = C stringx0 = run pointer, x1 = fully-numeric flag
__rt_str_to_numberParse a PHP numeric string for loose comparison, is_numeric(), and numeric-string casts (via __rt_php_num_scan)x1/x2numeric payload + success flag
__rt_str_looks_like_int_for_coercionValidate PHP coercive int-parameter numeric strings while rejecting libc-only strtod forms such as 0x, INF, and NANx1/x2x0 (0 or 1)
__rt_str_to_intParse a PHP numeric-string prefix with integer/float forms and truncate toward zero like PHP (int) castsx1/x2x0 (integer)
__rt_str_loose_eqCompare two strings using PHP loose-comparison numeric-string rules before falling back to bytestwo stringsx0 (0 or 1)
__rt_strtolowerLowercase conversionx1/x2x1/x2
__rt_strtoupperUppercase conversionx1/x2x1/x2
__rt_trimStrip whitespace (no args) or chars in maskx1/x2x1/x2
__rt_ltrim / __rt_rtrimStrip left/right whitespace or maskx1/x2x1/x2
__rt_trim_maskStrip chars in custom mask from both endsx1/x2 + maskx1/x2
__rt_ltrim_mask / __rt_rtrim_maskStrip custom mask from left/rightx1/x2 + maskx1/x2
__rt_strrevReverse string (byte-wise)x1/x2x1/x2
__rt_grapheme_strrevReverse a UTF-8 string by grapheme cluster for PHP 8.6 grapheme_strrev(); returns false on malformed UTF-8x1/x2x1/x2
__rt_mb_strlenMultibyte-aware string length for mb_strlen() (emitted only for programs that use it)x1/x2x0
__rt_strposFind substringx1/x2 + x3/x4x0 (index or -1)
__rt_strrposFind last occurrencex1/x2 + x3/x4x0
__rt_striposFind substring, ASCII case-insensitivex1/x2 + x3/x4x0 (index or -1)
__rt_strriposFind last occurrence, ASCII case-insensitivex1/x2 + x3/x4x0
__rt_str_repeatRepeat N times with heap fallback for large resultsx1/x2 + countx1/x2
__rt_str_replaceReplace all occurrencessearch + replace + subjectx1/x2
__rt_explodeSplit by delimiterdelimiter + stringx0 (array ptr)
__rt_implodeJoin string array with glueglue + arrayx1/x2
__rt_implode_intJoin integer array with glueglue + arrayx1/x2
__rt_strcmpBinary comparisontwo stringsx0 (-1, 0, 1)
__rt_strcasecmpCase-insensitive comparetwo stringsx0
__rt_str_starts_withCheck prefix matchx1/x2 + x3/x4x0 (0 or 1)
__rt_str_ends_withCheck suffix matchx1/x2 + x3/x4x0 (0 or 1)
__rt_chrASCII code → charx0x1/x2
__rt_addslashesEscape quotes/backslashesx1/x2x1/x2
__rt_nl2brInsert <br /> before newlinesx1/x2x1/x2
__rt_bin2hexBinary → hex stringx1/x2x1/x2
__rt_hex2binHex → binaryx1/x2x1/x2
__rt_md5MD5 hashx1/x2x1/x2
__rt_sha1SHA1 hashx1/x2x1/x2
__rt_sprintfFormat string, including deferred static/Mixed coercion and eval-aware object stringificationformat + tagged args on stack + optional eval contextx1/x2
__rt_sprintf_pack_mixedPack a boxed scalar or preserve a boxed non-scalar for deferred formattingboxed Mixed in x0record payload/tag in x0/x1
__rt_sprintf_mixed_to_intApply PHP numeric formatting rules and warnings to a boxed or raw-tagged array, object, callable, iterable, or resourcerecord tag/payload + conversion kind + optional eval context in x0-x3x0
__rt_sprintf_mixed_to_stringRender a boxed or raw-tagged array/resource, dispatch native/eval __toString(), or throw a catchable Error for a non-stringable valuerecord tag/payload + optional eval context in x0/x1/x2owner + x1/x2
__rt_base64_encodeBase64 encodex1/x2x1/x2
__rt_base64_decodeBase64 decode (php-src semantics, $strict in x3)x1/x2/x3x0 ok flag + x1/x2
__rt_quoted_printable_encodeMIME quoted-printable encodex1/x2x1/x2
__rt_urlencodeURL encodex1/x2x1/x2
__rt_urldecodeURL decodex1/x2x1/x2
__rt_htmlspecialcharsHTML escapex1/x2x1/x2
__rt_html_entity_decodeDecode HTML entitiesx1/x2x1/x2
__rt_rawurlencodeURL encode (RFC 3986)x1/x2x1/x2
__rt_parse_urlParse URL bytes and select an array/string/int/null/false resultx1/x2 + component in x3x0 (Mixed ptr)
__rt_stripslashesRemove escape backslashesx1/x2x1/x2
__rt_ucwordsUppercase first letter of each wordx1/x2x1/x2
__rt_str_ireplaceCase-insensitive replacesearch + replace + subjectx1/x2
__rt_substr_replaceReplace substring at offsetstr + replacement + start + lenx1/x2
__rt_str_padPad string to lengthstr + len + pad_str + typex1/x2
__rt_str_splitSplit into chunksstr + chunk_lenx0 (array ptr)
__rt_wordwrapWrap text at word boundariesstr + width + break + cutx1/x2
__rt_number_formatFormat number with separatorsfloat + decimals + sepx1/x2
__rt_hashHash with algorithmalgo + datax1/x2
__rt_hash_init / __rt_hash_update / __rt_hash_finalIncremental hash-context API backing hash_init() and friendscontext + datacontext / x1/x2
__rt_hash_copyClone an incremental hash contextcontextcontext
__rt_hash_ctx_freeFree a HashContext via elephc_crypto_free; the sole destructor, called by __rt_mixed_free_deep when a Mixed(tag=9, kind=2) cell is released at scope exit (hash_final no longer frees)context
__rt_hash_hmacKeyed HMAC over a messagealgo + key + datax1/x2
__rt_hash_equalsConstant-time string comparisontwo stringsx0 (0 or 1)
__rt_hash_algos_listBuild the hash_algos() array of supported algorithm namesx0 (array ptr)
__rt_digest_to_stringFormat a raw digest as lowercase hexdigestx1/x2
__rt_crc32CRC32 checksumx1/x2x0
__rt_inet_ntop / __rt_inet_ptonIPv4/IPv6 address ↔ packed-binary conversionaddressx1/x2
__rt_long2ip / __rt_ip2longDotted-quad string ↔ integer conversionx0 or x1/x2x1/x2 or x0
__rt_vsprintfvsprintf() formatting with an argument arrayformat + array + optional eval contextx1/x2
__rt_sscanfParse string with formatstr + formatx0 (array ptr)

Callable routines

Source: src/codegen_support/runtime/callables/ (5 files including mod.rs)

These routines implement the runtime fallback path for is_callable() when the argument is not a compile-time literal or statically known callable value, plus the Closure::bind family helper. They consult generated metadata for builtins, user functions, public methods, public static methods, and __invoke objects.

Dynamic invocation builtins use generated callable descriptors rather than these boolean helpers. A descriptor is an eight-word record: callable kind, native entry pointer, PHP-visible name pointer, name length, signature-record pointer, environment-record pointer, invocation-record pointer, and optional uniform invoker pointer. Indirect calls keep the one-word callable ABI by loading the native entry from the descriptor, while descriptor-invoker paths call the generated (descriptor, boxed argument container) -> mixed adapter.

The signature side record stores visible, required, and regular parameter counts; variadic index; return type and return register count; declared-return flags; parameter names and types; defaults; by-reference flags; and declared-parameter flags. The environment record stores capture and hidden wrapper-parameter bindings. The invocation record stores the callable shape (string, callable array, closure, first-class callable, object __invoke, static method, instance method, builtin, extern, or user function) plus receiver, method, and auxiliary names where applicable.

call_user_func(), call_user_func_array(), direct string-variable calls, direct callable-array variable and literal calls, direct invokable-object calls, method first-class callable variable calls, and iterator_apply() compare runtime string names or descriptor-selected callable entries against generated cases. Runtime string callback names now materialize the matched descriptor and invoke its uniform invoker slot for user functions, extern wrappers, builtins, and public static methods, so signature defaults, named arguments, by-reference flags, variadics, and return boxing live behind the descriptor rather than in each string-dispatch callsite. iterator_apply() can also retain a branch-selected captured descriptor or runtime-selected callable-array descriptor and call that descriptor’s invoker directly for each loop iteration. array_map(), array_filter(), array_reduce(), array_walk(), usort(), uksort(), uasort(), and preg_replace_callback() retain descriptor-valued callable variables and callable parameters in descriptor callback environments, so by-value closure captures, method receivers, and late-static binding state are read from descriptor storage instead of current source locals. These callback runtimes also match runtime callable-array variables such as [$object, $method] or [$class, $method] against public method descriptor cases before invoking the shared descriptor callback wrapper. usort(), uksort(), and uasort() use descriptor comparator environments for branch-selected captured descriptors. CallbackFilterIterator and RecursiveCallbackFilterIterator store branch-selected captured descriptors and runtime-selected callable-array variable or literal descriptors in persistent callback environments, then call the same descriptor invoker from accept(). call_user_func() and direct callable-variable invocations build boxed indexed argument containers for descriptor-backed calls; named direct calls build associative hashes. Variable arguments in either shape can be encoded as internal reference-cell markers, including boxed markers inside named hash entries, so the generated invoker can either pass the original storage to by-reference parameters or dereference it for by-value parameters after reading the descriptor signature. call_user_func_array() clones the provided indexed array or hash, widens the clone to boxed Mixed, and passes one boxed container to the descriptor invoker. The invoker inspects the boxed container tag at runtime, dispatches to indexed-array or associative-hash argument materialization, unboxes boxed array/hash payloads for declared array parameters, and is keyed by callable signature rather than by caller array shape. Closure and first-class-callable descriptors with use (...), receiver, or late-static-binding context allocate runtime descriptor copies whose fixed header is followed by capture value slots; descriptor invokers reload those slots as hidden arguments, including by-reference captures. Method first-class callable variables use this descriptor path even when the original receiver variable still has static metadata, so reassignment of that source local cannot change the stored receiver. Callable variables and array elements whose descriptor was selected by an earlier runtime expression use the descriptor invoker when the local callsite no longer has static signature or capture metadata. Compile-time static-method callable arrays now materialize static-method descriptors for direct variable and literal calls, call_user_func() calls, and call_user_func_array() calls, including associative argument containers whose leftover string keys are forwarded into user-defined variadic method tails. Direct instance-method callable-array variable calls read the receiver from slot zero of the stored callable array, while direct instance-method callable-array literal calls evaluate the receiver before visible call arguments; both prepend it as descriptor argument zero and then let the descriptor signature normalize visible named/default/variadic/by-reference arguments. Direct invokable-object calls prepend the object as descriptor argument zero and use the object-invoke descriptor shape for the same named/default/variadic/by-reference handling, including non-local receiver expressions such as (new Runner())(...). Compile-time instance-method callable arrays and invokable objects use instance/object descriptor shapes for direct call_user_func() calls and for call_user_func_array() calls whose argument container is literal indexed, literal associative, dynamic indexed, dynamic associative, or runtime-opaque mixed/union; the receiver is prepended as a synthetic descriptor argument before the visible callback arguments. Receiver-bound runtime-opaque containers branch on their boxed payload tag, clone and widen the payload to Mixed entries, then build a receiver-prefixed indexed array or associative hash for the descriptor invoker.

Extern callback trampolines use the same descriptor invoker from a C-facing entry point. Each extern callable callsite has a generated descriptor slot and trampoline symbol; the trampoline reloads the current descriptor, boxes incoming scalar/pointer C callback arguments as a temporary indexed Mixed array, invokes the descriptor, casts the boxed result to int, float, bool, ptr, or void, and returns through the target C ABI.

RoutineWhat it doesInputOutput
__rt_is_callable_stringResolve a string as a builtin, active user function, or Class::method static-method callablex1/x2 = stringx0 = bool
__rt_is_callable_method_nameCheck whether an object exposes a public method with the supplied nameobject pointer + method stringx0 = bool
__rt_is_callable_static_method_nameCheck whether a class string exposes a public static method with the supplied nameclass string + method stringx0 = bool
__rt_is_callable_objectCheck object callability through public __invoke metadataobject pointerx0 = bool
__rt_is_callable_arrayValidate indexed callable arrays such as [$obj, "method"] or [ClassName::class, "method"]array pointerx0 = bool
__rt_is_callable_assocValidate associative callable-array payloads produced through boxed or dynamic data pathshash pointerx0 = bool
__rt_is_callable_mixedUnbox a Mixed value and dispatch string, array, hash, or object callable checksmixed pointerx0 = bool
__rt_is_callable_heapDispatch callable checks from a raw heap pointer by inspecting its heap-kind tagheap pointerx0 = bool
__rt_callable_descriptor_releaseFree a heap-backed callable descriptor copy plus the by-value capture slots appended after its static header; static .data descriptors are ignoredx0 = descriptor pointer
__rt_closure_bindBind a $this-only closure to a new receiver for Closure::bind / Closure::bindTo / Closure::call: copy the runtime descriptor, overwrite the captured object, and incref it. Closures with any other capture shape abort with a fatal diagnosticx0 = source closure descriptor, x1 = new $this objectx0 = bound descriptor copy

Array routines

Source: src/codegen_support/runtime/arrays/ (176 files, plus the hash_sort/ target split, 2 files)

Core allocation

RoutineWhat it doesInputOutput
__rt_heap_allocFree-list + bump allocator with a 16-byte [size:4][refcount:4][kind:8] headerx0 = sizex0 = pointer
__rt_heap_freeReturn block to free list (bump reset if last block)x0 = pointer
__rt_heap_free_safeFree only if pointer is in heap rangex0 = pointer
__rt_heap_debug_failPrint a heap-debug fatal error and terminate immediatelyx1 = msg ptr, x2 = msg len
__rt_heap_debug_check_liveReject incref / decref operations on already-freed heap blocksx0 = pointer
__rt_heap_debug_validate_free_listValidate the ordered free list and small-bin chains before allocator mutations
__rt_heap_debug_reportPrint heap-debug exit summary with leak/high-water stats
__rt_heap_kindReturn the uniform heap-kind tag for a heap-backed pointerx0 = pointerx0 = kind
__rt_array_newCreate indexed array with headerx0 = capacity, x1 = elem_sizex0 = array ptr
__rt_array_clone_shallowClone indexed array storage for copy-on-write splitting, retaining nested heap children as neededx0 = arrayx0 = new array
__rt_array_to_mixedConvert an indexed array’s live slots to boxed Mixed cells and stamp the array metadata as mixedx0 = arrayx0 = same array
__rt_array_ensure_uniqueSplit a shared indexed array before mutationx0 = arrayx0 = unique array
__rt_array_growEnsure uniqueness, double array capacity, copy elements, free old unique storagex0 = arrayx0 = new array
__rt_array_free_deepFree array storage and release nested heap-backed elementsx0 = array
__rt_array_unionBuild PHP indexed-array union: left numeric keys win, only missing right suffix keys are appendedx0 = left array, x1 = right arrayx0 = result array
__rt_array_hash_unionBuild PHP indexed+associative union by converting left indexes to integer hash keys before appending missing right entriesx0 = left array, x1 = right hashx0 = result hash
__rt_array_push_intAppend int to array (grows if needed)x0 = array, x1 = valuex0 = array
__rt_array_push_refcountedincref borrowed heap payload, then append it as an 8-byte array elementx0 = array, x1 = heap ptrx0 = array
__rt_array_push_strPersist string + append to array (grows if needed)x0 = array, x1/x2 = strx0 = array
__rt_sort_int / __rt_rsort_intIn-place sort ascending or descendingx0 = array
__rt_mixed_sort_require_scalarsGuard sort() / rsort() on a runtime-typed (Array(Mixed)) array: walks every element first and terminates with Fatal error: sorting Mixed arrays containing non-scalar values is not supported for a nested array, object, resource, or boxed callable, so an unsupported container is refused instead of ordered wronglyx0 = array
__rt_str_persistCopy string from concat_buf to heap (skips .data/heap)x1/x2 = strx1/x2 = heap str

Common copy-producing array/hash routines now also have dedicated _refcounted siblings for nested heap-backed payloads. These variants retain borrowed values before pushing or inserting them into freshly allocated arrays/hash tables, covering array literals with spreads plus array_merge, array_chunk, array_slice, array_reverse, array_pad, array_splice, array_diff, array_intersect, array_filter, array_fill, array_combine, and array_fill_keys.

Refcounted siblingWhat it does
__rt_array_reverse_refcountedReverse an indexed array while retaining nested heap-backed elements
__rt_array_merge_refcountedMerge indexed arrays that carry nested heap-backed payloads
__rt_array_slice_refcounted / __rt_array_splice_refcountedSlice or splice while retaining nested heap-backed payloads
__rt_array_fill_refcounted / __rt_array_fill_keys_refcountedBuild filled arrays/hashes from borrowed heap-backed values
__rt_array_pad_refcountedPad an array with retained heap-backed values
__rt_array_diff_refcounted / __rt_array_intersect_refcountedSet-style comparisons that keep nested heap-backed values alive
__rt_array_combine_refcountedCombine key/value arrays into a hash while retaining heap-backed values
__rt_array_chunk_refcountedSplit an array into retained heap-backed chunks
__rt_array_filter_refcountedFilter an array of heap-backed elements without dropping borrowed payloads; an optional third argument carries a captured-closure environment
__rt_array_merge_into_refcountedAppend one indexed array into another in-place while retaining nested heap-backed elements

Hash table (for associative arrays)

RoutineWhat it doesInputOutput
__rt_hash_fnv1aFNV-1a hash of stringx1/x2 = stringx0 = hash
__rt_hash_normalize_keyNormalize PHP string array keys, converting integer-form numeric strings to integer keysx1/x2 = string keyx1/x2 = normalized key
__rt_hash_key_hashHash a normalized int/string array keyx1/x2 = normalized keyx0 = hash
__rt_hash_key_eqCompare normalized int/string array keysx1/x2, x3/x4 = keysx0 = equal flag
__rt_hash_newCreate hash tablex0 = capacity, x1 = coarse value-type summaryx0 = hash ptr
__rt_hash_clone_shallowClone hash storage for copy-on-write splitting, re-persisting keys and retaining nested heap values as neededx0 = hashx0 = new hash
__rt_hash_ensure_uniqueSplit a shared hash table before mutationx0 = hashx0 = unique hash
__rt_hash_growDouble hash table capacity, rehash all entriesx0 = hashx0 = new hash
__rt_hash_setInsert/update (grows at 75% load)x0=hash, x1/x2=normalized key, x3/x4=value, x5=value_tagx0 = hash
__rt_hash_appendAppend with PHP’s next automatic integer key (largest existing int key + 1, or 0), then delegate to __rt_hash_setx0=hash, x3/x4=value, x5=value_tagx0 = hash
__rt_hash_insert_ownedReinsert an already-owned key/value pair during hash growthx0=hash, x1/x2=normalized key, x3/x4=value, x5=value_tagx0 = hash
__rt_hash_getLook up value by keyx0=hash, x1/x2=normalized keyx0=found, x1=val_lo, x2=val_hi, x3=value_tag
__rt_hash_unsetRemove one key for unset($hash[$key]): copy-on-write split, probe like __rt_hash_get, release the owned key/value payloads, tombstone the slot (so probe chains stay intact), and unlink it from the insertion-order chain; missing keys are a no-opx0=hash, x1/x2=normalized keyx0 = (possibly cloned) hash
__rt_hash_spreadFlatten a source hash into a destination hash with PHP [...$src] spread semantics: integer keys are re-sequenced from the destination’s next automatic key, string keys are preserved, later operands overwrite on collision, and each value is re-owned (strings persisted, refcounted payloads retained)x0=dest hash, source hashx0 = (possibly reallocated) dest hash
__rt_hash_iter_nextIterate to next entry in insertion orderx0=hash, x1=cursorx0=next cursor, x1/x2=key, x3/x4=value, x5=value_tag
__rt_hash_unionBuild PHP associative-array union: left duplicate keys win, missing right entries append in insertion orderx0=left hash, x1=right hashx0=result hash
__rt_hash_array_unionBuild PHP associative+indexed union by cloning the left hash and appending right indexes absent from the shared key spacex0=left hash, x1=right arrayx0=result hash
__rt_hash_countCount occupied entriesx0=hashx0=count
__rt_hash_free_deepFree a hash table plus owned keys and nested heap-backed valuesx0=hash
__rt_hash_to_mixedCopy-on-write a hash, then widen each entry payload into a boxed Mixed cell so by-reference foreach can alias a stable pointer slotx0=hashx0 = same hash
__rt_mixed_from_valueBox a tagged payload into a heap-allocated mixed cellx0=value_tag, x1=value_lo, x2=value_hix0 = mixed cell
__rt_mixed_write_stdoutPrint a boxed mixed value by inspecting its inner tagx0 = mixed cell

__rt_hash_iter_next uses a small cursor protocol rather than a raw slot index: 0 starts from the hash header’s head, positive cursors encode slot_index + 1, -2 marks the post-tail state after yielding the final entry, and -1 means iteration is exhausted.

See Memory Model for the hash table memory layout.

Array manipulation

RoutineWhat it does
__rt_array_key_existsCheck if integer key is in bounds
__rt_warn_undefined_array_key_intEmit PHP’s Undefined array key warning for a missing integer key (warning-only; caller still supplies the null fallback)
__rt_array_searchLinear search for value in indexed array
__rt_array_reverseReverse element order
__rt_array_sum / __rt_array_productSum/product of all elements
__rt_array_shift / __rt_array_unshiftRemove/add at beginning
__rt_array_mergeConcatenate two indexed arrays into a new array
__rt_array_merge_intoAppend all elements from source array into dest array (in-place)
__rt_array_slice / __rt_array_spliceExtract slices and remove splice windows from indexed arrays
__rt_array_splice_strThe array_splice() removal for indexed string arrays, whose payload slots are 16-byte {pointer, length} pairs rather than the 8-byte slots the other splice helpers move. The removed strings are MOVED into the result array: an indexed string array owns its persisted bytes exclusively, so retaining them would double free and copying them would leak
__rt_array_splice_insert / _refcounted / _boxed / _unboxed / _strWrite array_splice()’s $replacement into the gap the removal opened, growing the destination first. The five variants differ in what one replacement slot becomes: copied verbatim, retained, wrapped in a fresh boxed Mixed cell, read back out of one as a plain integer, or duplicated with __rt_str_persist into a 16-byte string slot
__rt_array_to_hash_unique / __rt_hash_to_hash_uniquearray_unique() for indexed and associative sources: dedupe by value through a second hash while preserving each survivor’s original key (an indexed source therefore returns a sparse hash)
__rt_array_diff / __rt_array_intersectSet difference/intersection by value
__rt_array_diff_key / __rt_array_intersect_keySet operations by key
__rt_array_flipSwap indexed integer values into associative-array keys
__rt_array_flip_stringSwap indexed string values into associative-array keys, normalizing numeric-string keys
__rt_array_combineCombine key array + value array → AssocArray
__rt_array_fill / __rt_array_fill_keysCreate filled arrays
__rt_array_chunk / __rt_array_padChunk/pad arrays
__rt_array_columnExtract column from array of assoc arrays (int values)
__rt_array_column_refExtract column of retained heap-backed values (arrays / hashes / objects)
__rt_array_column_strExtract column from array of assoc arrays (string values)
__rt_array_column_mixedExtract column values as boxed Mixed cells for heterogeneous input payloads
__rt_rangeGenerate integer range array
__rt_shuffle / __rt_array_randRandomize order / pick random
__rt_random_u32 / __rt_random_uniform / __rt_random_u64 / __rt_random_uniform64Target-aware random primitives used by rand(), random_int(), shuffle(), and array_rand()
__rt_asort / __rt_arsortSort an indexed array by value, ascending or descending
__rt_hash_ksort / __rt_hash_krsortSort an associative array by key, ascending or descending
__rt_hash_asort / __rt_hash_arsortSort an associative array by value while preserving keys, ascending or descending
__rt_hash_sort_linksShared engine behind the four hash sorts: an allocation-free, stable bottom-up merge sort with O(n log n) comparisons that relinks the table’s prev/next/head/tail chain, so buckets never move, key/value association is preserved, and no refcount changes
__rt_hash_sort_compare_entriesReads and compares the heads of two merge runs with exact SORT_REGULAR key semantics or PHP’s general value comparison table
__rt_key_compare_regular / __rt_key_compare_exact_decimal_integers / __rt_key_parse_i64_decimalKey-comparison family behind the hash sorts: SORT_REGULAR key ordering with PHP’s exact numeric-string rules, including overflow-safe decimal-integer comparison
__rt_hash_sort_tripleReads a hash entry’s key or value as a __rt_php_compare (tag, lo, hi) triple, peeling boxed Mixed cells
__rt_natsort / __rt_natcasesortNatural-order sort, case-sensitive or case-insensitive
__rt_array_mapApply callback to each scalar element, return new array; an optional third argument carries a captured-closure environment for generated callback wrappers
__rt_array_map_strApply callback to each scalar or string element and return a string array; an optional third argument carries a captured-closure environment
__rt_array_map_str_ownedApply a descriptor-wrapper callback that returns owned strings and transfer those strings directly into the result array
__rt_array_map_mixedApply a descriptor-backed callback that returns owned boxed Mixed cells and store them directly into a newly allocated result array
__rt_array_filterFilter scalar elements where callback returns truthy; an optional third argument carries a captured-closure environment
__rt_array_reduceReduce an indexed array of 8-byte payload slots to a single value via callback; an optional fourth argument carries a captured-callback environment
__rt_array_reduce_strReduce an indexed string array’s 16-byte [ptr][len] slots into one integer accumulator, passing each element to the callback as a pointer/length pair; an optional fourth argument carries a captured-callback environment
__rt_array_walkCall callback on each element (side-effects); an optional third argument carries a captured-callback environment
__rt_usortSort an indexed array of 8-byte payload slots using a user comparison callback; an optional third argument carries a captured-callback environment
__rt_usort_strStable insertion sort over an indexed string array’s 16-byte [ptr][len] slots using a user comparison callback that receives both strings as pointer/length pairs; an optional third argument carries a captured-callback environment

Reference counting

RoutineWhat it doesInputOutput
__rt_increfIncrement reference count (safe with null/non-heap pointers)x0 = user pointer
__rt_decref_anyRelease any heap-backed value by inspecting the uniform heap-kind tagx0 = pointer
__rt_decref_arrayDecrement refcount, deep-free indexed array if zerox0 = array pointer
__rt_decref_hashDecrement refcount, free hash table if zerox0 = hash pointer
__rt_decref_mixedDecrement refcount, deep-free mixed cell if zerox0 = mixed pointer
__rt_decref_objectDecrement refcount, free object if zerox0 = object pointer
__rt_gc_note_child_refAdd one transient incoming edge to a heap child during cycle countingx0 = child pointer
__rt_gc_mark_reachableRecursively mark array/hash/object blocks reachable from external rootsx0 = pointer
__rt_gc_collect_cyclesRun the targeted cycle collector over heap-backed arrays/hashes/objects
__rt_mixed_free_deepFree a mixed cell and release any nested heap-backed payload; for tag-9 resources, dispatch the kind-specific destructor (kind 1 close, kind 2 __rt_hash_ctx_free, kind 3 __rt_pclose, kind 4 __rt_closedir)x0 = mixed pointer
__rt_object_free_deepFree an object and release heap-backed properties using runtime/class metadatax0 = object pointer

Refcounts are stored as a 32-bit value in the uniform 16-byte heap header, at [user_ptr - 12]. Each heap allocation starts with refcount 1. When a reference is shared (e.g., assigned to another variable or passed to a function), __rt_incref bumps it. When the reference goes away, __rt_decref_any can dispatch through the uniform heap-kind tag to the concrete string/array/hash/object/mixed release path. Runtime-thrown Throwable payloads carry the dedicated heap kind 6, which __rt_decref_any and __rt_object_free_deep accept and route through the same object release path (issue #448). Arrays, hashes, objects, and boxed mixed cells still use ordinary reference counting first, but when a decref sees a container/object graph that can contain nested heap-backed values, the runtime can invoke __rt_gc_collect_cycles to clear transient metadata, count heap-only incoming edges, mark externally reachable blocks, and deep-free the remaining unreachable array/hash/object/mixed island.

Loose-equality routines

Source: src/codegen_support/runtime/compare/

PHP’s == is decided at run time whenever the static operand types do not settle it: two boxed mixed cells, two arrays, or two objects. Three mutually recursive helpers implement PHP 8’s comparison table; the backend’s lower_loose_eq fallback boxes both operands and calls the first one.

RoutineWhat it doesInputOutput
__rt_mixed_loose_eqPHP == between two boxed mixed values, entering at recursion depth 0x0/x1 = mixed pointersx0 = 0/1
__rt_mixed_loose_eq_dSame, carrying an explicit recursion depth so the walkers stay reentrantx0/x1 = mixed pointers, x2 = depthx0 = 0/1
__rt_mixed_array_loose_eqEqual counts plus, for every key of the left array, the same key on the right with a loosely equal value (order-independent)x0/x1 = boxed arrays, x2 = depthx0 = 0/1
__rt_obj_loose_eqSame instance, or same runtime class id with every descriptor property loosely equalx0/x1 = object pointers, x2 = depthx0 = 0/1

Rule order inside __rt_mixed_loose_eq is load-bearing: a bool operand coerces both sides first, then null (converted to "" against a string and to bool otherwise), then containers (an array equals only another array), then objects, then strings, then a numeric comparison. Same-tag int/resource/callable payloads compare word-for-word so large integers keep full precision.

Both operands stay borrowed. The array walker reads elements through __rt_mixed_array_get and the object walker reads properties through __rt_obj_prop_value; both return OWNED cells, and both walkers release them after each comparison. Key presence is probed (__rt_hash_get, or the list bounds for tag-4 arrays) before a value is read, because a missing key and a stored null are otherwise indistinguishable.

The depth argument caps recursion (MAX_LOOSE_EQ_DEPTH): a cyclic object/array graph reports “not equal” instead of running the stack out. PHP instead raises Nesting level too deep - recursive dependency?, which the runtime has no unwind path for from a leaf helper.

System routines

Source: src/codegen_support/runtime/system/ (46 top-level files plus date/, strtotime/, json_validate/, json_decode_mixed/, json_encode_str/, and unserialize/ subdirectories; 84 files recursively)

__rt_build_argv — Build $argv array

File: system/build_argv.rs

At program start, the OS passes argc (argument count) in x0 and argv (pointer to C string pointers) in x1. This routine:

  1. Creates a new string array
  2. For each C string pointer in argv: measures the string length (scan for null byte), pushes ptr+len into the array
  3. Returns the array pointer

Core system routines

RoutineWhat it doesInputOutput
__rt_timeGet current Unix timestamp via gettimeofday syscallx0 = seconds since epoch
__rt_microtimeGet current time as float seconds via gettimeofday syscalld0 = seconds.microseconds
__rt_getenvGet one environment variable via libc getenv(), copying the value out of the environment block so the caller can own itx1/x2 = name stringPresent: x1/x2 = owned value string (non-null even when empty); unset: a null pointer, which the caller boxes as PHP false
__rt_getenv_allWalk the live environ and build the whole environment as a string-keyed hash. Splits each entry at its first =, since a value may contain morex0 = hash pointer
__rt_php_unameRead target runtime system information via libc uname(); supports PHP modes a, s, n, r, v, and mx1/x2 = mode stringx1/x2 = selected uname string
__rt_shell_execExecute shell command and capture output via libc popen()/pclose()x1/x2 = command stringx1/x2 = output string

Call-stack overflow guard

File: system/stack_guard.rs

Unbounded recursion would otherwise run the stack pointer off the end of the mapping and kill the process with a raw SIGSEGV. Two runtime helpers plus one word of state turn that into a controlled fatal on every supported target.

RoutineWhat it doesInputOutput
__rt_stack_limit_initMeasure the running stack once and publish the lowest address compiled prologues may reachwrites _stack_limit and _stack_limit_main
__rt_stack_overflowWrite Fatal error: Maximum call stack size reached. Infinite recursion? to stderr, then escape an active cdylib boundary or exit a standalone process with status 255does not return

__rt_stack_limit_init calls getrlimit(RLIMIT_STACK, …) — resource number 3 on both Linux and macOS — and publishes entry_sp - (min(rlim_cur, 64 MiB) - 32 KiB). The cap absorbs RLIM_INFINITY; the 32 KiB reserve is the headroom a guarded frame may still consume before the next guarded call (outgoing stack arguments, __rt_* helper frames, and their libc calls). When getrlimit fails, reports an implausibly small limit, or the subtraction would wrap, the routine publishes zero instead, and zero disables the guard for the whole process.

The process-entry prologue calls it once, after argc/argv have been stored to globals (it is an ordinary call and clobbers the argument registers). Under --web the call sits in the process-entry stub, before the workers are forked, so every worker inherits a floor that matches its own stack. A cdylib has no process entry, so elephc_init() calls the same helper before the host enters exported PHP code; stack exhaustion then unwinds to the export boundary as ELEPHC_STATUS_RUNTIME_FAILURE instead of terminating the host.

Two globals hold the state:

SymbolMeaning
_stack_limitLowest stack address the currently running context may reach; 0 disables the guard
_stack_limit_mainThe OS-thread floor, remembered so __rt_fiber_switch can restore it

Fibers and generators run on their own 256 KiB mmap’d coroutine stack, which has nothing to do with the OS-thread stack, so __rt_fiber_switch swaps _stack_limit along with the exception and cleanup chain heads: switching into a fiber publishes stack_base + guard page + reserve, and switching back to the main context restores _stack_limit_main. A fiber whose stack allocation failed publishes zero, leaving the guard inert rather than comparing against a nonsensical address.

The check itself lives in every compiled function prologue — see The codegen.

PCNTL routines

Files: system/pcntl.rs, system/pcntl_data.rs

The elephc-pcntl bridge owns the syscalls; these helpers are the adapters that translate its stable C-ABI records into PHP values and drive PHP-visible signal handlers from the compiled side. The bridge never sees a PHP value and the runtime never declares a target’s struct rusage or siginfo_t layout — the record shapes are elephc’s own (ElephcPcntlRUsage is a fixed 17-word block), which is what keeps one bridge binary correct on macOS and Linux.

RoutineWhat it does
__rt_pcntl_rusage_arrayTurn an ElephcPcntlRUsage record into PHP’s pcntl_wait() resource-usage array
__rt_pcntl_siginfo_arrayTurn a captured siginfo snapshot into the array a handler’s $siginfo parameter receives
__rt_pcntl_dispatch_pendingDrain the pending-signal queue and invoke each registered PHP handler, replaying delivery counts preserved through a self-pipe overflow
__rt_pcntl_async_dispatch_preservingThe same drain from an async-signals context, preserving the interrupted frame’s in-flight result
__rt_pcntl_invoke_descriptorCall one handler through its callable descriptor
__rt_pcntl_abort_dispatchStop the drain when a handler throws, so the exception unwinds the interrupted frame instead of the dispatcher
__rt_pcntl_release_handlersRelease the registered handler records after fork() or daemonization, so an inherited copy cannot fire in the child

Compiled and eval’d handlers keep separate queues: a callable descriptor is private to the backend that made it, so the dispatcher routes a pending record only to the backend that registered its handler.

Exception routines

Source: src/codegen_support/runtime/exceptions.rs plus src/codegen_support/runtime/exceptions/ (7 files in the directory)

elephc lowers exceptions with a small runtime layer around _setjmp / _longjmp. Codegen publishes the current exception object into _exc_value, pushes a handler record into _exc_handler_top, and then uses these helpers to unwind, match catch clauses, and resume control flow through catch / finally.

RoutineWhat it doesInputOutput
__rt_exception_cleanup_framesWalk the activation-record stack, run per-frame cleanup callbacks, and stop at the frame that should survive the catchx0 = surviving activation record
__rt_exception_matchesCheck whether the active exception matches a catch target by class id or interface idx0 = exception object, x1 = target id, x2 = 0 for class / 1 for interfacex0 = 1 if it matches, 0 otherwise
__rt_instanceof_lookupResolve a dynamic class-string instanceof target through emitted case-insensitive class/interface metadatax1/x2 = target stringx0 = found flag, x1 = target id, x2 = 0 class / 1 interface
__rt_instanceof_invalid_targetAbort when a dynamic instanceof target is neither a string nor an objectdoes not return
__rt_class_implements_interfaceTest class metadata against an interface id for dynamic class-string checks without an object instancex0 = class id, x1 = interface idx0 = 1 if implemented, 0 otherwise
__rt_throw_currentUnwind to the nearest active handler or print the fatal uncaught-exception message and exitreads _exc_value, _exc_handler_top, _exc_call_frame_topdoes not return normally
__rt_rethrow_currentRe-enter the ordinary throw path with the currently active exceptionnone (uses global exception state)does not return normally

The fatal uncaught-exception path tail-jumps to __rt_report_uncaught_exception, which reads the published _exc_value and writes Fatal error: Uncaught <Class>: <message> in <file>:<line> to stderr before exiting with status 255. The class name comes from _class_name_entries, the message from payload offsets 8/16, the line from the payload’s creation-line slot (THROWABLE_CREATION_LINE_OFFSET, formatted through __rt_itoa), and the file from _script_source_file. An empty message drops the ": " separator, and a zero line drops the whole in <file>:<line> suffix, both matching reference PHP.

Codegen guards in codegen::lower_inst::exceptions have their OWN uncaught path: they write a fatal message baked at emit time and exit before the throwable is ever allocated, so they never reach this helper. They share UNCAUGHT_EXIT_STATUS with it so the status a script observes does not depend on which kind of exception escaped.

The runtime also resets the concat-buffer cursor before the final longjmp, so partially built string state from the throwing frame does not leak into the resumed catch/finally code.

Date/time routines

Files: system/date/, system/date_data.rs, system/mktime.rs, system/microtime.rs, system/hrtime.rs, system/getdate.rs, system/localtime.rs, system/checkdate.rs, system/date_default_timezone.rs, system/strtotime/

RoutineWhat it doesInputOutput
__rt_date / __rt_gmdateFormat a Unix timestamp using PHP date format characters (Y, m, d, H, i, s, l, F, T, e, O, P, …). __rt_date decomposes with libc localtime(), __rt_gmdate with gmtime() (UTC); both share the formatter and the static _day_names/_month_names tables. The T token reports "GMT" on the gmdate pathx1/x2 = format string, x0 = timestampx1/x2 = formatted string
__rt_mktime / __rt_gmmktimeCreate a Unix timestamp from date components (hour, minute, second, month, day, year). Populates a tm struct and calls libc mktime() (local) or timegm() (UTC)x0-x5 = h, m, s, mon, day, yearx0 = Unix timestamp
__rt_strtotimeParse trimmed date/time strings through strategy emitters: ISO dates/datetimes (iso_date), M/D/Y slash dates (slash_date), textual dates like 15 January 2020 (textual_date), @<timestamp> epoch forms (epoch), first/last day of … and first/last <weekday> of … phrases (first_last_day), time-only forms, bare keywords (now, today, tomorrow, yesterday, midnight, noon), relative offsets (+1 day, 3 months ago, a/an <unit> article forms), and named weekdays with next / last / this. Successful paths populate a tm struct and call libc mktime(); malformed input returns the i64::MIN failure sentinelx1/x2 = date stringx0 = Unix timestamp or sentinel
__rt_getdate / __rt_localtimeDecompose a timestamp into PHP’s getdate() / localtime() associative array via libc localtime(), defaulting the timezone to UTC through __rt_tz_init_utc on first usex0 = timestampx0 = assoc array pointer
__rt_checkdateValidate a Gregorian month/day/year, leap-year awarex0-x2 = month, day, yearx0 = 0/1
__rt_microtime / __rt_hrtimeCurrent wall-clock (gettimeofday) / monotonic (clock_gettime) time, as a float/string or [sec, nsec] arrayflagresult
__rt_date_default_timezone_get / __rt_date_default_timezone_set / __rt_tz_init_utcRead / set the process default timezone (putenv("TZ=…") + tzset); __rt_tz_init_utc lazily defaults it to UTC like PHP— / x1/x2 = id

DateTimeZone introspection (getLocation(), getTransitions(), listAbbreviations()) is backed by the bundled elephc-tz workspace crate, which bakes PHP timelib’s IANA timezone tables into committed data files and exposes them through the elephc_tz_location / elephc_tz_transitions / elephc_tz_abbreviations ABI symbols. Like the elephc-tls and elephc-phar bridges it is linked only into programs that use it. The offset/DST resolution used by date()/gmdate() themselves is delegated to libc (localtime/gmtime/tzset).

JSON routines

Files: system/json_data.rs, system/json_depth.rs, system/json_throw_error.rs, system/json_last_error_msg.rs, system/json_validate/, system/json_decode.rs, system/json_decode_mixed/, system/json_encode_bool.rs, system/json_encode_null.rs, system/json_encode_float.rs, system/json_encode_str/, system/json_encode_array_int.rs, system/json_encode_array_str.rs, system/json_encode_array_dynamic.rs, system/json_encode_assoc.rs, system/json_encode_mixed.rs, system/json_encode_object.rs, system/json_pretty.rs, plus objects/stdclass.rs for stdClass-specific JSON object encoding.

The json_encode implementation uses type-aware dispatch — the codegen calls a different runtime routine depending on the compile-time type of the value being encoded:

RoutineWhat it doesInputOutput
__rt_json_encode_boolEncode bool as "true" or "false" using static data labelsx0 = 0 or 1x1/x2 = JSON string
__rt_json_encode_nullEncode null as "null" using a static data labelx1/x2 = JSON string
__rt_json_encode_strEncode a string with JSON escaping (quotes, backslashes, control chars)x1/x2 = input stringx1/x2 = JSON string
__rt_json_encode_array_intEncode an integer array as a JSON array (e.g., [1,2,3])x0 = array ptrx1/x2 = JSON string
__rt_json_encode_array_strEncode a string array as a JSON array with quoted elementsx0 = array ptrx1/x2 = JSON string
__rt_json_encode_array_dynamicEncode an indexed array by inspecting its packed runtime value_type tag at runtime (int, string, float, bool, nested array/hash, mixed, or null fallback), caching active JSON flags in a callee-saved register during the walkx0 = array ptrx1/x2 = JSON string
__rt_json_encode_assocEncode an associative array as a JSON object, while tracking PHP list-shape keys during the same hash walk and compacting to JSON array form when applicablex0 = hash ptrx1/x2 = JSON string
__rt_json_encode_floatEncode finite floats and record JSON_ERROR_INF_OR_NAN for INF/NAN, honoring throw and partial-output flagsd0 = floatx1/x2 = JSON string
__rt_json_encode_mixedEncode a boxed mixed payload by unboxing its runtime tag and dispatching to the concrete JSON encoderx0 = mixed ptrx1/x2 = JSON string
__rt_json_encode_objectEncode class objects by consulting per-class JSON descriptors; dispatches JsonSerializable::jsonSerialize() when present, otherwise walks public propertiesx0 = object ptrx1/x2 = JSON string
__rt_json_encode_stdclassEncode the dynamic-property hash backing stdClass, preserving {} for empty instancesx0 = stdClass hash ptrx1/x2 = JSON string
__rt_json_decodeString-only compatibility helper used by string decode paths; trims outer whitespace and unescapes quoted JSON strings including surrogate-aware \uXXXX sequencesx1/x2 = JSON stringx1/x2 = decoded string
__rt_json_decode_mixedChecked structural recursive decoder that returns boxed Mixed cells for null, bool, int, float, string, indexed arrays, associative arrays, and stdClass objects depending on _json_decode_assoc; records syntax/depth/UTF-8/UTF-16 errors plus source offsets and returns 0 on malformed inputx1/x2 = JSON stringx0 = Mixed* or 0
__rt_json_decode_mixed_array_realRecursive array parser used by json_decode_mixed once the outer [ token is knownparser cursor + JSON boundsboxed Mixed array
__rt_json_decode_mixed_object_realRecursive object parser used by json_decode_mixed once the outer { token is known; returns assoc hash or stdClass payload based on decode modeparser cursor + JSON boundsboxed Mixed object/hash
__rt_json_skip_wsShared RFC 8259 whitespace skipper used by json_decode_mixed and its recursive array/object parsers; advances a caller-owned cursor to the next token or caller-supplied limitJSON slice pointer, exclusive limit, cursorupdated cursor
__rt_json_validateStandalone RFC 8259 validator used by json_validate(); scalar validator helpers are also reused by json_decode_mixed for strings and numbersx1/x2 = JSON stringx0 = 1 valid / 0 invalid
__rt_json_depth_enter / __rt_json_depth_exitMaintain _json_active_depth and compare against _json_depth_limit for recursive encode/decode/validate walksglobal JSON statestatus / updated state
__rt_json_set_error_locationConvert a decoder target pointer into one-based line/column data relative to _json_error_source_ptrtarget pointerupdates _json_error_location_active, _json_error_line, _json_error_column
__rt_json_error_messageBuild the current JSON error message, appending the PHP 8.6 " near location line:column" suffix when decode location state is activeglobal JSON statex1/x2 = message
__rt_json_throw_errorRecord a JSON error code and construct/throw JsonException when JSON_THROW_ON_ERROR is active, using the shared formatted JSON error messagex0 = JSON_ERROR_* codemay not return
__rt_json_last_error_msgReturn the message string corresponding to _json_last_error through the _json_err_msg_table data table, including decode location suffixes when activeglobal JSON statex1/x2 = message
__rt_json_pretty_push / __rt_json_pretty_pop / __rt_json_pretty_line / __rt_json_pretty_colon_spaceMaintain _json_indent_depth and append PHP-style pretty-print whitespace while each container encoder emits bytes. These helpers are no-ops unless JSON_PRETTY_PRINT is active, avoiding a second buffer walk.current JSON state, x11 write pointer for line/space helpersupdated formatting state / x11 write pointer

Serialization routines

Files: system/serialize.rs, system/unserialize/ (11 Rust modules including mod.rs)

These helpers back PHP’s serialize() / unserialize(). The serializer writes PHP’s exact wire format (N;, b:0;/b:1;, i:<int>;, d:<shortest-round-trip>;, s:<bytelen>:"<raw>";, a:<n>:{...}, and O:<len>:"<class>":<n>:{...}) directly into the concat buffer, reusing __rt_json_ftoa for shortest-round-trip float digits. Object serialization honors __sleep() / Serializable and reuses an object back-reference table so repeated instances emit r:/R: references.

The unserializer keeps the fixed runtime-emission order in unserialize/mod.rs. Shared diagnostics and per-call context lifecycle are separated from target-specific allowed-class policy parsing, allocation-free validation, recursive decoding, and object-storage/key helpers. The two decoder files are cohesive architecture leaves; every surrounding orchestration or support module remains below the repository’s 500-line warning threshold.

RoutineWhat it doesInputOutput
__rt_serialize_valueTag-dispatching serializer for a raw runtime value, appending its wire form to the concat buffervalue tag + payloadx1/x2 = string slice
__rt_serialize_mixedUnbox a boxed Mixed cell (null pointer → N;), then serialize itx0 = mixed cellx1/x2 = string slice
__rt_serialize_indexed_array / __rt_serialize_hashSerialize indexed arrays and hashes as a:<n>:{...}array/hash pointerx1/x2 = string slice
__rt_serialize_object / __rt_serialize_named_prop / __rt_serialize_obj_refSerialize objects (O:/C:), emit one named property entry, and resolve back-referencesobject pointerx1/x2 = string slice
__rt_unserialize_begin / __rt_unserialize_mixed / __rt_unserialize_objectParse a serialized string back into boxed Mixed cells, including nested arrays/hashes and objectsx1/x2 = serialized stringx0 = Mixed* (0 on malformed input)

Regex routines

Files: system/preg_strip.rs, system/pcre_to_posix.rs, system/mb_ereg_match.rs, system/preg_match.rs, system/preg_match_all.rs, system/preg_replace.rs, system/preg_replace_callback.rs, system/preg_split.rs; the embedded shim source is src/native_deps/recipes/pcre2_shim.c.

Generated runtime assembly calls only the versioned elephc_pcre2_v1_compile, elephc_pcre2_v1_exec, and elephc_pcre2_v1_free ABI. The Elephc-owned C shim uses PCRE2’s POSIX wrapper internally, but no regex_t, regmatch_t, regoff_t, or other PCRE2-owned layout crosses the boundary. compile returns an opaque handle and slot count; exec writes fixed 16-byte [start, end] signed-64-bit pairs, initializing unmatched and surplus slots to [-1, -1].

__rt_preg_strip strips PHP-style delimiters and maps supported modifiers (i, m, s, u, U) to shim compile flags. __rt_pcre_to_posix keeps its historic symbol name for compatibility but only materializes a null-terminated pattern. __rt_mb_ereg_match backs mb_ereg_match() through the same shim. The whole regex family is emitted only when the program’s RuntimeFeatures request it. A final-link regex program resolves the managed pcre2 package and links exact verified archives in shim, POSIX, then 8-bit order. Dynamic eval does so only when static regex detection or --with-regex requests the same feature. There is no production system-PCRE2 fallback; compilation itself never installs a package.

RoutineWhat it doesInputOutput
__rt_preg_matchTest if a regex matches the subject string. Compiles the pattern, executes once, freespattern + subject stringsx0 = 1 (match) or 0 (no match)
__rt_preg_match_captureTest once and materialize PHP’s optional $matches array from the shim-reported slot count and fixed offset pairs, omitting trailing unmatched captures while keeping interior unmatched captures as empty stringspattern + subject stringsmatch flag plus matches array pointer
__rt_preg_match_allCount all non-overlapping matches by repeatedly executing the regex with advancing offsetspattern + subject stringsx0 = match count
__rt_preg_replaceReplace all regex matches with a replacement string. Builds the result incrementally in the concat buffer and expands $0..$99 / \0..\99 from the PCRE2 capture vectorpattern + replacement + subjectx1/x2 = result string
__rt_preg_replace_callbackReplace all regex matches using shim-sized fixed-pair capture storage, building an indexed $matches string array, invoking the callback, and appending its string result while preserving concat-buffer state across callback prologuespattern + callback + subjectx1/x2 = result string
__rt_preg_splitSplit the subject string at regex match boundaries using shim-sized fixed-pair capture storage. Applies limit, no-empty, delimiter-capture, and offset-capture flags; dynamic flags return boxed Mixed slots to preserve layoutpattern + subject strings, limit, flagsx0 = array pointer

I/O routines

Source: src/codegen_support/runtime/io/ (121 files)

These routines handle file and filesystem operations through target-aware libc/syscall helpers. PHP strings (pointer + length) must be converted to null-terminated C strings before passing to C or OS APIs — __rt_cstr handles the primary buffer and also emits __rt_cstr2 for routines that need a second simultaneous C string.

The first table covers the file/filesystem core; the subsections after it cover the stream/networking surface emitted from the same directory: stream contexts and metadata, stream filters and user-defined stream wrappers, TCP/Unix/IPv6 sockets, TLS/SSL helpers, FTP/HTTP transfer helpers, hostname/service resolution, phar archives, and var_dump.

RoutineWhat it does
__rt_cstrConvert PHP string (ptr+len) to null-terminated C string
__rt_fopenOpen file via target-aware open() handling, or return -1 after emitting a suppressible warning for open failures and invalid modes
__rt_fgetsRead line from file descriptor
__rt_fgetcRead a single byte from a file descriptor (tail-calls __rt_fread with length 1)
__rt_feofCheck end-of-file flag for a file descriptor
__rt_freadRead N bytes from file descriptor
__rt_readfileOpen a path, stream contents to stdout, and return copied byte count, -1 on read failure, or a false sentinel on open failure
__rt_fpassthruStream the remaining bytes from an existing descriptor to stdout and return copied byte count or -1 on read failure
__rt_flockCall libc flock(), translating PHP’s LOCK_UN constant and exposing would-block state for the optional output parameter
__rt_tmpfileCreate an anonymous temporary file descriptor through mkstemp() plus immediate unlink
__rt_file_get_contentsRead entire file into string, or return a null pointer after emitting a suppressible warning on failure
__rt_file_put_contentsWrite string to file (create/truncate)
__rt_fileRead file into array of lines
__rt_file_exists / __rt_is_file / __rt_is_dirExistence and path-type checks backed by stat()
__rt_is_readable / __rt_is_writableAccess checks backed by access() on real paths
__rt_stat_mode_accessThe wrapper-aware permission predicate behind is_readable() / is_writable() / is_executable() on userspace-wrapper paths: selects exactly one owner/group/world triad from the url_stat()-reported uid/gid against the process identity (getuid/getgid/getgroups)
__rt_filesize / __rt_filemtime / __rt_fileatime / __rt_filectime / __rt_fileperms / __rt_fileowner / __rt_filegroup / __rt_fileinodeStat scalar metadata. Each returns a payload plus a success flag (x1/rdx) so codegen can box PHP false without confusing legitimate zero values — a size of 0 for an empty file, or a timestamp of 0.
__rt_filetype / __rt_is_executable / __rt_is_linkFile type and permission predicates; filetype() uses lstat() so symlinks report "link" and missing paths box as false.
__rt_stat_array / __rt_lstat_array / __rt_fstat_arrayBuild PHP-compatible stat arrays with numeric and string keys, returning a null pointer for codegen to box as false on failure
__rt_unlink / __rt_mkdir / __rt_rmdir / __rt_chdirFilesystem path operations via libc/syscalls
__rt_rename / __rt_copyTwo-path filesystem helpers using dual C-string scratch buffers
__rt_symlink / __rt_linkCreate symbolic or hard links through libc
__rt_readlinkRead a symbolic-link target into a heap-backed string, with null output for PHP false on failure
__rt_linkinfoReturn lstat() device metadata for a link path, or PHP’s -1 failure sentinel
__rt_getcwdGet current working directory
__rt_scandirList directory contents into array
__rt_globPattern-match filenames
__rt_tempnamCreate temporary filename
__rt_fgetcsvParse CSV line from file
__rt_fputcsvWrite CSV line to file
__rt_basename / __rt_dirname / __rt_dirname_levelsCompute path components for basename() / dirname() including repeated parent traversal
__rt_fnmatchMatch shell-style path globs with PHP/libc-compatible flag bits for the selected target
__rt_realpathCanonicalize an existing path, returning a null pointer on failure so codegen can box PHP false
__rt_pathinfo_str / __rt_pathinfo_arrayReturn one pathinfo() component for component flags, or build the associative-array PATHINFO_ALL shape
__rt_parse_urlScan PHP-compatible URL components, persist strings, and box either a selected component or a Mixed-valued associative hash
__rt_chmod / __rt_chown / __rt_lchown / name-resolving variantsFile ownership and mode modification helpers, including symlink-aware ownership updates
__rt_lookup_passwd_uid / __rt_lookup_group_gidResolve local user/group names by scanning /etc/passwd and /etc/group without calling NSS, so static Linux binaries do not require glibc NSS modules at runtime
__rt_umask / __rt_ftruncateProcess umask and file truncation helpers
__rt_fsync / __rt_fflush / __rt_fdatasyncFile descriptor flush helpers; fflush() maps to fsync() because elephc has no userspace stdio buffer
__rt_touchCreate missing files and update access/modification timestamps

Stream and socket routines

RoutineWhat it does
__rt_stream_socket_client / __rt_stream_socket_client_v6Open TCP client connections (IPv4/IPv6) with timeout handling
__rt_stream_socket_server / __rt_stream_socket_server_v6Bind and listen on TCP server sockets (IPv4/IPv6)
__rt_unix_socket_client / __rt_unix_socket_serverUnix-domain socket client/server endpoints
__rt_stream_socket_acceptAccept a pending connection with optional timeout
__rt_stream_socket_pairCreate a connected socket pair
__rt_stream_socket_recvfrom / __rt_stream_socket_sendtoDatagram receive/send with peer-address formatting
__rt_stream_socket_get_nameLocal or remote endpoint name for a socket
__rt_stream_socket_shutdownHalf/full shutdown of a connected socket
__rt_socket_backlog / __rt_apply_socket_bindto / __rt_apply_socket_client_opts / __rt_apply_socket_server_optsSocket-option plumbing for context-driven behavior
__rt_stream_selectstream_select() over descriptor arrays via poll()/select()
__rt_stream_set_blocking / __rt_stream_set_timeoutPer-descriptor blocking mode and read timeout
__rt_stream_get_contents / __rt_stream_get_contents_bounded / __rt_stream_get_lineBulk and line-delimited stream reads
__rt_stream_copy_to_streamCopy bytes between two descriptors
__rt_stream_get_meta_dataBuild the stream_get_meta_data() associative array
__rt_stream_context_set_option_4Store context options consumed by the open/transfer helpers
__rt_stream_isattyTTY detection for descriptors
__rt_data_streamdata:// stream payload decoding
__rt_get_ssl_peer_namePeer-certificate name lookup for TLS streams

Networking and transfer routines

RoutineWhat it does
__rt_http_open / __rt_https_openOpen http:// / https:// streams (TLS via the elephc-tls bridge)
__rt_http_build_requestAssemble the HTTP request from context options (method, headers, body, request_fulluri)
__rt_http_fire_notificationInvoke the stream-notification callback during transfers
__rt_ftp_open / __rt_ftp_send_recv / __rt_ftp_parse_pasvftp:// stream support (control dialog, passive-mode parsing)
__rt_resolve_host / __rt_resolve_host_v6Hostname resolution to IPv4/IPv6 addresses
__rt_gethostbyname / __rt_gethostbyaddr / __rt_gethostnameHost lookup builtins
__rt_getprotobyname / __rt_getprotobynumber / __rt_protoent_loadProtocol-database lookups backed by /etc/protocols
__rt_getservbyname / __rt_getservbyportService-database lookups backed by /etc/services

User stream wrappers and filters

Userspace streamWrapper classes registered with stream_wrapper_register() dispatch through a vtable of __rt_user_wrapper_* routines (fopen/fread/fwrite/fclose/feof/fseek/ftell/fflush/fstat/ftruncate/flock/set_option/stream_cast, the dir_* family, path_op, and rename), each bridging the synthetic descriptor back to PHP method calls on the wrapper instance. Stream filters use __rt_stream_filter_register, __rt_apply_stream_filter / __rt_apply_user_stream_filter, __rt_stream_filter_attach_user, __rt_resolve_user_filter_id, __rt_user_filter_brigade_invoke, and __rt_user_filter_release_fd to run built-in (zlib.*, bzip2.*, convert.iconv.*, string.*) and user-defined filter chains over stream reads and writes.

Phar archive routines

RoutineWhat it does
__rt_fopen_maybe_phar / __rt_file_get_contents_maybe_pharRoute dynamic phar:// read paths to archive entry reads and write-mode fopen() paths to PHAR write streams, falling through to plain file I/O otherwise
__rt_phar_read_entryLocate and read one entry from a PHAR URL through the elephc-phar bridge, which handles native PHAR, tar, and ZIP containers and authenticates OpenSSL signatures with <archive>.pubkey; missing/invalid keys and a missing bridge fail closed rather than entering the legacy unauthenticated assembly parser
__rt_phar_write_open / __rt_phar_write_open_url / __rt_phar_write_append / __rt_phar_write_finalize / __rt_file_put_contents_maybe_pharBuffer phar:// write entries in bridge-owned descriptor slots, then finalize each through the elephc-phar bridge so native PHAR, tar, and ZIP archives preserve existing entries; runtime-built file_put_contents() and fopen() write URLs call a bridge variant that splits the full phar:// URL; the assembly fallback still emits a single-entry SHA1-signed native archive

var_dump output routines

var_dump() lowering calls a family of __rt_var_dump_array_* routines (int, float, str, bool, mixed) that walk array payloads, __rt_var_dump_hash for associative arrays, and a set of __rt_var_dump_emit_* helpers that print one typed line (int(...), float(...), bool(...), string headers, indexed keys) with the PHP-compatible indentation. The walkers do not issue raw write syscalls: they call __rt_vd_write, a register-preserving shim that routes the bytes through __rt_stdout_write (so output buffering, print_r return-mode capture, and --web capture all see var_dump output) while saving and restoring every register — including the float walker’s pending d0 — that the walkers expect a raw syscall to leave untouched.

The stdout funnel and --web helpers

Every terminal stdout write in a compiled program travels through one indirection, __rt_stdout_write (io/stdout_write.rs). Its branch order is part of the output contract:

  1. print_r return-mode capture — while _print_r_mode is set, append the bytes to _print_r_buf via __rt_pr_append instead of writing
  2. user output-handler guard — while _ob_in_handler is set, discard the bytes entirely (PHP discards output produced inside an ob_start() handler)
  3. output-buffer capture — while the ob_* stack is non-empty (_ob_level > 0), append the bytes to the top output buffer via __rt_ob_append
  4. --web capture — in --web builds only, a non-zero elephc_web_capture flag routes the bytes to elephc_web_write; default worker isolation buffers them, while pool/request isolation frames them onto the handler response stream
  5. plain write(1, ptr, len) syscall — the universal fallback

The --web capture branch is emitted only when compiling with --web, so ordinary binaries never reference the bridge symbols. A few related helpers are always emitted so their EIR calls resolve on every build, with bodies that differ under --web: __rt_php_input (reads the request body for file_get_contents('php://input'), false otherwise) and __rt_http_response_code / __rt_header (call the bridge setters under --web, no-ops otherwise). PHP session support (session_*) is not part of this runtime: it lives in the --web PHP prelude (src/web_prelude.rs) as elephc_web_session_* extern functions provided by the web bridge.

print_r() uses walker helpers that mirror the var_dump family — __rt_print_r_spaces, __rt_print_r_open / __rt_print_r_close, __rt_print_r_int_key / __rt_print_r_str_key, __rt_print_r_value, __rt_print_r_indexed, and __rt_print_r_hash — all writing through __rt_pr_write. Return mode (print_r($value, true)) is backed by three capture helpers in io/print_r_buffer.rs:

RoutineWhat it does
__rt_pr_appendAppend bytes to the 64KB _print_r_buf at _print_r_off, clamping to the remaining capacity so oversized captures truncate instead of overflowing
__rt_pr_writeWalker-facing write wrapper: branch on _print_r_mode between the stdout path and __rt_pr_append
__rt_pr_finishPersist the captured bytes to an owned heap string via __rt_str_persist and reset the capture state

Output buffering (ob_*) routines

Added with 0.26.2, the ob_* builtins are backed by a runtime buffer stack (io/ob_buffer.rs, io/ob_handler.rs, io/ob_status.rs). State lives in fixed data: _ob_level is the nesting depth, and 64-slot parallel arrays (_ob_ptrs / _ob_lens / _ob_caps plus _ob_handler_stubs / _ob_handler_envs / _ob_name_ptrs / _ob_name_lens / _ob_chunk_sizes / _ob_flags / _ob_started) hold each level’s heap-allocated buffer and handler metadata. Buffer capacity is PHP-shaped: 16384 bytes by default, or the page-aligned chunk size + 1 when a chunk size is given, growing by doubling. These helpers are always emitted because __rt_stdout_write, __rt_pr_write, and the process-exit paths reference them unconditionally.

RoutineWhat it does
__rt_ob_start_ex / __rt_ob_startPush a new output buffer with handler stub + env word, chunk size, flags, and persisted display name; __rt_ob_start is the default-handler compatibility wrapper. Calling ob_start() inside a running handler is a fatal error
__rt_ob_appendAppend bytes to the top buffer, growing it as needed; reaching a non-zero chunk-size threshold triggers an auto-flush with the WRITE handler phase
__rt_ob_contentsReturn a persisted copy of the top buffer contents
__rt_ob_length / __rt_ob_levelInteger queries: top buffer’s used byte count (-1 when inactive) / nesting depth
__rt_ob_process_and_writeShared flush/clean core for one slot: run the handler phase, then emit or discard the buffered bytes
__rt_ob_pop_freePop the top buffer, releasing its storage and handler resources
__rt_ob_clean / __rt_ob_end_clean / __rt_ob_flush / __rt_ob_end_flushThe four flags-gated bool-returning mutations, with PHP’s per-operation gating flags, handler phases, and notice texts
__rt_ob_get_clean_pop / __rt_ob_get_flush_popComposite helpers behind ob_get_clean() (silent on no buffer) and ob_get_flush() (PHP notice on no buffer)
__rt_ob_flush_allDrain every still-active buffer at process exit, top-down with the FINAL handler phase, guarded by _ob_flushing against handler-triggered re-entry; gating flags are ignored because PHP force-flushes at shutdown
__rt_ob_apply_handlerUser-handler dispatch core: compute the handler phase (ORing in START on the first run), set _ob_in_handler around the call so handler output is discarded
__rt_ob_result_to_bytesMap a handler’s Mixed result to the replacement bytes: false passes the original through, anything else is cast to string and persisted
__rt_ob_invoke_descriptorInvoke an AOT callable-descriptor handler through its uniform (descriptor, mixed-arg-array) invoker
__rt_ob_eval_trampolineInvoke an eval-registered handler through the installed magician hook (_elephc_eval_ob_handler_fn)
__rt_ob_notice_namedWrite the PHP-parity Failed to ... buffer of NAME (LEVEL) notice
__rt_ob_get_status / __rt_ob_status_entry / __rt_ob_list_handlersBuild the ob_get_status() status hash (simple and full modes) and the ob_list_handlers() name array

Handler stubs use a uniform ABI — stub(env, buf_ptr, buf_len, phase) returns a replaced-flag plus the owned replacement string — where AOT handlers pass a retained callable-descriptor pointer as env and eval handlers pass a magician registry id. All ob_* notices and warnings are ordinary output routed through __rt_stdout_write, so active parent buffers capture them exactly like PHP.

Pointer routines

Source: src/codegen_support/runtime/pointers/ (7 files including mod.rs)

These helpers support the compiler-specific pointer builtins.

RoutineWhat it doesInputOutput
__rt_ptoaFormat a pointer value as a hexadecimal string with 0x prefixx0 = pointer/addressx1/x2 = formatted string
__rt_ptr_check_nonnullAbort with Fatal error: null pointer dereference if the pointer is nullx0 = pointer/addressx0 unchanged on success
__rt_str_to_cstrCopy an elephc string to temporary null-terminated heap storage for a native callx1/x2 = stringx0 = C string pointer
__rt_cstr_to_strCopy a borrowed null-terminated C string back into an owned elephc stringx0 = C string pointerx1/x2 = elephc string
__rt_ptr_read_stringCopy a fixed-length byte range from a raw pointer into an owned elephc stringx0 = pointer, x1 = lengthx1/x2 = elephc string
__rt_ptr_write_stringCopy an elephc string’s bytes into the memory addressed by a raw pointerx0 = pointer, x1/x2 = string

zval bridge routines

Source: src/codegen_support/runtime/zval/ (11 files including mod.rs)

These helpers back the PHP zval bridge extension: they convert elephc runtime values (boxed Mixed cells, indexed/hash arrays, strings) into PHP zval / zend_string / zend_array structures and back. String and array children are freshly allocated through __rt_heap_alloc, so a produced zval owns independent PHP-shaped storage.

RoutineWhat it does
__rt_zval_packConvert a boxed Mixed cell into a 16-byte zval heap block
__rt_zval_pack_array_packed / __rt_zval_pack_array_hashBuild zend_array storage from an indexed array / associative hash
__rt_zval_unpack / __rt_zval_unpack_arrayConvert a zval (or zend_array) back into elephc runtime values
__rt_zval_string_newAllocate a zend_string from an elephc string
__rt_zval_djbx33aThe DJBX33A hash used for zend_string key hashing
__rt_zval_typeReport a zval’s type tag
__rt_zval_free / __rt_zval_free_array / __rt_zval_free_childrenRelease packed zval storage, including nested children

Buffer routines

Source: src/codegen_support/runtime/buffers/ (8 files including mod.rs)

These helpers support the compiler-specific buffer<T> hot-path data type. Public Buffer values are opaque (generation:u32 << 32) | descriptor_index:u32 handles. The runtime resolves each non-null handle through a 4096-slot static descriptor registry and validates its active marker and generation before exposing payload metadata. Payload bytes remain a separate compiler-heap allocation.

RoutineWhat it doesInputOutput
__rt_buffer_resolveValidate handle index, non-zero generation, active state, and exact descriptor generationx0 = opaque buffer handlex0 = static descriptor address
__rt_buffer_newReuse or claim a descriptor, allocate and exactly zero length * stride payload bytes, then publish the new handlex0 = element count, x1 = element stridex0 = opaque buffer handle
__rt_buffer_freeInvalidate a resolved descriptor, recycle it unless its u32 generation is saturated, then release the detached payloadx0 = opaque buffer handle
__rt_buffer_lenResolve the handle and read the logical element count from descriptor offset 8x0 = opaque buffer handlex0 = length
__rt_buffer_bounds_failAbort with Fatal error: buffer index out of boundsdoes not return
__rt_buffer_new_size_failAbort with Fatal error: buffer_new() length is negative or exceeds the maximum buffer size when length * stride is invaliddoes not return
__rt_buffer_registry_exhaustedAbort with Fatal error: buffer registry exhausted when no descriptor slot can be issueddoes not return
__rt_buffer_use_after_freeAbort with Fatal error: use of buffer after buffer_free()does not return

Mixed-type helpers

RoutineWhat it doesInputOutput
__rt_mixed_cast_intUnbox a mixed cell and cast to integerx0 = mixed cell pointerx0 = integer
__rt_mixed_cast_boolUnbox a mixed cell and cast to booleanx0 = mixed cell pointerx0 = 0 or 1
__rt_mixed_cast_floatUnbox a mixed cell and cast to floatx0 = mixed cell pointerd0 = float
__rt_mixed_cast_stringUnbox a mixed cell and cast to stringx0 = mixed cell pointerx1/x2 = string
__rt_mixed_cast_arrayThe (array) cast tag dispatch: arrays keep their COW payload, objects project to property hashes, null yields an empty array, and every other tag wraps into a one-element Mixed arrayx0 = mixed cell pointerx0 = array pointer
__rt_mixed_instanceofUnbox a mixed cell and test object payloads against class/interface metadatax0 = mixed cell pointer, x1 = target id, x2 = 0 class / 1 interfacex0 = 0 or 1
__rt_instanceof_lookupResolve a dynamic class-string target against emitted class/interface name metadatax1/x2 = stringx0 = found, x1 = target id, x2 = 0 class / 1 interface
__rt_mixed_is_emptyCheck emptiness of a mixed cell (PHP semantics)x0 = mixed cell pointerx0 = 0 or 1
__rt_mixed_strict_eqCompare two mixed cells by tag and valuex0, x1 = mixed pointersx0 = 0 or 1
__rt_mixed_unboxExtract the raw payload from a mixed cellx0 = mixed cell pointerx0/x1/x2 depending on type
__rt_mixed_countCount boxed indexed arrays and hashes, returning zero for non-countable payloadsx0 = mixed cell pointerx0 = count
__rt_iterable_write_stdoutPrint iterable arrays and hashes as PHP’s "Array" display stringx0 = iterable heap pointer
__rt_iterable_unsupported_kindAbort when runtime iterable dispatch sees an unsupported heap kinddoes not return
__rt_hash_may_have_cyclic_valuesScan hash entries to check if any contain refcounted childrenx0 = hash pointerx0 = 0 (scalar-only) or 1 (has cycles)
__rt_match_unhandledAbort with Fatal error: unhandled match casedoes not return

Object and stdClass routines

Source: src/codegen_support/runtime/objects/ (15 files)

These helpers support stdClass, json_decode() object results, boxed Mixed property/index access, object destructor dispatch, and dynamic new $name() instantiation. stdClass instances use a compact [class_id][hash_ptr] payload, with dynamic properties stored in a hash of boxed Mixed values.

RoutineWhat it doesInputOutput
__rt_object_to_hashProject an object’s properties into a string-keyed hash — backs get_object_vars() and (array) object casts through serialize descriptors, with declaring-class protected/private filtering and __PHP_Incomplete_Class handlingobject pointer + modehash pointer
__rt_throw_object_not_arrayRaise PHP’s catchable Cannot use object of type X as arrayobject pointerdoes not return
__rt_new_by_nameInstantiate a class by its textual name through the _classes_by_name table (case-insensitive __rt_strcasecmp lookup), allocating and zeroing the object payloadclass name stringobject pointer, or 0 (null) on miss
__rt_call_object_destructorLook up the object’s __destruct in the class_id-indexed _class_destruct_ptrs table and invoke it with $this borrowed before storage is released; guarded against re-entryobject pointer
__rt_stdclass_newAllocate an empty stdClass object with hash-backed dynamic property storagestdClass class id from runtime dataobject pointer
__rt_stdclass_from_hashWrap a decoded JSON object hash in a stdClass instancehash pointerobject pointer
__rt_stdclass_getRead a dynamic property and return a boxed Mixed value, or Mixed(null) when missingobject pointer + property stringboxed mixed payload
__rt_stdclass_setStore a boxed Mixed value into a dynamic property hashobject pointer + property string + boxed value
__rt_mixed_property_getUnbox a Mixed object payload and dispatch stdClass property readsboxed mixed + property stringboxed mixed payload
__rt_mixed_property_setUnbox a Mixed object payload and dispatch stdClass property writesboxed mixed + property string + boxed value
__rt_mixed_array_getUnbox Mixed array/hash/stdClass payloads for $mixed[$key] accessboxed mixed + normalized key tupleboxed mixed payload
__rt_mixed_array_setUnbox a Mixed indexed-array/hash payload and write a boxed Mixed value for $mixed[$key] = ...; consumes the boxed value on successboxed mixed + normalized key tuple + boxed value
__rt_json_encode_stdclassEncode the dynamic-property hash backing stdClass as a JSON objectstdClass hash pointerx1/x2 = JSON string

SPL and iterable routines

Source: src/codegen_support/runtime/spl/ (3 files including mod.rs)

These helpers back SPL container classes whose PHP surface needs custom runtime storage. SplDoublyLinkedList (and its SplStack / SplQueue subclasses) store a class id, an owned indexed array of boxed Mixed cells, an iterator index, and iterator-mode bits. SplFixedArray stores a class id and a fixed-size storage array of owned boxed Mixed cells (or null for unset/null slots). Mutating methods take ownership of the boxed Mixed arguments prepared by call lowering, and resize/overwrite paths release any replaced cell first.

SplDoublyLinkedList / SplStack / SplQueue

RoutineWhat it does
__rt_spl_dll_newAllocate an empty doubly-linked-list object with initial mixed-cell storage
__rt_spl_dll_push / __rt_spl_dll_popAppend to / remove from the end
__rt_spl_dll_unshift / __rt_spl_dll_shiftPrepend to / remove from the front
__rt_spl_dll_top / __rt_spl_dll_bottomPeek the last / first element
__rt_spl_dll_insertInsert at an index honoring the iterator mode
__rt_spl_dll_count / __rt_spl_dll_is_emptyElement count / emptiness check
__rt_spl_dll_set_iterator_mode / __rt_spl_dll_get_iterator_modeWrite / read LIFO/FIFO and DELETE iterator-mode bits
__rt_spl_dll_rewind / __rt_spl_dll_valid / __rt_spl_dll_current / __rt_spl_dll_key / __rt_spl_dll_next / __rt_spl_dll_prevIterator surface honoring the active iterator mode
__rt_spl_dll_offset_exists / __rt_spl_dll_offset_get / __rt_spl_dll_offset_set / __rt_spl_dll_offset_unsetArrayAccess operations
__rt_spl_dll_serialize / __rt_spl_dll_serialize_array / __rt_spl_dll_unserializeSerialization helpers

SplFixedArray

RoutineWhat it does
__rt_spl_fixed_newAllocate a fixed-size array object with a zero-initialized mixed-cell storage block
__rt_spl_fixed_countReturn the fixed size
__rt_spl_fixed_set_sizeResize storage, releasing dropped cells and zero-filling new slots
__rt_spl_fixed_offset_exists / __rt_spl_fixed_offset_get / __rt_spl_fixed_offset_set / __rt_spl_fixed_offset_unsetArrayAccess operations with bounds checking
__rt_spl_fixed_to_array / __rt_spl_fixed_from_array / __rt_spl_fixed_copy_from_arrayConvert to / build from a PHP array
__rt_spl_fixed_unserializeSerialization helper

Generator routines

Source: src/codegen_support/runtime/generators/ (3 files: mod.rs, coro.rs, frame.rs)

These helpers back the built-in Generator class. Generators are stackful coroutines that reuse the Fiber runtime: a Generator object reuses the Fiber 232-byte layout (so it can drive itself through __rt_fiber_switch / suspend / resume / throw) plus a small block of generator-specific fields (last_key, last_value, return_value, auto_key, delegated_iter) at offsets 184..224 inside the otherwise-unused Fiber reserved region. The generated generator body runs on its own coroutine stack and calls __rt_gen_suspend at each yield; the accessor helpers below drive the coroutine for the public Iterator surface, send()/throw(), and getReturn().

Because the fiber suspend boundary re-raises a scheduled exception inside the coroutine’s own stack, Generator::throw() lands in an in-generator try/catch (issue #329) rather than unwinding the caller.

RoutineWhat it doesInputOutput
__rt_gen_suspendyield suspension primitive: record the yielded key/value into the generator’s persistent slots (NULL key → auto-increment integer key), then suspend via __rt_fiber_suspendboxed key cell, boxed value cellboxed mixed delivered by the next send()/next()
__rt_gen_currentReturn an owned ref to the boxed Mixed value from the most recent yieldGenerator*boxed mixed payload
__rt_gen_keyReturn an owned ref to the boxed Mixed key from the most recent yieldGenerator*boxed mixed key
__rt_gen_validReport whether the generator is not terminatedGenerator*bool
__rt_gen_nextResume the coroutine past the current yield unless terminatedGenerator*
__rt_gen_sendStore a boxed Mixed sent value, then resume the coroutineGenerator*, boxed mixed valueboxed mixed payload
__rt_gen_throwSchedule a pending throw and resume so the exception is re-raised inside the coroutineGenerator*, throwable objectboxed mixed payload or rethrown exception
__rt_gen_rewindRun the generator to its first yield onceGenerator*
__rt_gen_get_returnReturn an owned ref to the boxed terminal return valueGenerator*boxed mixed payload
__rt_gen_delegateDrive a yield from delegate stored in delegated_iter, forwarding inner yields to the outer caller until the inner iterator is exhaustedGenerator*

Generators are stamped as object heap blocks (heap kind 4) because Generator is a built-in class implementing Iterator. __rt_object_free_deep detects the built-in Generator class id and releases the coroutine’s custom Mixed slots plus any active yield from delegate instead of treating the payload as ordinary class properties.

Fiber routines

Source: src/codegen_support/runtime/fibers/ (4 top-level files plus 4 files in the api/ subdirectory)

These helpers implement PHP 8.1-style cooperative coroutines. They are emitted by the shared runtime on every supported target.

RoutineWhat it doesInputOutput
__rt_fiber_alloc_stackAllocate a per-fiber native stack with a protected guard pagerequested usable stack sizestack base, initial top, mapped size
__rt_fiber_free_stackReturn a mapped fiber stack to the OSstack base, mapped size
__rt_fiber_switchSave the current callee-saved context and restore the target fiber/main contexttarget Fiber* or null for mainresumes when this context is switched back to
__rt_fiber_entryTrampoline run on first entry to a fiber stack; calls the generated wrapper, records return/escape state, and switches backactive _fiber_currentdoes not return normally inside the fiber
__rt_fiber_constructAllocate and initialize the runtime-managed Fiber object and its initial stack framecallable descriptor pointer, Fiber class id, generated wrapper pointerFiber*
__rt_fiber_throw_state_errorAllocate a FiberError and throw it through the normal exception runtimemessage pointer and lengthdoes not return
__rt_fiber_startStart a not-yet-started fiber and return its first yielded value or null on immediate terminationFiber*boxed mixed payload
__rt_fiber_resumeResume a suspended fiber with a boxed payloadFiber*, boxed mixed valueboxed mixed payload
__rt_fiber_suspendSuspend the current fiber and yield a boxed payload to its callerboxed mixed valueboxed mixed value supplied by the next resume
__rt_fiber_throwResume a suspended fiber by throwing into its pending suspend pointFiber*, throwable objectboxed mixed payload or rethrown exception
__rt_fiber_get_currentReturn the currently running fiber, or null when running on the main stackboxed mixed payload
__rt_fiber_get_returnRead the terminal return payload from a terminated fiberFiber*boxed mixed payload
__rt_fiber_state_eqShared predicate helper for isStarted(), isSuspended(), isRunning(), and isTerminated()Fiber*, state idbool

How routines are emitted

File: src/codegen_support/runtime/emitters.rs

The emit_runtime() function calls the target-aware routine emitters in a fixed order. Each runtime module owns the shared helper surface and dispatches internally when AArch64 and Linux x86_64 need different instruction sequences or ABI setup. A RuntimeFeatures argument gates the optional groups: the regex family and __rt_mb_strlen are emitted only for programs that use them, the eval bridge/scope helpers only when the final EIR module requires them, and the --web flag selects the web-aware bodies of __rt_stdout_write, __rt_php_input, __rt_http_response_code, and __rt_header.

pub(crate) fn emit_runtime(emitter: &mut Emitter, features: RuntimeFeatures) {
    // diagnostics: runtime warning emission and @ suppression state
    // numeric: PHP float-to-int coercion and shared rounding-mode decoding
    // strings: itoa, resource display/stdout, ftoa, concat, atoi, equality, formatting, trim/mask,
    // search/replace, explode/implode, hashing, encoding, sscanf, mb_strlen (gated), ...
    // bcmath: exact-decimal bridge marshalling and catchable error translation
    // callables: dynamic is_callable() fallback, callable-descriptor release, Closure::bind
    // system: argv, time, getenv, shell, date/mktime/strtotime, JSON, serialize/unserialize, regex (gated)
    // exceptions: cleanup walk, catch matching, class-implements, throw/rethrow helpers
    // generators: fiber-backed Generator suspend/current/key/valid/next/send/rewind/throw/getReturn/yield-from
    // arrays: heap alloc/free, array/hash helpers, sort, callbacks, refcount, GC
    // eval bridge/scope: boxed-value hooks and native eval scope helpers (gated)
    // spl: SplDoublyLinkedList/SplStack/SplQueue and SplFixedArray storage helpers
    // resource ids: stable PHP-visible resource handle allocation and lookup
    // objects/comparison: stdClass, boxed Mixed property/index dispatch, loose equality
    // buffers: generation-safe handle resolution, descriptor allocation/free, bounds/size/UAF traps
    // io: stdout funnel + web helpers, c-string buffers, file I/O, stat/fs helpers,
    // scandir/glob/tempnam, CSV, streams/sockets, var_dump/print_r, ob_* buffer stack
    // pointers: ptoa, null check, str_to_cstr, cstr_to_str
    // zval: pack/unpack bridge between elephc values and PHP zval structures
    // pdo: callable collation/scalar/aggregate adapters (gated by pdo_udf)
    // fibers: guarded stack allocation, context switch, entry trampoline, Fiber API
}

Notable runtime-only helpers emitted here include __rt_diag_push_suppression, __rt_diag_pop_suppression, __rt_diag_warning, __rt_exception_cleanup_frames, __rt_exception_matches, __rt_instanceof_lookup, __rt_instanceof_invalid_target, __rt_throw_current, __rt_heap_debug_fail, __rt_heap_kind, __rt_hash_insert_owned, __rt_hash_free_deep, __rt_array_column_ref, __rt_mixed_instanceof, __rt_iterable_write_stdout, __rt_iterable_unsupported_kind, __rt_class_implements_interface, __rt_callable_descriptor_release, __rt_closure_bind, __rt_serialize_value, __rt_unserialize_begin, __rt_spl_dll_new, __rt_spl_fixed_new, __rt_gen_suspend, __rt_gen_current, __rt_gen_send, __rt_preg_strip, __rt_pcre_to_posix, __rt_str_to_cstr, __rt_cstr_to_str, __rt_stdout_write, __rt_vd_write, __rt_pr_write, __rt_ob_start_ex, __rt_ob_apply_handler, __rt_ob_flush_all, __rt_zval_pack, __rt_fiber_switch, and __rt_fiber_entry in addition to the more user-visible helpers.

Internal helper inventory

The tables above document the public runtime operations. The emitters also split complex operations into the following internal symbols so the linker can dead-strip each unit independently:

  • Arrays, hashes, and Mixed values: __rt_abs_mixed, __rt_amr_box_value, __rt_array_edge_key, __rt_array_ensure_elem_for_write, __rt_array_fill_assoc, __rt_array_fill_str, __rt_array_find_any_all, __rt_array_get_mixed_key, __rt_array_is_list, __rt_array_merge_recursive, __rt_array_multisort, __rt_array_replace, __rt_array_replace_recursive, __rt_array_set_int, __rt_array_set_mixed, __rt_array_set_mixed_key, __rt_array_set_refcounted, __rt_array_set_str, __rt_array_sum_mixed, __rt_array_to_hash, __rt_array_udiff_uintersect, __rt_array_walk_recursive, __rt_assoc_diff_intersect, __rt_hash_flip, __rt_hash_map, __rt_hash_sum_mixed, __rt_hash_to_indexed_array, __rt_in_array_mixed_int, __rt_mixed_array_append, __rt_mixed_array_get_for_write, __rt_mixed_cell_autovivify_array, __rt_mixed_cell_promote_to_hash, __rt_mixed_new_empty_array_cell, and __rt_mixed_numeric_common.
  • Strings, dates, JSON, and serialization: __rt_concat_append, __rt_date_entry, __rt_implode_bool, __rt_json_validate_number, __rt_json_validate_string, __rt_microtime_build_into, __rt_microtime_mixed, __rt_microtime_str, __rt_mktime_shifted, __rt_serialize_begin, __rt_serialize_hash_body, __rt_serialize_indexed_body, __rt_serialize_pstr, __rt_serialize_uint, __rt_unser_at, and __rt_unser_key.
  • Objects, callables, resources, and zvals: __rt_box_wrapper_stat_result, __rt_function_exists_lookup, __rt_obj_store_prop, __rt_object_handle_acquire, __rt_object_handle_of, __rt_object_handle_release, __rt_resource_id_mint, __rt_resource_id_of, __rt_resource_type_name, __rt_spl_object_hash, __rt_zval_pack_element.
  • Files, streams, sockets, and networking: __rt_addr_is_udp, __rt_build_sockaddr_in6, __rt_chgrp_group, __rt_chown_user, __rt_disk_space, __rt_fd_write, __rt_file_get_contents_maybe_url, __rt_format_sockaddr_in, __rt_format_sockaddr_in6, __rt_format_sockaddr_unix, __rt_fsockopen, __rt_fwrite, __rt_get_int_context_option, __rt_get_string_context_option, __rt_http_build_copy_aarch64, __rt_http_build_copy_x86, __rt_inet6_pton, __rt_inet_addr_parse, __rt_lchgrp_group, __rt_lchown_user, __rt_opendir, __rt_opendir_glob, __rt_path_is_wrapper, __rt_popen, __rt_readdir, __rt_readfile_wrapper, __rt_rewinddir, __rt_servent_load, __rt_stash_connect_host, __rt_stream_wrapper_register, and __rt_stream_wrapper_unregister.
  • User stream wrappers: __rt_user_wrapper_dir_closedir, __rt_user_wrapper_dir_readdir, __rt_user_wrapper_dir_rewinddir, __rt_user_wrapper_fclose, __rt_user_wrapper_feof, __rt_user_wrapper_fflush, __rt_user_wrapper_flock, __rt_user_wrapper_fread, __rt_user_wrapper_fseek, __rt_user_wrapper_fstat, __rt_user_wrapper_ftell, __rt_user_wrapper_ftruncate, __rt_user_wrapper_fwrite, __rt_user_wrapper_opendir, __rt_user_wrapper_path_op, __rt_user_wrapper_rename, __rt_user_wrapper_set_option, __rt_user_wrapper_stream_cast, __rt_user_wrapper_url_stat, and __rt_user_wrapper_url_stat_field.
  • Diagnostics and structured output: __rt_touch_meta_array, __rt_var_dump_array_bool, __rt_var_dump_array_float, __rt_var_dump_array_int, __rt_var_dump_array_str, __rt_var_dump_close_container, __rt_var_dump_emit_bool_line, __rt_var_dump_emit_float_line, __rt_var_dump_emit_indexed_key, __rt_var_dump_emit_int_line, __rt_var_dump_emit_null_line, __rt_var_dump_emit_object_key, __rt_var_dump_emit_recursion_line, __rt_var_dump_emit_string_key, __rt_var_dump_emit_string_line, __rt_var_dump_emit_uninit_line, __rt_var_dump_indexed, __rt_var_dump_object, __rt_var_dump_open_container, __rt_var_dump_open_object, __rt_var_dump_value, __rt_vd_indent_pop, __rt_vd_indent_push, __rt_vd_obj_count, __rt_vd_obj_desc, __rt_vd_pad, __rt_vd_seen_find, __rt_vd_seen_pop, __rt_vd_seen_push, __rt_warn_array_offset_on_null, __rt_warn_foreach_non_iterable, __rt_warn_nan_coerced_bool, and __rt_warn_undefined_array_key_str.
  • Additional array and scalar entry points: __rt_alloc_overflow, __rt_array_chunk_to_hash, __rt_array_count_values, __rt_array_iter_next, __rt_array_key_exists_mixed_key, __rt_array_ptr_key, __rt_array_ptr_seek, __rt_array_ptr_value, __rt_array_slice_to_hash, __rt_array_splice_insert_boxed, __rt_array_splice_insert_refcounted, __rt_array_splice_insert_str, __rt_array_splice_insert_unboxed, __rt_array_strict_eq, __rt_array_to_hash_reverse, __rt_count_values_bump, __rt_hash_count_values, __rt_int_pow_checked, __rt_min_max_hash, __rt_min_max_mixed, __rt_min_max_str, __rt_mixed_clone, __rt_mixed_inc_dec, __rt_mixed_intval_base, __rt_mixed_numeric_pow, __rt_php_float_to_int, __rt_php_truthy, and __rt_round_mode.
  • Additional string and crypto entry points: __rt_base_convert, __rt_base_to_number, __rt_chunk_split, __rt_concat_grow, __rt_concat_publish, __rt_concat_reserve, __rt_count_chars, __rt_dec_to_base, __rt_openssl_cipher_iv_length, __rt_openssl_decrypt, __rt_openssl_encrypt, __rt_openssl_get_cipher_methods, __rt_parse_url_key_address, __rt_parse_url_throw_component, __rt_quotemeta, __rt_str_inc_dec, __rt_str_to_int_base, __rt_str_word_count, __rt_strncasecmp, __rt_strncmp, __rt_strtr_array, __rt_strtr_hash, __rt_strtr_int_key_len, __rt_strtr_pairwise, __rt_strtr_probe, and __rt_substr_count.
  • Additional object, generator, I/O, bridge, and PDO entry points: __rt_bcmath_throw, __rt_file_get_contents_range, __rt_gen_suspend_delegated, __rt_obj_enum_case_name, __rt_obj_prop_count, __rt_obj_prop_name, __rt_pdo_call_agg_final, __rt_pdo_call_agg_step, __rt_pdo_call_collation, __rt_pdo_call_scalar, __rt_pr_obj_desc, __rt_print_r_object, and __rt_var_dump_emit_enum_line.

Compiled executables dead-strip unreachable runtime helpers at link time. On Linux each __rt_* helper is emitted in its own .text.<name> section and collected with --gc-sections; on macOS the runtime object carries a .subsections_via_symbols footer so each helper is a separately collectable atom dropped by -dead_strip (internal cross-helper labels stay assembler-local L-locals, with the few helpers reached by a b/bl from another atom marked .alt_entry so they remain live symbols). Combined with the AST-side control-flow pruning and dead-code elimination elephc already does before codegen, only the helpers a program actually reaches are linked. Shared libraries (--emit cdylib) keep the full runtime so every exported entry stays callable.

The runtime can also be emitted in position-independent mode for --emit cdylib builds: the emitter’s pic_data_refs flag makes the abi::symbols helpers route every global data reference through the GOT (@GOTPCREL on x86_64, :got:/:got_lo12: on AArch64) instead of direct PC-relative addressing. A separate cdylib_boundary flag controls exception activations and recoverable runtime exits, so those semantics no longer depend implicitly on PIC. Every internal ELF global receives .hidden; Mach-O uses .private_extern. The PIC and non-PIC variants produce different assembly text, so they cache as separate runtime objects.

The cdylib boundary also makes recoverable unwinding explicit. Each cdylib user function publishes an exception-activation record that links the current frame to its generated cleanup callback. If an exception escapes any export, the existing Throwable machinery walks those records, releases function-owned values, and returns through the boundary handler. Heap and concat allocation exhaustion use the same active boundary to report ELEPHC_STATUS_ALLOCATION_FAILURE; other shared emit_exit paths become ELEPHC_STATUS_RUNTIME_FAILURE while a boundary is active, and executable fatal paths remain unchanged. Scalar wrappers preserve their original return signatures and expose the recorded result through elephc_last_status; owned-string wrappers preserve any fixed supported input layout, return status directly, copy successful bytes into independently owned runtime-heap storage, and transfer that buffer to the C caller for elephc_free. Boundary depth is counted, and each wrapper saves/restores _concat_off, so nested teardown cannot disable or corrupt its caller’s boundary state. Diagnostic presence is stored separately from diagnostic length, so an empty Throwable message remains distinguishable from “no error”. See The Codegen and Shared Libraries.

Runtime data

The runtime data layer lives in src/codegen_support/runtime/data/. fixed.rs emits shared buffers, error strings, and lookup tables; user.rs emits per-program globals, statics, enum-case slots, and metadata tables; instanceof.rs formats dynamic instanceof lookup names. Together they declare global buffers using .comm and static data tables:

.comm _concat_buf, 65536     ; 64KB string buffer
.comm _concat_off, 8         ; current offset into string buffer
.comm _print_r_mode, 8       ; print_r($v, true) return-mode capture flag
.comm _print_r_off, 8        ; print_r capture write offset
.comm _print_r_buf, 65536    ; 64KB print_r return-mode capture buffer
.comm _ob_level, 8           ; output-buffer (ob_*) stack depth
.comm _ob_ptrs, 512          ; 64-slot ob buffer base pointers
.comm _ob_lens, 512          ; 64-slot ob buffer used byte counts
.comm _ob_caps, 512          ; 64-slot ob buffer capacities
.comm _ob_handler_stubs, 512 ; 64-slot output-handler invocation stubs
.comm _ob_handler_envs, 512  ; 64-slot handler env words (descriptor ptr / eval registry id)
.comm _ob_name_ptrs, 512     ; 64-slot handler display-name pointers
.comm _ob_name_lens, 512     ; 64-slot handler display-name lengths
.comm _ob_chunk_sizes, 512   ; 64-slot auto-flush chunk sizes
.comm _ob_flags, 512         ; 64-slot ob_start() flags words
.comm _ob_started, 512       ; 64-slot handler-started flags
.comm _ob_in_handler, 8      ; non-zero while a user output handler runs (output discarded)
.comm _ob_flushing, 8        ; process-exit drain re-entry guard
.comm _ob_implicit_flush, 8  ; stored ob_implicit_flush() flag (semantically inert)
.comm _elephc_eval_ob_handler_fn, 8 ; magician hook for eval-registered ob handlers
.comm _elephc_eval_dynamic_object_destruct_fn, 8 ; eval-bridge dynamic-object destructor hook
.comm elephc_web_capture, 8  ; --web output-capture flag (per-target C-ABI symbol mangling)
.comm _global_argc, 8        ; saved argc from OS
.comm _global_argv, 8        ; saved argv pointer from OS
.comm _exc_handler_top, 8    ; top of the active exception-handler stack
.comm _exc_call_frame_top, 8 ; top of the activation-record cleanup stack
.comm _exc_value, 8          ; currently propagating exception object
.comm _fiber_current, 8      ; currently running Fiber object, or null on main
.comm _fiber_main_saved_sp, 8 ; saved main-stack pointer while running a fiber
.comm _fiber_main_saved_exc, 8 ; saved main exception-handler chain while running a fiber
.comm _fiber_main_saved_call_frame, 8 ; saved main cleanup-frame chain while running a fiber
.comm _rt_diag_suppression, 8 ; nested runtime warning-suppression depth for @
.comm _heap_buf, 8388608     ; 8MB heap by default (--heap-size overrides)
.comm _heap_off, 8           ; current heap offset
.comm _heap_free_list, 8     ; head of the general address-ordered free list
.comm _heap_small_bins, 32   ; 4 x 8-byte heads for <=8/16/32/64-byte cached blocks
.comm _heap_debug_enabled, 8 ; BSS-backed debug flag, set to 1 in _main when compiled with --heap-debug
.comm _buffer_registry, 196656 ; reserved slot 0 + 4096 generation-safe 48-byte descriptors
.comm _buffer_registry_free, 8 ; recycled descriptor-index free-list head
_buffer_registry_next:
    .quad 1                 ; next never-issued descriptor index
.comm _web_heap_guard_enabled, 8 ; enables per-request heap leak checks in --web mode
.comm _gc_collecting, 8      ; cycle collector re-entry guard
.comm _gc_release_suppressed, 8 ; suppress nested collection during deep frees
.comm _json_last_error, 8    ; last JSON_ERROR_* code
.comm _json_active_flags, 8  ; active JSON flags for encode/decode/validate
.comm _json_active_depth, 8  ; current recursive JSON container depth
.comm _json_indent_depth, 8  ; current JSON_PRETTY_PRINT formatting depth
.comm _json_depth_limit, 8   ; configured JSON depth limit
.comm _json_validate_idx, 8  ; validator cursor index
.comm _json_validate_ptr, 8  ; validator input pointer
.comm _json_validate_len, 8  ; validator input length
.comm _json_decode_assoc, 8  ; json_decode object-shape selector
.comm _json_error_source_ptr, 8 ; json_decode input pointer for error locations
.comm _json_error_location_active, 8 ; whether line/column should be appended
.comm _json_error_line, 8    ; one-based line for the last json_decode error
.comm _json_error_column, 8  ; one-based column for the last json_decode error
_heap_max:
    .quad 8388608            ; configured heap size limit
.comm _gc_allocs, 8          ; allocation counter
.comm _gc_frees, 8           ; free counter
.comm _gc_live, 8            ; current live heap footprint in bytes
.comm _gc_peak, 8            ; high-water mark counter
.comm _cstr_buf, 4096        ; 4KB C-string conversion buffer
.comm _cstr_buf2, 4096       ; 4KB second C-string buffer
.comm _eof_flags, 256        ; EOF flag per file descriptor
.comm _principal_lookup_buf, 4096 ; passwd/group lookup line buffer
.comm _elephc_crypto_cipher_iv_length_fn, 8 ; published crypto bridge IV-length callback
.comm _elephc_crypto_cipher_methods_fn, 8 ; published crypto bridge cipher-list callback
.comm _elephc_crypto_decrypt_fn, 8 ; published crypto bridge decryption callback
.comm _elephc_crypto_encrypt_fn, 8 ; published crypto bridge encryption callback
_etc_passwd_path:
    .asciz "/etc/passwd"     ; passwd database path for name lookups
_etc_group_path:
    .asciz "/etc/group"      ; group database path for name lookups
_principal_lookup_read_mode:
    .asciz "r"               ; fopen() mode for principal lookup files
; Per-program: global variable storage (one per `global $var` used)
.comm _gvar_x, 16            ; 16 bytes per global variable
; Per-program: static variable storage (one pair per `static $var`)
.comm _static_func_var, 16   ; 16 bytes for persisted value
.comm _static_func_var_init, 8 ; 8-byte initialization flag
; Per-program: static property storage (one slot per effective declaring class property)
.comm _static_prop_Class_prop, 16 ; 16 bytes for the static property value

Additionally, the runtime emits static data tables:

  • _fmt_g — printf format string for float-to-string conversion via %.14G
  • _b64_encode_tbl — 64-byte Base64 encoding lookup table
  • _b64_decode_tbl — 256-byte Base64 decoding lookup table
  • _spl_autoload_exts_default, _spl_autoload_exts_ptr, _spl_autoload_exts_len — mutable SPL autoload extension state
  • _heap_err_msg, _arr_cap_err_msg, _ptr_null_err_msg — fatal runtime error strings
  • _buffer_bounds_msg, _buffer_uaf_msg, _buffer_alloc_size_msg, _buffer_registry_exhausted_msg, _match_unhandled_msg, _static_prop_private_access_msg, _instanceof_target_type_msg, _iterable_unsupported_kind_msg — fatal runtime error strings for buffers, match, late-bound private static-property access, dynamic instanceof target validation, and iterable dispatch
  • _heap_dbg_bad_refcount_msg, _heap_dbg_double_free_msg, _heap_dbg_free_list_msg — fatal heap-debug error strings enabled by --heap-debug
  • _heap_dbg_* summary labels — fixed strings used by __rt_heap_debug_report for alloc/free/live/leak output
  • _resource_id_prefix — prefix used by resource display helpers
  • _pr_spaces, _pr_open, _pr_close — the 64-space padding block and (\n / )\n literals used by the print_r walkers
  • _ob_handler_name, _ob_closure_invoke_name, _ob_k_* — the default-handler / Closure::__invoke display names and status-array key strings used by ob_get_status() and ob_list_handlers()
  • _ob_ntc_*, _ob_warn_bad_callback_*, _ob_fatal_in_handler — PHP-parity ob_* notice, warning, and fatal texts, routed through __rt_stdout_write so parent buffers capture them like PHP
  • _uncaught_exc_msg — fatal exception string written by __rt_throw_current when no handler exists
  • _diag_fopen_failed_msg, _diag_file_get_contents_failed_msg, _diag_define_already_defined_msg — suppressible runtime warning text routed through __rt_diag_warning
  • _fiber_msg_already_started, _fiber_msg_not_suspended, _fiber_msg_throw_not_suspended, _fiber_msg_not_terminated, _fiber_msg_suspend_outside, _fiber_msg_unsupported_callable, _fiber_msg_stack_alloc_failed — messages used by FiberError runtime paths
  • _fiber_class_id, _fiber_error_class_id — per-program class ids used by Fiber object cleanup and FiberError construction
  • _generator_class_id — per-program class id used to recognize Generator frames during object deep-free
  • _php_uname_mode_len_msg, _php_uname_mode_value_msg — fatal php_uname() argument diagnostics for invalid mode strings
  • _filetype_*, _stat_key_*, _dirname_*, _pathinfo_key_*, _parse_url_*, _tmpfile_template — file metadata, path, URL-component, stat-array, and temporary-file lookup strings used by runtime helpers
  • _locale_utf8_name, _locale_env_name — locale selectors used by runtime helpers that need host locale fallback
  • _json_true, _json_false, _json_null — JSON keyword strings used by __rt_json_encode_bool and __rt_json_encode_null
  • _json_int_max_str, _json_int_min_str — decimal threshold strings used by JSON_BIGINT_AS_STRING overflow detection without wrapping through integer parsing
  • _json_err_msg_0_json_err_msg_10, _json_err_msg_table, _json_err_msg_count, _json_err_loc_prefix, _json_err_loc_colonjson_last_error_msg() lookup data and location-suffix fragments for the supported JSON_ERROR_* code range
  • _day_names — 7 entries (84 bytes), each 12 bytes: day name padded to 10 chars + 1 length byte + 1 padding byte. Used by __rt_date for l (full name) and D (abbreviated) format characters
  • _month_names — 12 entries (144 bytes), same layout as day names. Used by __rt_date for F (full name) and M (abbreviated) format characters
  • _strtotime_keyword_tab, _strtotime_unit_tab — keyword, weekday, modifier, and unit lookup tables used by __rt_strtotime
  • _instanceof_target_count, _instanceof_target_entries, _instanceof_name_* — case-insensitive class/interface name metadata used by dynamic instanceof string targets, including leading-backslash aliases
  • _class_gc_desc_count, _class_gc_desc_ptrs, _class_gc_desc_<id> — per-class property traversal metadata used by object deep-free and cycle collection
  • _class_json_desc_ptrs, _class_json_desc_<id>, _class_json_pname_<id>_<slot>, _json_exception_class_id, _stdclass_class_id — JSON object encoding descriptors, JsonException construction metadata, and stdClass runtime class id
  • _class_attribute_count, _class_attribute_ptrs, _class_attributes_<id> — emitted class-level PHP attribute metadata. The current PHP-facing helpers and class/enum Reflection attribute constructors materialize their results from the same ClassInfo metadata during codegen, rather than doing dynamic runtime class/member lookup.
  • _class_vtable_ptrs, _class_vtable_<id> — per-class virtual-method tables used by inheritance dispatch through class_id
  • _class_static_vtable_ptrs, _class_static_vtable_<id> — per-class static-method tables used by late static binding
  • _class_destruct_ptrs — class_id-indexed __destruct method pointers (or 0) consulted by __rt_call_object_destructor during object deep-free
  • _classes_by_name, _classes_by_name_count — case-insensitive name -> (class_id, object size) lookup table used by __rt_new_by_name for new $variable()
  • static_property_symbol(...)-derived .comm slots — 16-byte storage slots for effective declaring static properties, shared by inherited static properties until a subclass redeclares the property
  • enum_case_symbol(...)-derived .comm slots — singleton backing storage for enum cases emitted from user program metadata

When --heap-debug is enabled, the runtime also activates __rt_heap_debug_check_live, __rt_heap_debug_validate_free_list, and __rt_heap_debug_report. These helpers turn allocator corruption into immediate fatal errors for duplicate frees, zero-refcount incref/decref paths, and malformed free-list or small-bin state, poison freed payload bytes with 0xA5, and print an end-of-process summary with alloc/free counts, live block count, live bytes, leak summary, and the peak live-byte watermark.

Every heap allocation now also carries a uniform 8-byte kind tag in its 16-byte allocator header. The current runtime uses 0=raw/untyped, 1=string, 2=indexed array, 3=assoc/hash, 4=object, 5=boxed mixed, and 6=throwable (the compact runtime-thrown exception/error payloads, accepted by the release dispatchers and routed through the object release path), which lets runtime dispatch stay independent from each payload’s internal layout. Generator frames use heap kind 4 because Generator is a built-in object with a custom payload layout. On x86_64 the kind word additionally carries the ASCII marker "ELPH" in its high 32 bits; every stamp is built through the shared codegen_support::sentinels helpers (x86_64_heap_kind_word() / X86_64_HEAP_MAGIC_HI32) rather than hand-typed immediates, and the refcount/free helpers ignore pointers whose header lacks the marker. The low 16 bits keep the persistent container metadata: low byte = heap kind, bits 8..14 = indexed-array runtime value_type, and bit 15 = copy-on-write container flag. The collector reuses higher bits for transient reachable/incoming-edge metadata during __rt_gc_collect_cycles. Runtime data also now includes _gc_collecting, _gc_release_suppressed, _class_gc_desc_count, _class_gc_desc_ptrs, _class_vtable_ptrs, _class_static_vtable_ptrs, and static-property storage slots so deep-free / cycle-collection paths can coordinate nested releases, discover class property traversal metadata, and support inherited instance dispatch, static-property reads/writes, and late static binding.

See Memory Model for details on how these buffers work.