How TypePHP Achieves Deterministic Multi-File and Self-Hosted Builds
TypePHP guarantees deterministic builds by separating project analysis from code generation into two distinct phases, ensuring that output depends only on source content and configuration, never on file-system ordering or environment state.
The swoole/typephp repository implements a deterministic compilation strategy for transpiling PHP to C++. By resolving all symbols before emitting any code and using fixed type mappings in self-hosted scenarios, the toolchain produces bit-identical outputs across different machines and build runs.
The Two-Phase Compilation Architecture
TypePHP’s deterministic behavior stems from a strict two-phase design that keeps multi-file and self-hosted builds reproducible.
Phase 1: Project Analysis and Symbol Resolution
During the first phase, the compiler performs a complete, order-independent scan of the input sources. In src/compiler.php, the toolchain loads each file listed in project.yml and registers every class, function, constant, and trait into a global Context object. This creates a fully resolved symbol table before any C++ code is generated. Because the symbol table is complete before emission begins, changes in file system ordering or timestamps cannot alter the generated output.
Phase 2: Deterministic Code Generation
Only after the entire project has been collected does src/CompilerBase.php begin the second phase: walking the abstract syntax trees (ASTs) and emitting C++ code. Since all symbols are already resolved, the code generator operates on a stable, in-memory representation rather than querying the file system incrementally. This separation ensures that identical source trees always produce identical C++ source files.
Multi-File Deterministic Builds
For projects spanning multiple PHP files, TypePHP uses a configuration-driven pipeline to maintain determinism.
Configuration via project.yml
The command-line tool bin/tpc.php reads a project.yml file that explicitly lists all source files and compiler options. By centralizing the file list and settings in a version-controlled configuration, the build process eliminates non-determinism introduced by shell globbing or directory traversal order.
# project.yml – deterministic build manifest
sources:
- src/Foo.php
- src/Bar.php
- src/Internal/Traits.php
options:
php_version: "8.2"
optimize: true
The Compilation Pipeline
When bin/tpc.php executes, it invokes the logic in src/compiler.php to process each entry in the sources list sequentially. The compiler parses each file into an AST and populates the Context symbol table. Only after the last file is parsed does src/CompilerBase.php begin emission. This guarantees that adding, removing, or reordering files in the directory never changes the generated C++ beyond the intentional structural changes in the source code itself.
# Multi-file deterministic build
php bin/tpc.php --config project.yml
Self-Hosted Deterministic Builds
TypePHP also supports self-hosted builds, where a single PHP file is compiled into a standalone C++ binary, useful for bootstrapping the compiler itself.
Single-File Compilation with bin/bootstrap.php
The entry point bin/bootstrap.php handles self-hosted compilation by treating the input as a closed system. Unlike multi-file builds that merge symbols from many sources, self-hosted mode compiles one file with strict assumptions about type stability.
Fixed Type Assignment in CompilerBase.php
To ensure determinism when compiling a single file, src/CompilerBase.php assigns one fixed C++ type to each PHP local variable at compile time. According to comments in src/CompilerBase.php near lines 95–96, this fixed assignment prevents runtime type inference variations from affecting the generated code. Because every variable maps to a predetermined C++ type, the resulting binary behaves identically across every build invocation.
# Self-hosted deterministic build
php bin/bootstrap.php myscript.php
Practical Examples
Both build modes use the same underlying CompilerBase logic but differ in how they resolve symbols:
Multi-file workflow:
// src/Foo.php
class Foo {
public function bar(): int {
return 42;
}
}
$ php bin/tpc.php --config project.yml
# Generates deterministic C++ output in build/
Self-hosted workflow:
$ php bin/bootstrap.php --input compiler.php --output typephp.cpp
# Produces identical typephp.cpp on every run
Summary
- Two-phase design separates symbol resolution from code generation, preventing file-system ordering from affecting output.
- project.yml acts as a deterministic manifest for multi-file builds, explicitly listing sources and options in
bin/tpc.php. - src/compiler.php builds a complete Context symbol table before
src/CompilerBase.phpemits any C++. - Self-hosted builds via
bin/bootstrap.phpuse fixed C++ type assignments for local variables, ensuring single-file determinism. - Identical inputs always produce identical C++ outputs regardless of the host environment or build machine.
Frequently Asked Questions
What makes TypePHP builds deterministic compared to standard PHP compilation?
Standard PHP compilation happens at runtime with autoloading and dynamic class resolution, which can vary based on include paths and load order. TypePHP resolves all classes, functions, and constants into a static Context during a dedicated analysis phase before any code generation occurs. This pre-resolution ensures that the C++ output depends only on the actual source content and the explicit project.yml configuration, not on the order files are read from disk.
How does project.yml ensure repeatable multi-file builds?
The project.yml file acts as a build manifest that bin/tpc.php reads to discover source files. By explicitly listing files in a specific order and pinning compiler options like php_version, the configuration removes ambiguity from directory scanning or environment-specific defaults. Because the compiler processes the sources list sequentially and builds a complete symbol table before emission, rearranging entries in project.yml becomes the only way to alter build order, making the process transparent and version-controllable.
What is the difference between bin/tpc.php and bin/bootstrap.php?
bin/tpc.php is the standard CLI tool for multi-file projects; it reads project.yml, orchestrates the full compilation pipeline in src/compiler.php, and generates C++ code from multiple PHP sources. bin/bootstrap.php is the self-hosted compiler entry point designed for single-file transpilation, typically used to compile TypePHP itself. While both use src/CompilerBase.php for code generation, the bootstrapper enforces stricter type fixing and does not rely on external project configuration.
Why does self-hosted compilation require fixed C++ type assignment?
In self-hosted mode, the compiler must generate C++ that can be compiled into a standalone binary without a surrounding PHP runtime. By assigning a single, immutable C++ type to each PHP local variable—as noted in the implementation comments within src/CompilerBase.php—the compiler eliminates runtime type inference. This fixed mapping ensures that the generated C++ source is structurally identical on every compilation, which is essential for bootstrapping the compiler reproducibly across different host systems.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →