Compilation pipeline
PHP source (.php)
│
▼
┌─────────┐
│ Lexer │ src/lexer/
│ │ scan.rs, literals.rs, cursor.rs, token.rs
│ │ Source text → Vec<(Token, Span)>
└────┬─────┘
│
▼
┌─────────┐
│ 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,web,version}_prelude*
│ │ Injects only the compiler-owned PHP surfaces required by
│ │ resolved source usage, forced bridge flags, or --web.
└─────┬────────┘
│
▼
┌──────────────┐
│ 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.
└────┬─────┘
│
▼
┌──────────────┐
│ 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/
│ Checker │ traits.rs, checker/mod.rs, checker/builtins/, checker/functions/, warnings/
│ │ 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, 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) before codegen.
└──────┬──────┘
│
▼
┌─────────────┐
│ 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.
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
├── pipeline.rs Frontend/backend compilation pipeline
├── 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, 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
├── 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, constant folding, common-subexpression elimination, loop-invariant code motion, dead-instruction elimination, dead-store elimination, branch simplification, the cross-function small-function inliner (run to a module-level fixed point), dominance analysis, loop analysis, and linear-scan register allocation
├── 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
├── 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<(Token, Span)>
│ ├── 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
│ ├── block_emit.rs Basic-block scheduling and emission
│ ├── lower_inst.rs EIR instruction lowering dispatcher
│ ├── lower_inst/ Target-aware instruction, typed runtime-target, callable, object, ownership, and conversion 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: callables, class constants, constructors, methods, properties, ref args, reflection (+ owners), static properties (9 files)
│ ├── shared_*.rs Shared once-per-program helper frames: the count() TypeError guard, the boxed-mixed __toString ladder, their common helper-frame plumbing, and the module-wide state that dedupes callable descriptors and owns the label counter (4 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 (4 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, and conversion helpers (97 top-level files + iconv/ target-neutral bridge helpers, 4 files)
│ ├── arrays/ heap_alloc, heap_free, array_free_deep, array_grow, hash_grow, hash_*, mixed boxing/freeing, mixed instanceof, sort, usort, refcount, gc/decref dispatch, ... (175 files + hash_sort/ target split, 2 files)
│ ├── callables/ Runtime `is_callable()` fallback for dynamic strings/arrays/hashes/objects/Mixed, callable descriptor release, and `Closure::bind` support (5 files)
│ ├── compare/ Loose/strict comparison and truthiness helpers (5 files)
│ ├── io/ fopen, fgets, fread, stat, streams, sockets, filters, scandir, ... (121 files)
│ ├── 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, and builtin adapters (23 files)
│ ├── exceptions.rs Exception runtime module root / re-exports
│ ├── exceptions/ cleanup_frames, dynamic_instanceof, matches, throw_current, rethrow_current, class_implements helpers (7 files)
│ ├── pdo/ Target-aware PDO callable callback adapters (5 files)
│ ├── system/ build_argv, time, getenv, shell_exec, date/JSON/strtotime, serialize/unserialize, preg_*, ... (43 top-level files + 38 files under 6 subdirectories)
│ ├── 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 (15 files)
│ ├── spl/ SplDoublyLinkedList and SplFixedArray runtime container helpers (3 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
├── 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-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-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
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.stringreturn values are converted with__rt_cstr_to_str, which treats the returnedchar *as borrowed and copies bytes back into an owned elephc string.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.
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, _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 warning state/text plus fatal error messages and heap-debug summary/failure strings |
| GC statistics and cycle state | _gc_allocs, _gc_frees, _gc_live, _gc_peak, _gc_collecting, _gc_release_suppressed | Allocation/free/live-byte counters plus targeted-cycle-collector coordination flags |
| Exception state | _exc_handler_top, _exc_call_frame_top, _exc_value, _class_parent_ids | Active handler stack, activation cleanup stack, current exception object, and parent links used for catch matching |
| 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 ... entries (each entry is 64 bytes)
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.
- 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.