Compilation pipeline
Source (.php or .lfc)
│
▼
┌─────────┐
│ Lexer │ src/lexer/
│ │ scan.rs, literals.rs, cursor.rs, token.rs
│ │ Source text -> Vec<SpannedToken> (Token + TokenMetadata)
└────┬─────┘
│
▼
┌─────────┐
│ Parser │ src/parser/
│ │ expr/, stmt/, control.rs, ast/
│ │ Tokens → Program (Vec<Stmt>)
└────┬─────┘
│
▼
┌────────────────┐
│ MagicConstants │ src/magic_constants.rs
│ │ Lowers PHP magic constants per source file before
│ │ include inlining and semantic passes.
└────┬───────────┘
│
▼
┌─────────────┐
│ Conditional │ src/conditional/
│ │ Applies CLI `--define` symbols to `ifdef` branches.
│ │ Removes inactive AST branches before include resolution.
└────┬────────┘
│
▼
┌──────────┐
│ Autoload │ src/autoload/
│ (build) │ Reads Composer autoload metadata and extracts supported
│ │ top-level `spl_autoload_register()` rules.
└────┬─────┘
│
▼
┌─────────┐
│ Resolver │ src/resolver/
│ │ Pre-scans statically resolvable include declarations,
│ │ inlines executable include bodies, and lowers *_once guards.
└────┬─────┘
│
▼
┌──────────────┐
│ Preludes │ src/{pdo,mysqli,tz,list_id,var_export,opcache,image,hash,curl,xml,dom_html,web,version}_prelude*
│ │ Injects only the compiler-owned PHP surfaces required by
│ │ resolved source usage, forced bridge flags, or --web. Before
│ │ injection, src/autoload/prelude_probe.rs scans an autoloaded
│ │ copy for PDO/mysqli/tz/image/hash/curl/xml/var_export usage.
└─────┬────────┘
│
▼
┌──────────────┐
│ NameResolver │ src/name_resolver/
│ │ Flattens namespace/use scopes and rewrites names to
│ │ canonical fully-qualified names before semantic passes.
└─────┬────────┘
│
▼
┌──────────┐
│ Autoload │ src/autoload/
│ (run) │ Inserts Composer/SPL-resolved class files before the first
│ │ reference that needs each class-like symbol.
└────┬─────┘
│
▼
┌──────────────┐
│ DOM autoload │ src/dom_html_prelude.rs
│ │ Rechecks newly loaded classes for DOM HTML usage.
└─────┬────────┘
│
▼
┌──────────────┐
│ Object casts │ src/object_cast_prelude*
│ │ Injects stdClass declarations for object casts after autoload.
└─────┬────────┘
│
▼
┌──────────────┐
│ Function args│ src/func_args/
│ desugaring │ Rewrites func_num_args/get_args/get_arg into a hidden
│ │ variadic parameter before optimization and checking.
└─────┬────────┘
│
▼
┌──────────────┐
│ OPcache bake │ src/opcache_prelude/
│ │ Completes the resolved/autoloaded script manifest and
│ │ replaces the injected placeholder bodies with baked data.
└─────┬────────┘
│
▼
┌──────────────┐
│ Optimizer │ src/optimize/
│ (fold) │ Folds scalar constants and simplifies pure expressions
│ │ before type checking.
└─────┬────────┘
│
▼
┌─────────┐
│ Type │ src/types/ + src/generics.rs
│ Checker │ traits.rs, checker/mod.rs, checker/builtins/, checker/functions/, warnings/
│ │ Specializes reachable templates to a fixed point, validates types,
│ │ computes packed layouts, collects warnings, returns CheckResult
└────┬─────┘
│
▼
┌──────────────┐
│ Exports │ src/exports.rs
│ Scan │ Collects #[Export]-marked functions and validates their
│ │ C-ABI signatures and post-lowering safety graph (warns and
│ │ ignores them only in normal executable emission).
└─────┬────────┘
│
▼
┌──────────────┐
│ Optimizer │ src/optimize/
│ (propagate) │ Propagates scalar locals conservatively after
│ │ successful checking.
└─────┬────────┘
│
▼
┌──────────────┐
│ Optimizer │ src/optimize/
│ (prune) │ Removes constant-dead control flow after successful
│ │ checking.
└─────┬────────┘
│
▼
┌──────────────┐
│ Optimizer │ src/optimize/
│ (normalize) │ Canonicalizes equivalent control-flow shells into
│ │ simpler AST shapes.
└─────┬────────┘
│
▼
┌──────────────┐
│ Optimizer │ src/optimize/
│ (DCE) │ Drops leftover unreachable or non-observable
│ │ statements from the normalized AST.
└─────┬────────┘
│
▼
┌──────────────┐
│ Optimizer │ src/optimize/reachability/
│ (decl-reach) │ Prunes unreachable functions, classes, methods, and
│ │ prelude declarations, then reconciles checked metadata.
└─────┬────────┘
│
▼
┌─────────────┐
│ EIR Lowerer │ src/ir_lower/ + src/ir/
│ │ Lowers the checked optimized AST into validated EIR.
└──────┬──────┘
│
▼
┌─────────────┐
│ EIR passes │ src/ir_passes/
│ │ Module-level fixed-point pipeline: a cross-function
│ │ small-function inliner interleaved with the per-function
│ │ pass driver (identity folding, peephole rewrites, scalar
│ │ local promotion, immutable local classification, checked-
│ │ integer sinking, bounded mixed-counter promotion,
│ │ integer range analysis (IntegerRange),
│ │ boxed numeric-chain fusion, constant folding,
│ │ common-subexpression elimination, loop-invariant code motion,
│ │ dead-instruction elimination, dead-store elimination, branch
│ │ simplification) plus dominance and loop analysis and linear-
│ │ scan register allocation (liveness, intervals, pools).
└──────┬──────┘
│
▼
┌─────────────┐
│ EIR Codegen │ src/codegen/ + shared src/codegen_support/abi/
│ │ Emits target assembly text from EIR.
└──────┬──────┘
│
▼
┌───────────────┐
│ Tooling glue │ runtime_cache.rs, native_deps/, link_plan.rs, link_planning.rs
│ │ Reuses cached runtime objects, resolves required managed
│ │ artifacts read-only for final links, and builds a typed
│ │ link plan. Source maps/timings remain optional outputs.
└─────┬─────────┘
│
▼
┌─────────┐
│ as + ld │ System assembler and linker
│ │ .s → .o → target-native binary
└─────────┘
Experimental eval() follows the same front-end pipeline. A literal fragment
is classified inside EIR lowering by src/eval_aot.rs: eligible fragments
become native EIR, scope-backed fragments use only core runtime helpers, and
dynamic fragments enable the optional elephc-magician interpreter staticlib
at final linking. This is a lowering/linking decision, not a separate timed
compiler phase. See Eval Runtime Architecture.
Before EIR validation, src/ir_lower/boxed_array_appends.rs reconciles single-use append
receivers with their final local storage. A later reference call can widen a local from a
native array to boxed Mixed; earlier appends then use MixedArrayAppend and a borrowed
boxed read when no consumer observes a concrete native element layout. Typed consumers such as
string-array sorting keep native append; codegen retires the old box before mutation so the
unboxed lease becomes the mutator’s owner without forcing a copy. A borrowed reference-cell
read on an unpromoted raw path first retakes that lease before a consuming mutation, then
transfers it through mutation writeback; a later explicit release makes writeback retain its
own owner. Indexed writes, hash mutations, nested writes and reference iteration share this
protocol. Iterator conversion reloads the SSA source after retiring its previous box because
cleanup may clobber result registers. Append lowering evaluates the RHS before loading the current local, so a reference
write on the RHS also selects the ordinary scalar/string parent guards. The final storage read
stays after RHS evaluation, while the shared runtime keeps copy-on-write and reference aliases intact.
Rejected append branches release an owned string RHS before raising the catchable parent error.
Ordinary local loads unboxed from final Mixed storage own a lease that the consumer releases,
including on paths that never promote. Reference-cell reads preserve their alias payload shape
and remain borrowed. Indexed writes still detach a shared cell; only append bypasses that copy.
Generated-artifact write policy
Source: src/pipeline/artifact_io.rs
Every output path is derived from the source filename (pipeline/output.rs’s
output_paths), so a checked-out tree someone else controls — an untrusted pull
request, a shared build directory — chooses them. A symlink named main.s,
main.map, libmain.h or main.key next to main.php used to make the
compiler truncate and overwrite that symlink’s target with the compiling user’s
permissions. One module now owns the policy for all of them.
Preflight, before any work runs. reject_unsafe_destinations inspects each
destination with symlink_metadata — which does not follow the final component,
unlike metadata, whose answer would describe the target — and refuses a
symbolic link or an existing non-regular file (a directory, a FIFO, a device
node). An existing regular file is accepted: recompiling over yesterday’s
main.s is the normal case. Only ErrorKind::NotFound counts as absence; any
other inspection failure is refused rather than assumed benign, because a path
whose kind could not be read is exactly the one that cannot be vouched for.
The check is scoped to the run about to happen. ArtifactPlan::for_run
derives the destination set from the flags that decide where compile() returns:
--check and --emit-ir return before the backend stage and produce none of
these files, --emit-asm stops after the assembly, and the probe-key sidecar is
a destination only under --with-monitoring. Validating a path the command will
never write would turn an unrelated symlink at that name into a refusal of a
valid command.
Staged replacement, for what this process writes. write_artifact creates a
private file in the destination’s own directory with O_CREAT | O_EXCL, writes
and syncs it, then renames it over the destination. rename(2) replaces a
symlink rather than following it, so for the assembly, the source map, the
generated header and the probe key the window between the preflight and the write
is closed rather than merely narrowed. write_private_artifact is the same path
with mode(0o600) applied at creation — used for the probe key, whose bytes are
the monitoring HMAC credential and must never exist at their well-known .key
name under a permissive umask, not even for the instant before a chmod.
What the preflight does not close. The object file and the final binary are
written by an external assembler and linker, which this process never opens
itself — refusing before the tool runs is the only place to cover them, and it
covers the threat above completely, because a symlink that arrives in a checkout
is present before the compile starts. It does not cover an attacker running
concurrently with write access to the build directory, who can plant one in the
interval between the check and the invocation. Staging those two would close it,
at the cost of reworking the debug-info path: the linker’s debug map records the
object path as handed to it (keep_obj_for_debug in pipeline/backend.rs
deliberately keeps the object at that path for debuggers to follow), and
dsymutil names its bundle after the binary it is given.
Target Model
The compiler now distinguishes the operating-system side of a target from the instruction set:
Platformdescribes OS / binary format / libc concerns such as macOS vs Linux.Archdescribes the instruction set and calling convention such asAArch64vsX86_64.Targetcombines both and is threaded from the CLI into codegen and the test harness.
All five supported targets are first-class: macos-aarch64, ios-arm64,
ios-sim-arm64, linux-aarch64, and linux-x86_64. AArch64 snippets in the
internals documentation are examples only. The explicit Target split keeps OS,
binary-format, SDK, ABI, and instruction-set decisions separate across macOS,
iOS device and Simulator, and Linux.
Module map
src/
├── lib.rs Public module exports
├── main.rs CLI binary entry point
├── cli.rs Command-line option parsing
├── repl/ Interactive `elephc repl` session: parsing, cached native host, and history
├── pipeline.rs Frontend/backend compilation pipeline
│ └── artifact_io.rs Generated-artifact write policy: preflight destination
│ validation and O_EXCL-staged atomic replacement
├── exports.rs #[Export] collection and C-ABI signature validation for --emit cdylib
├── link_plan.rs Ordered typed archives, libraries, paths, frameworks, and Linux link mode
├── link_planning.rs Compile/runtime/user/managed inputs to one final ordered link plan
├── linker/ Link-plan rendering, bridge discovery, SDK lookup, and archive handling
├── native_deps/ Curated native package subsystem
│ ├── orchestration.rs Slim native-command state-transition coordinator
│ ├── materialize.rs Locked download/extract/build/receipt/publication path
│ ├── catalog.rs Exact trusted source, dependency, and recipe metadata for all curated packages
│ ├── cache.rs Cache keys, advisory locks, and atomic publication
│ ├── doctor.rs Read-only project/artifact/cache-size diagnostics
│ ├── prune.rs Explicit stale-fingerprint and abandoned-staging cleanup
│ ├── resolver.rs Read-only compile requirement to exact archive resolution
│ └── recipes/ Reviewed PCRE2, zlib, OpenSSL, nghttp2, libssh2, curl, Oniguruma, and libxml2 source-build recipes
├── timings.rs Phase timing collection/reporting
├── span.rs Source position (line, col)
├── intrinsics.rs Compiler-recognized intrinsic method calls for runtime-managed core objects
├── builtins/ AOT `builtin!` bindings: checker/EIR semantics joined to `elephc-builtin-contract`
├── builtin_metadata.rs Public builtin metadata snapshots for parity tests and external audits
├── string_bytes.rs Parser string-literal payload → PHP runtime bytes conversion
├── docblock.rs Per-file generic PHPDoc annotation application
├── docblock/ Declaration binding and generic member annotations
├── generics.rs Type-checking and specialization fixed point
├── generics/ Class/method/constructor specialization, scope keys, variance
├── magic_constants.rs Per-file lowering for PHP magic constants
├── magic_constants/ File/scope/trait magic-constant walkers
├── conditional/ Build-time `ifdef` pass
├── strict_php.rs `--strict-php` mode state and audit entry point
├── strict_php/ Strict-mode AST audit pass rejecting elephc-only syntax extensions
├── autoload/ Composer/SPL AOT autoload indexing, rule interpretation, and file insertion
├── resolver/ Include/require resolution, declaration discovery, once guards
├── eval_aot.rs Target-independent literal eval planning and fallback classification
├── php_version.rs Accepted and automatically maintained PHP compatibility profiles
├── php_profile/ Project-profile discovery, constraints, minimums, and sensitivity
├── curl_prelude.rs Curl PHP surface injection entry point
├── curl_prelude/ Curl usage detection, classes, constants, and function bodies
├── monitor/ Exact, sampled, local, remote, service, and export profiling
├── call_graph.rs Profiling call-graph aggregation and DOT/HTML rendering
├── pprof_encode.rs Profiling export in pprof protobuf form
├── probe_key.rs Monitoring build-key creation and validation
├── optimize.rs Public optimizer entry points and effect context
├── optimize/ Constant folding, constant propagation, control-flow pruning, normalization, dead-code elimination, declaration reachability pruning
├── ir/ EIR types, builder, validator, printer, effects, and tests
├── ir_lower/ Active checked-AST to EIR lowering
├── ir_passes/ EIR optimization pass driver, identity folding, peephole patterns, scalar local promotion, immutable integer-local loads, checked-integer sinking, canonical loop optimization, integer range and induction-variable analysis, numeric-chain fusion, constant folding, common-subexpression elimination, loop-invariant code motion, dead-instruction elimination, dead-store elimination, branch simplification, concat scratch-state optimization, fresh numeric ownership finalization, direct self/mutual tail-recursion lowering, the cross-function small-function inliner (run to a module-level fixed point), dominance analysis, loop analysis, and linear-scan register allocation with cost-aware constant rematerialization
├── codegen/ Active EIR to target assembly backend
├── codegen_support/ Shared ABI, runtime, platform, metadata, and callable support
├── runtime_cache.rs Cached shared runtime object preparation
├── runtime_cache/ Cache identity, lease/publication protocol, and pruning internals
├── synthetic_class.rs Rust builders for compiler-injected synthetic PHP declarations
├── synthetic_class/ Built class bodies (date/time, calendar, …) with parse-parity oracles against the replaced PHP
├── prelude_prune.rs Pre-name-resolution usage scan feeding the class gates and superglobal seeding
├── prelude_prune/ Shared exhaustive AST walk over expressions, statements, and declarations
├── source_map.rs Assembly comment markers → JSON sidecar map
├── debug_info.rs DWARF debug-info injection for `--debug-info` (lldb/gdb source mapping)
├── termination.rs Structured terminal-effect analysis shared by checker and optimizer
├── names.rs Qualified/FQN name model + assembly symbol mangling
├── name_resolver/ Namespace/use resolution to canonical names
├── pdo_prelude.rs PDO standard-library prelude injection entry point
├── pdo_prelude/ PDO driver detection from the DSN prefix
├── mysqli_prelude.rs mysqli prelude injection entry point (over the shared elephc_pdo bridge)
├── mysqli_prelude/ mysqli connection, statement, result, procedural, and detection surfaces
├── tz_prelude.rs Timezone-introspection prelude injection entry point
├── tz_prelude/ Timezone-introspection prelude usage detection
├── list_id_prelude.rs DateTimeZone identifier-list prelude injection entry point
├── list_id_prelude/ Identifier-list prelude detection and baked table data
├── var_export_prelude.rs var_export prelude injection entry point
├── var_export_prelude/ var_export prelude usage detection
├── dom_html_prelude.rs Pay-for-use DOM HTML injection before name resolution and after autoload
├── dom_html_prelude/ Cached declarations, HTML entity data, and DOM usage detection
├── image_prelude.rs Image (GD, Exif/IPTC, Imagick, Gmagick, Cairo) prelude injection entry point
├── image_prelude/ Image prelude usage detection
├── web_prelude.rs --web request superglobals and session prelude injection (flag-gated, not usage-detected)
├── web_prelude/ Web-prelude function-reachability analysis for pay-for-use injection
├── superglobals.rs Canonical --web request-superglobal set shared by checker, IR lowering, and runtime reset
│
├── lexer/
│ ├── mod.rs tokenize() -> Vec<SpannedToken> (Token + TokenMetadata)
│ ├── token.rs Token enum
│ ├── cursor.rs Byte-level source reader
│ ├── scan.rs Main scanning loop, operators
│ ├── literals.rs String, number, variable, keyword scanning entry point
│ └── literals/ Identifier, number, and string literal scanners
│
├── parser/
│ ├── mod.rs parse() → Program
│ ├── ast/ ExprKind, StmtKind, BinOp, CastType
│ ├── expr/ Pratt parser passes and expression helpers
│ ├── stmt/ Statement parsing, assignment, functions, OOP, namespaces, FFI
│ └── control.rs if, while, for, do-while, foreach, try/catch/finally
│
├── types/
│ ├── mod.rs Public checker entry point and type exports
│ ├── model.rs PhpType enum and TypeEnv
│ ├── result.rs CheckResult and semantic metadata returned by the checker
│ ├── schema.rs Class/interface/enum/trait metadata models
│ ├── signatures.rs Built-in call signatures and first-class callable wrappers
│ ├── call_args/ Shared named/spread call-argument planner
│ ├── array_keys.rs PHP array-key normalization helpers
│ ├── ffi.rs C-facing extern type models
│ ├── fibers.rs Fiber callback validation helpers
│ ├── traits.rs Trait flattening and conflict-resolution helpers
│ ├── traits/ Trait expansion, merge, and validation helpers
│ ├── warnings/ Non-fatal diagnostics (unused vars, unreachable code)
│ └── checker/
│ ├── mod.rs Type-checker orchestration boundary
│ ├── driver/ Main checker driver and program passes
│ ├── builtin_interfaces.rs Built-in SPL/core interface injection
│ ├── builtin_iterators.rs Built-in Iterator / IteratorAggregate metadata
│ ├── builtin_json.rs JsonException / JsonSerializable metadata
│ ├── builtin_spl_classes.rs SPL class metadata orchestration
│ ├── builtin_spl_classes/ Focused SPL container and iterator metadata builders
│ ├── builtin_spl_exceptions.rs SPL exception hierarchy metadata
│ ├── builtin_stdclass.rs stdClass dynamic-property metadata
│ ├── builtin_types/ Shared builtin class/type helper predicates
│ ├── builtins/ Registry integration plus compiler-resident language-construct checks
│ ├── callables/ Closure, extern-callable, and first-class callable signature resolution
│ ├── extern_decl.rs Extern declaration validation
│ ├── functions.rs Function-checking module root / orchestration
│ ├── functions/ Call validation, signature resolution, return collection
│ ├── inference/ Focused expression and object inference helpers
│ ├── method_pass.rs Method pre-declaration and override checking
│ ├── schema/ Class, interface, enum, and declaration validation
│ ├── stmt_check.rs Statement-checking module root
│ ├── stmt_check/ Assignment and control-flow statement checks
│ ├── type_compat.rs Type-compatibility module root
│ ├── type_compat/ Declaration, object, pointer, and union compatibility helpers
│ ├── yield_validation/ Generator return coercion and yield-scope validation
│ └── ...
│
├── codegen/
│ ├── mod.rs Active EIR → target assembly entry point
│ ├── context.rs Per-function lowering state and value placement
│ ├── shared_state.rs Module-wide artifacts shared across function contexts (callable descriptor/wrapper/invoker dedup)
│ ├── local_analysis.rs Precomputed local-slot facts (explicit stores, ref-cell representation, owned parameters)
│ ├── frame.rs EIR frame layout and local/temporary slots
│ ├── frame/ Frame-layout unit tests
│ ├── stack_numeric.rs Escape/lifetime proofs for frame-local checked numeric cells
│ ├── stack_numeric/ Numeric frame-cell proof unit tests
│ ├── block_emit.rs Basic-block scheduling and emission
│ ├── block_layout.rs Hot/cold block order and compare-and-branch fusion
│ ├── parallel_moves.rs Direct scalar moves for integer, boolean, and float block arguments
│ ├── machine_peephole.rs Late cleanup of redundant moves, reloads, and jumps to the next label
│ ├── lower_inst.rs EIR instruction lowering dispatcher
│ ├── lower_inst/ Target-aware instruction, typed runtime-target, callable, object, ownership, conversion, and constant-multiply strength-reduction lowerers
│ ├── lower_inst/runtime_calls.rs Typed RuntimeCallTarget dispatcher with no PHP-name lookup
│ ├── lower_inst/runtime_functions/ Bounded RuntimeFnId backend implementation groups
│ ├── lower_term.rs EIR terminator lowering
│ ├── value_placement.rs Linear-scan register and stack value placement
│ ├── runtime_callable_invoker.rs Runtime callable-descriptor invocation lowering
│ ├── function_variants.rs Include-loaded function-variant dispatcher emission
│ ├── literal_defaults.rs Literal property defaults → backend-native values
│ ├── eval_*_helpers.rs Eval-to-native bridge helpers: arguments, callables, class constants, constructors, error handlers, methods, properties, ref args, reflection (+ owners), static properties, and values (13 files)
│ ├── shared_*.rs Shared once-per-program helper frames: the count() TypeError guard, mbstring callable dispatch, the boxed-mixed __toString ladder, their common helper-frame plumbing, and the module-wide state that dedupes callable descriptors and owns the label counter (5 files)
│ ├── fibers.rs Fiber-aware EIR codegen integration
│ └── web.rs `--web` program-entry lowering
│
├── codegen_support/
│ ├── mod.rs Shared codegen metadata registries and support re-exports
│ ├── driver_support.rs Runtime object, deferred callable, boxing, and hash-key helpers
│ ├── arrays.rs Shared array value-type metadata stamping helpers
│ ├── callable_descriptor.rs Callable descriptor metadata and materialization
│ ├── callable_dispatch.rs Runtime callable dispatch-table emission
│ ├── callable_invoker_args.rs Descriptor-invoker argument cloning and Mixed boxing helpers
│ ├── compilation_context.rs Compile-scoped target, data, runtime-feature, and metadata state
│ ├── declaration_order.rs Stable declaration ordering shared by metadata emitters
│ ├── emitted_classes.rs Emitted-class selection and class-table metadata
│ ├── value_boxing.rs Shared runtime-value and owned-value boxing into Mixed cells
│ ├── wrappers/ Shared callback and fiber wrapper emitters
│ ├── interface_wrappers.rs Interface dispatch return-shape adapters
│ ├── dynamic_new.rs Builtin-class allow-list metadata for dynamic object construction
│ ├── hash_crypto.rs `hash()` / `hash_hmac()` routing through the elephc-crypto staticlib
│ ├── iconv_bridge.rs `iconv*()` entry-point publication into runtime function-pointer slots
│ ├── bcmath.rs BCMath bridge entry-point publication and call lowering
│ ├── curl.rs Curl bridge entry-point publication and runtime slots
│ ├── phar_stream.rs `phar://` URL and PHAR archive metadata parsing for I/O lowering
│ ├── runtime_features.rs Runtime helper-family derivation keeping optional native link deps pay-for-use
│ ├── stream_filters/ zlib/bzip2/iconv stream-filter attachment helper emitters
│ ├── tls.rs TLS bridge entry-point publication into runtime function-pointer slots
│ ├── try_handlers.rs Stack-layout constants for EIR exception-handler slots
│ ├── reflection.rs Shared ReflectionAttribute materialization helpers
│ ├── prescan.rs Constant pre-scan feeding EIR lowering
│ ├── program_usage.rs Required-class analysis feeding metadata emission
│ ├── program_usage/ Required-class scanners
│ ├── abi/ Target-aware calling convention helpers
│ │ ├── mod.rs Public ABI helpers
│ │ ├── bootstrap.rs Program entry / bootstrap helpers
│ │ ├── calls/ Call-site argument / result wiring
│ │ ├── frame.rs Stack frame prologue / epilogue
│ │ ├── registers.rs Register names per architecture
│ │ ├── symbols.rs Symbol / literal address loading
│ │ ├── tests.rs ABI unit-test module root
│ │ ├── tests/ ABI unit tests (`basics.rs`, `arguments.rs`, `symbols.rs`, `linux_x86_64.rs`)
│ │ └── values.rs Push/pop/load/store by PhpType
│ ├── platform/ Target selection and Linux transforms
│ │ ├── mod.rs Platform module root, re-exports Platform / Arch / Target
│ │ ├── target.rs Platform / Arch / Target definitions and derived codegen properties
│ │ ├── linux_transform.rs Linux post-emit transforms, syscall mapping, C-symbol remapping
│ │ └── toolchain.rs Assembler / linker invocation
│ ├── cdylib.rs Owned-string boundary orchestration + lifecycle/status/error/memory symbols
│ ├── cdylib/boundary.rs Recoverable scalar wrappers + nested boundary/concat state
│ ├── cdylib/owned_string.rs Caller-owned binary-string ABI helpers
│ ├── visibility.rs ELF hidden / Mach-O private visibility for internal cdylib globals
│ ├── sentinels.rs Null representation selection (sentinel vs tagged) and constants
│ ├── data_section.rs String/float literal .data section
│ ├── emit.rs Assembly text buffer plus independent PIC/cdylib-boundary modes
│ │
│ └── runtime/ Runtime routines and target-specific emission helpers
│ ├── mod.rs Runtime module boundary; re-exports the emission entry points
│ ├── data/ Fixed, user-program, and instanceof runtime data tables (6 files)
│ ├── diagnostics.rs Suppressible runtime-warning channel used by `@`
│ ├── eval_bridge.rs C-ABI value, callable, class, and runtime hooks used by Magician
│ ├── eval_scope.rs Core materialized-scope helpers usable without the interpreter
│ ├── emitters.rs `emit_runtime()` orchestration — emits every runtime category in a fixed order
│ ├── emitters/ Managed-value and platform-facing runtime orchestration (3 files)
│ ├── numeric.rs Shared numeric parsing and conversion emitters
│ ├── resource_ids.rs Runtime resource-kind identifiers shared by cleanup paths
│ ├── round_mode.rs PHP rounding-mode constants used by runtime helpers
│ ├── sysv_call_alignment.rs x86_64 SysV nested-call stack-alignment helpers
│ ├── strings/ itoa, concat, resource display, ftoa, sprintf, hashes, iconv, mbstring, parse_url, and conversion helpers (106 top-level Rust files + 62 nested Rust helpers)
│ ├── arrays/ heap_alloc, heap_free, array_free_deep, array_grow, hash_grow, hash_*, mixed boxing/freeing, mixed instanceof, sort, usort, refcount, gc/decref dispatch, ... (210 top-level Rust files + 9 nested target/debug/test helpers)
│ ├── callables/ Runtime `is_callable()` fallback for dynamic strings/arrays/hashes/objects/Mixed, callable descriptor release, and `Closure::bind` support (7 files)
│ ├── compare/ Loose/strict comparison and truthiness helpers (5 files)
│ ├── io/ fopen, fgets, fread, stat, streams, sockets, filters, scandir, ... (126 top-level files + 1 nested response-metadata helper)
│ ├── buffers/ Generation-safe handle resolution, allocation/free, length, bounds/size/use-after-free diagnostics (8 files incl. mod.rs)
│ ├── bcmath/ Target-aware C-ABI marshalling for exact decimal bridge calls (3 files incl. target assembly)
│ ├── curl/ Easy, multi, share, callback, multipart, error, and version bridge adapters (14 files)
│ ├── eval_bridge/ Magician value, array, cast, reflection, clone, mbstring, output, and builtin adapters (37 top-level files + 1 nested output helper)
│ ├── exceptions.rs Exception runtime module root / re-exports
│ ├── exceptions/ cleanup frames, handler dispatch, dynamic instanceof, matching, throwing, and Throwable helpers (23 top-level Rust files + 2 nested cleanup helpers)
│ ├── pdo/ Target-aware PDO callable callback adapters (5 files)
│ ├── system/ build_argv, time, getenv, shell_exec, date/JSON/strtotime, serialize/unserialize, preg_*, ... (50 top-level Rust files + 61 nested Rust files)
│ ├── pointers/ ptoa, ptr_check_nonnull, str_to_cstr, cstr_to_str, ptr_read_string, ptr_write_string, ... (7 files)
│ ├── fibers/ stack allocation/free, context switch, entry trampoline (4 top-level files) + `api/` (4 target-aware public API helper files)
│ ├── objects/ stdClass, object handles, Mixed property/index autovivification, object-vars/export, destructor dispatch, and new-by-name helpers (19 files)
│ ├── spl/ SplDoublyLinkedList and SplFixedArray runtime container helpers, including shared PHP offset conversion (4 files)
│ ├── generators/ Generator frame layout and fiber-backed coroutine __rt_gen_* helpers (3 files)
│ └── zval/ Zval bridge packing, unpacking, type, and lifetime helpers (11 files)
│
│
└── errors/
├── mod.rs CompileError, error trait
└── report.rs Error formatting
crates/
├── elephc-builtin-contract/ Dependency-neutral builtin catalog and signatures
├── elephc-bcmath/ Pure-Rust arbitrary-precision decimal bridge for PHP `bc*()` functions and immutable `BcMath\Number` objects
├── elephc-crypto/ Pure-Rust hashing/HMAC bridge staticlib behind PHP `hash()` / `hash_hmac()`
├── elephc-curl/ Static libcurl easy, multi, share, callback, and multipart bridge
├── elephc-generics/ Shared structural generic type terms, binding, and variance for AOT compilation and Magician
├── elephc-iconv/ Charset-conversion and RFC 2047 MIME bridge staticlib behind PHP `iconv*()`
├── elephc-image/ Pure-Rust image bridge staticlib (GD, Exif, Imagick, Gmagick, Cairo C ABI)
├── elephc-instr/ Exact profiling instrumentation runtime
├── elephc-magician/ Optional EvalIR parser/interpreter staticlib for dynamic eval
├── elephc-mbstring/ Shared mbstring engine and native bridge with managed Oniguruma-backed mbregex
├── elephc-monitoring-contract/ Typed monitoring policy shared by the compiler and every bridge
├── elephc-pcntl/ Unix process control, wait, exec, priority, and signal bridge staticlib
├── elephc-pdo/ Multi-driver database bridge staticlib behind the PDO prelude
├── elephc-phar/ Pure-Rust PHAR/tar/zip archive bridge for `phar://` runtime paths
├── elephc-probe/ Sampled profiling and authenticated service endpoint
├── elephc-tls/ TLS bridge for the `https://` stream wrapper
├── elephc-tz/ IANA timezone-introspection bridge staticlib with baked tz tables
├── elephc-web/ Prefork HTTP bridge with compile-time worker/pool/request isolation
└── elephc-xml/ ext/xml and ext/xmlwriter bridge over the catalog's static libxml2
ARM64 calling conventions
| What | Register | Notes |
|---|---|---|
| Integer result | x0 | After emit_expr for Int/Bool/Void/Resource |
| Float result | d0 | After emit_expr for Float |
| String result | x1 (ptr), x2 (len) | After emit_expr for Str |
| Array result | x0 (heap ptr) | After emit_expr for Array/AssocArray/Iterable |
| Mixed result | x0 (heap ptr) | Pointer to boxed mixed cell |
| Object result | x0 (heap ptr) | After emit_expr for Object |
| Pointer / Buffer / Packed / Callable result | x0 | Raw address, opaque generation-safe buffer handle, packed-record pointer, or callable descriptor pointer |
| Function args (int) | x0-x7 | Int/Bool/Resource/Array/AssocArray/Iterable/Mixed/Object/Pointer/Buffer/Packed/Callable/Union = 1 reg, Str = 2 regs |
| Function args (float) | d0-d7 | Separate index from int regs |
| Frame pointer | x29 | Saved in prologue |
| Link register | x30 | Saved in prologue |
| Stack locals | [x29, #-offset] | Negative offsets from frame pointer |
| Null sentinel | 0x7FFFFFFFFFFFFFFE | Distinguished from real integers |
FFI pipeline
FFI declarations are parsed into dedicated AST nodes:
StmtKind::ExternFunctionDeclStmtKind::ExternClassDeclStmtKind::ExternGlobalDecl
During type checking, extern declarations are registered in dedicated maps that are carried into codegen:
extern_functions: extern signatures exposed through the C ABIextern_classes: flat C struct layout metadataextern_globals: native global symbols loaded through the linker
The checker registers extern and packed classes before the interface and class schema passes,
because class members name them too (public buffer<Vec2> $data, public ptr<Point> $p, or a
generic argument such as Store<Vec2>). Duplicate detection against ordinary classes therefore
reads the pre-declared class names rather than the finished class table.
Extern calls differ from ordinary elephc function calls in four important ways:
- Codegen dispatches extern functions before built-ins, so an
extern function strlen(...)declaration really calls Cstrlen, not the elephc builtin. stringarguments are converted with__rt_str_to_cstr, which allocates a null-terminated C buffer that is valid for the duration of the native call and is released immediately after the call returns. A?stringparameter (ExternAbiType::NullableCStr) passes PHP null as a NULLchar *instead, deciding a boxedstring|nullargument from its runtime tag; its cleanup slot then holds NULL, which__rt_heap_freeignores. A boxed array or object is a fatal error there, so no PHP code (__toString(), an error handler) can throw while earlier arguments’ C copies exist only on the temporary stack.stringreturn values are converted with__rt_cstr_to_str, which treats the returnedchar *as borrowed and copies bytes back into an owned elephc string. A?stringreturn boxes a NULLchar *as PHP null and moves the owned copy of any other pointer into a Mixed box, because the call is typedstring|null.extern classlayouts are available to pointer-oriented codegen too, soptr_sizeof("StructName")andptr_cast<StructName>($p)->fielduse the same checked layout metadata recorded by the type checker. The checker lays fields out with natural C alignment (ctype_field_layoutinsrc/types/ffi.rs) and keeps each field’s C shape (ExternFieldInfo::abi), andsrc/codegen/lower_inst/objects/extern_fields.rsturnsprop_get/prop_seton aptr<StructName>receiver into width-aware loads and stores, the extern sibling of the packed-classptr<T>route.
callable FFI parameters pass a user-defined elephc function by address. The function name is provided as a string literal at the call site, and codegen loads the address of the compiled mangled function symbol before branching into C. Namespaced functions therefore still map to unique assembly labels after canonicalization.
Namespace resolution and symbol mangling
Namespace syntax is preserved through parsing and include resolution, then normalized by src/name_resolver/ before type checking or codegen sees the program. That pass:
- tracks the current
namespacescope - applies
use,use function, anduse constaliases, including group-use forms - resolves class/interface/trait/function/constant references to canonical fully-qualified names
- rewrites supported string-literal callbacks such as
call_user_func("name", ...)to the resolved target name;function_exists($name)keeps PHP’s introspection semantics instead — a literal name is not namespace-resolved, and a dynamic name is matched at run time against the declared-function set baked into the binary - flattens namespace-only AST statements so downstream passes operate on a simpler canonical AST
src/names.rs is the shared utility layer for this work. It defines the internal Name representation plus common helpers for:
- canonical declaration names
- namespace qualification rules
- assembly symbol mangling for functions, methods, and static storage
Because codegen receives canonical names, namespaces do not require special cases in most later passes: mangled labels are derived centrally from the final fully-qualified name.
First-class callable syntax rides on the same canonical naming pipeline. The parser emits a dedicated callable-target node, the checker validates the target statically, and codegen lowers it to a synthesized wrapper function plus a static callable descriptor. Instance-method targets and static:: targets use the same hidden-capture channel as closures to forward the receiver or called-class context into that wrapper; indirect call sites load the descriptor’s entry slot before invoking the wrapper.
Runtime memory layout
Array header (heap-allocated)
Offset Size Field
0 8 length (current number of elements)
8 8 capacity (allocated slots)
16 8 elem_size (8 for Int, 16 for Str)
24 ... elements (contiguous)
Buffer handle and descriptor registry (for buffer<T>)
Public 64-bit handle: [generation:u32][descriptor index:u32]
Descriptor offset Size Field
0 8 payload pointer
8 8 logical element count
16 8 element stride
24 8 generation slot (low u32 used)
32 8 active marker
40 8 free-list successor index
buffer<T> is deliberately separate from the PHP array/hash runtime path. The static registry has 4096 usable 48-byte descriptors plus reserved index zero. Every length, read, write, and free operation resolves the handle and requires a matching non-zero generation on an active descriptor before consulting its metadata. The payload is a separate allocation in the compiler-managed heap and contains exactly the zero-initialized length * stride bytes. Codegen then uses the checked static element type plus the descriptor stride to emit direct address arithmetic and scalar loads/stores, or typed packed-field access for buffer<PackedType>. Freeing invalidates the descriptor before releasing the detached payload; eligible slots are recycled with an incremented generation so stale aliases cannot revive.
match expressions stay in the normal expression pipeline. When the source omits default, codegen now emits a branch to a dedicated runtime fatal helper (__rt_match_unhandled) instead of falling through to an undefined result.
Runtime BSS and data symbols
The runtime data emission in src/codegen_support/runtime/data/ is split into emit_runtime_data_fixed() for shared heap buffers, diagnostics, and lookup tables, plus emit_runtime_data_user() for globals, statics, enum-case storage, metadata derived from the user’s program, and dynamic instanceof lookup names:
| Symbol group | Symbols | Purpose |
|---|---|---|
| String scratch | _concat_buf, _concat_off | Temporary string results for expression evaluation |
| CLI globals | _global_argc, _global_argv | Saved OS argument state used to build $argv |
| Heap allocator | _heap_buf, _heap_off, _heap_free_list, _heap_small_bins, _heap_debug_enabled, _heap_max | Heap storage plus general/small-bin allocator metadata and heap-debug toggle |
| Buffer registry | _buffer_registry, _buffer_registry_free, _buffer_registry_next | Static generation-safe descriptors, recycled-slot free-list head, and next never-issued descriptor index |
| Runtime diagnostics | _rt_diag_suppression, _rt_diag_pending_*, _php_diagnostic_*, _diag_*, _heap_err_msg, _arr_cap_err_msg, _ptr_null_err_msg, _buffer_bounds_msg, _buffer_uaf_msg, _buffer_alloc_size_msg, _buffer_registry_exhausted_msg, _match_unhandled_msg, _uncaught_exc_msg, _instanceof_target_type_msg, _heap_dbg_* | Suppressible and deferred warning state/text plus source locations, fatal messages, and heap-debug summary/failure strings |
| GC statistics and cycle state | _gc_allocs, _gc_frees, _gc_live, _gc_peak, _gc_enabled, _gc_collecting, _gc_freeing_unreachable, _gc_pin_head, _gc_pending_throw, _gc_release_suppressed, _gc_runs, _gc_collected, _gc_application_started, _gc_*_started, _gc_*_time, _gc_destructor_depth | Allocation/free/live-byte counters, collector lifecycle and timing state, pinned roots, and deferred throws |
| Exception and handler state | _exc_handler_top, _exc_call_frame_top, _exc_value, _magic_set_guard_head, _php_error_handler_*, _php_exception_handler_*, _class_parent_ids | Active exception and cleanup stacks, current user handlers with heap-backed restore stacks, magic-set recursion guard, and parent links used for catch matching |
| Reference and resource tracking | _rt_unmanaged_ref_borrow_top, _resource_inventory_head, _resource_inventory_tail | Active unmanaged array-element borrow plus the heap-backed resource-incarnation inventory used by get_resources() |
| Include-once guards | _include_once_<hash> | Per-resolved-file loaded flags used by include_once / require_once runtime guards |
| Include-loaded function variants | _fn_variant_active_<function> | Active hidden implementation pointer for a function loaded through an include point |
| I/O scratch | _cstr_buf, _cstr_buf2, _eof_flags, _principal_lookup_buf, _etc_passwd_path, _etc_group_path, _principal_lookup_read_mode | Syscall-oriented C-string scratch buffers, EOF bookkeeping, and passwd/group lookup state for chown() / chgrp() name resolution |
| String/runtime tables | _fmt_g, _b64_encode_tbl, _b64_decode_tbl | Formatting and lookup tables for runtime helpers |
| JSON/date state and tables | _json_last_error, _json_active_flags, _json_active_depth, _json_indent_depth, _json_depth_limit, _json_validate_*, _json_decode_assoc, _json_error_*, _json_true, _json_false, _json_null, _json_err_msg_*, _json_err_msg_table, _json_err_loc_*, _json_int_max_str, _json_int_min_str, _day_names, _month_names, _strtotime_* | Runtime JSON state, JSON literal/error lookup data, decode error-location fragments, bigint thresholds, date lookup tables, and strtotime() keyword/unit tables |
| User-dependent storage | _gvar_<name>, _static_<func>_<name>, _static_<func>_<name>_init, _static_prop_<class>_<prop>, enum-case .comm symbols via enum_case_symbol(...) | Global/static local storage, class static-property storage, plus singleton backing slots for enum cases |
| Class/interface metadata tables | _instanceof_target_count, _instanceof_target_entries, _instanceof_name_*, _interface_count, _interface_method_ptrs, _interface_methods_<id>, _class_interface_ptrs, _class_interfaces_<id>, _class_interface_impl_<class>_<iface>, _classes_by_name, _classes_by_name_count, _generator_class_id, _fiber_class_id, _fiber_error_class_id, _class_gc_desc_count, _class_gc_desc_ptrs, _class_gc_desc_<id>, _class_destruct_ptrs, _class_vtable_ptrs, _class_vtable_<id>, _class_static_vtable_ptrs, _class_static_vtable_<id>, _class_tostring_count, _class_tostring_ptrs, _class_iface_method_count, _class_serprop_declaring_ptrs, _class_serprop_declaring_missing, _class_serprop_declaring_<id> | Dynamic instanceof lookup names, case-insensitive new $name() class lookup table, built-in runtime-managed class ids, per-interface method-order metadata, per-class property traversal metadata, per-class __destruct pointers, and instance/static dispatch tables |
Heap allocator
8MB free-list + bump hybrid allocator in BSS (_heap_buf). Each allocation has a uniform 16-byte header: [size:4][refcount:4][kind:8] — a 32-bit block size, a 32-bit reference count, and an 8-byte heap-kind tag shared by arrays, hashes, objects, boxed mixed cells, persisted strings, and raw helper buffers. The allocator now keeps four segregated small-block bins (<=8, <=16, <=32, <=64 bytes) in _heap_small_bins ahead of the general address-ordered free list, so tiny short-lived blocks can often be reused without walking the full first-fit chain. When memory is freed (via __rt_heap_free), tail blocks still fold directly back into _heap_off, small non-tail blocks are cached in their size class, and larger blocks remain in the ordered free list where adjacent neighbors are coalesced and any free chain that reaches the current bump tail is trimmed back into the bump pointer. New allocations consult the matching small-bin class first, then the general free list (splitting oversized free blocks when needed), and only bump allocate when neither path can satisfy the request. Reference counting (__rt_incref, __rt_decref_array, __rt_decref_hash, __rt_decref_mixed, __rt_decref_object) still handles the common acyclic case, while arrays and hashes now add copy-on-write splitting through __rt_array_ensure_unique / __rt_hash_ensure_unique plus shallow clone helpers before mutating shared containers. The low 16 bits of the kind word are now persistent container metadata: low byte = heap kind, bits 8-14 = indexed-array value_type, bit 15 = copy-on-write container flag, and higher bits remain reserved for transient cycle-collector state. Heap kind tags now use 0=raw/untyped, 1=string, 2=indexed array, 3=assoc/hash, 4=object, 5=boxed mixed, giving the runtime a uniform discriminator regardless of payload layout. With --heap-debug, the runtime validates both the ordered free list and the segregated small-bin chains on allocator/free mutations, traps on double free or zero-refcount incref/decref paths, poisons freed payload bytes, and prints an end-of-process summary with alloc/free counts, live blocks, live bytes, and the peak live-byte watermark. With --gc-stats, generated programs also print allocation/free counters to stderr at exit without enabling the heavier heap-debug checks. Fiber execution state uses dedicated BSS slots (_fiber_current, _fiber_main_saved_sp, _fiber_main_saved_exc, _fiber_main_saved_call_frame) plus guarded per-fiber stacks allocated with mmap; Fiber objects release those stacks through __rt_fiber_free_stack during object deep-free. Codegen now records a local ownership lattice (Owned, Borrowed, MaybeOwned, NonHeap) plus an epilogue_cleanup_safe bit in Context::variables so it can distinguish between stack slots that truly own a heap value and slots that merely alias global/static/container-backed storage. Ownership transfer points currently include ordinary reassignments, by-value call arguments, borrowed heap returns, indexed array writes, associative-array/hash writes, object property writes, static slot writes, global loads, foreach targets, and list(...) targets, while container-copy builtins now dispatch to dedicated _refcounted runtime helpers for nested array/hash/object/string payloads (array literals with spreads, array_merge, array_chunk, array_slice, array_reverse, array_pad, array_unique, array_splice, array_diff, array_intersect, array_filter, array_fill, array_combine, array_fill_keys). Mixed heap releases now funnel through __rt_decref_any, and object/container deep-free paths use richer runtime metadata plus per-class GC descriptor tables to discover nested heap-backed children. Function epilogues now clean up only locals that are both Owned and still marked safe; borrowed aliases such as $this, ref params, globals, and statics are explicitly excluded, and exhaustive if / elseif / else branches can now restore epilogue cleanup when every fallthrough branch directly stores the same heap-backed type into the same local. Loop-driven, switch-driven, and more dynamic alias-heavy joins remain conservative until more control-flow cases are proven. Configurable via --heap-size=BYTES (minimum 64KB), --gc-stats, and --heap-debug for runtime verification. Bounds-checked with fatal error on overflow.
Hash table header (heap-allocated, for associative arrays)
Offset Size Field
0 8 count (number of occupied entries)
8 8 capacity (number of slots)
16 8 value_type (coarse summary: 0=int, 1=str, 2=float, 3=bool, 4=array, 5=assoc, 6=object, 7=mixed, 8=null)
24 8 head (slot index of first inserted entry, or -1)
32 8 tail (slot index of last inserted entry, or -1)
40 8 entries (owned pointer to separate storage, each entry is 64 bytes)
48 8 pins (physical lifetime roots excluded from PHP copy-on-write ownership)
Each hash table entry:
Offset Size Field
0 8 occupied (0=empty, 1=occupied, 2=tombstone)
8 8 key_ptr (pointer to key string)
16 8 key_len (key string length)
24 8 value_lo (value or pointer)
32 8 value_hi (string length, or unused for single-word payloads)
40 8 value_tag (authoritative per-entry runtime tag)
48 8 prev (previous inserted slot, or -1)
56 8 next (next inserted slot, or -1)
Lookups still use FNV-1a hashing with linear probing for collision resolution, but language-visible iteration follows the head -> next -> ... -> tail insertion-order chain. The header value_type is now only a coarse summary; correctness-critical runtime paths read each entry’s value_tag instead. For the full runtime layout and iteration contract, see Memory Model.
Object layout (heap-allocated)
Offset Size Field
0 8 class_id (identifies which class this object belongs to)
8 16 prop[0] (first property — 16 bytes regardless of type)
24 16 prop[1] (second property)
... ... ...
Total size: 8 + (num_properties × 16). Properties are stored at fixed offsets determined at compile time in parent-first order across the inheritance chain. Property access is base + resolved_property_offset.
Method dispatch
- Instance methods: codegen resolves a stable slot number from the static class metadata, then uses the object’s
class_idto load the concrete class vtable entry andblrto the implementation. The object pointer is still passed as the first argument inx0(as$this). - Private instance methods are excluded from the vtable and emitted as direct calls within the declaring class, preserving PHP’s lexical binding for parent-private helpers.
- For non-generic receiver classes, final methods and methods on a statically known final class call the emitted implementation directly, including inherited implementations. Generic instantiations retain their vtable slots because variance can pass a different instantiation with its own lexical body and static storage. Literal-parameter guards on final generic declarations select the actual runtime class without requiring an inheritance edge, preserving the declaring instantiation’s name in
TypeErrormessages. Ordinary->calls on overridable base-class receivers retain their hierarchy-based guards and vtable dispatch. The same rule applies to nullable receivers after their null guard and uses the target’s ordinary physical or source ABI entry. A method-defined closure captures$thisas boxedMixed, so rebinding selects the actual runtime class before this rule applies. - Interfaces and abstract classes are enforced at compile time. Runtime method calls still use the existing class vtables, while dedicated interface metadata tables are emitted alongside the class metadata for roadmap-aligned interface bookkeeping and future dispatch work.
- Static methods:
bl _static_ClassName_methodName. No object pointer is passed. self::method()/parent::method(): emitted as direct lexical calls, but static targets still forward the current “called class” id for laterstatic::lookups.static::method(): uses a per-class static-method table keyed by the forwarded called-class id, so late static binding works across inherited static overrides.- Undefined property reads can fall back to
__get($name), and undefined property writes can fall back to__set($name, $value), reusing the same instance-method dispatch path once the type checker has validated those magic methods. - Object values used in string contexts can fall back to
__toString(), which is enforced by the type checker and lowered through the same instance-method dispatch machinery.
Traits are flattened into the owning class before inheritance metadata is built. That means trait members participate in the same inherited property layout and vtable construction as ordinary class members after use / as / insteadof resolution.
String buffer
64KB scratch buffer in BSS (_concat_buf). Used by itoa, concat, strtolower, and all string-producing runtime routines. Reset to offset 0 at the start of each statement. Strings that need to persist beyond the current statement are copied to the heap via __rt_str_persist.
I/O buffers
Two 4KB C-string conversion buffers (_cstr_buf, _cstr_buf2) are still used by low-level I/O helpers and syscalls. FFI string calls do not use these scratch buffers anymore; they allocate per-call C strings through __rt_str_to_cstr so multiple string arguments remain valid across the same native call and are then released as soon as control returns from C. A 256-byte EOF flag array (_eof_flags) tracks end-of-file state per file descriptor. chown() / chgrp() string-name variants resolve local principals by scanning /etc/passwd and /etc/group through _principal_lookup_buf and fixed path/mode literals, avoiding NSS calls in static Linux binaries.