23 KiB
PeachPie Compiler Review: Roslyn Integration, Cross-Language Interop, and Type System
2026-06-11 · Based on source review of /home/swoole/workspace/cpp/peachpie
1. Architecture Overview
PeachPie is a PHP-to-.NET compiler based on Roslyn (Microsoft's .NET compiler platform), with roughly 710 C# source files and 272k lines of code.
Compilation Pipeline
PHP source
→ PhpSyntaxTree (Roslyn SyntaxTree for PHP — Syntax/)
→ SemanticModel + Symbols (Roslyn symbol system — Semantics/, Symbols/)
→ BoundControlFlowGraph (CFG with typed IR — Semantics/Graph/, FlowAnalysis/)
→ CIL Bytecode (EMIT — CodeGen/, Emitter/)
→ .NET Assembly (.dll / .exe)
| Stage | Component | Responsibility |
|---|---|---|
| Syntax parsing | Syntax/ (PhpSyntaxTree, NodesFactory) |
PHP → Roslyn SyntaxTree |
| Semantic binding | Semantics/ (SemanticsBinder, BoundExpression) |
Name resolution, method binding, type inference |
| Symbol system | Symbols/ (SourceTypeSymbol, PEMethodSymbol...) |
Roslyn symbol table for types/methods/properties |
| Dataflow analysis | FlowAnalysis/ (FlowState, TypeRefMask, ExpressionAnalysis) |
Type inference, unreachable code detection, conditional narrowing |
| CFG optimization | FlowAnalysis/Passes/ (TransformationRewriter) |
CFG rewriting, constantization, dead code elimination |
| Code generation | CodeGen/ (CodeGenerator, GhostMethodBuilder) |
IR → CIL instructions |
| Assembly output | Emitter/ (PEModuleBuilder) |
CIL → PE file (.dll/.exe) |
Core Files
| File | Lines | Purpose |
|---|---|---|
CodeGen/Graph/BoundExpression.cs |
5,742 | Core IR node definitions and CIL emission |
CodeGen/CodeGenerator.Emit.cs |
4,396 | CIL instruction generation |
FlowAnalysis/ExpressionAnalysis.cs |
2,911 | Expression-level type analysis |
Semantics/BoundExpression.cs |
2,721 | Semantic binding expressions |
Runtime/Operators.cs |
2,573 | Runtime implementation of PHP operators |
Runtime/PhpString.cs |
2,047 | PHP string value type |
CodeGen/VariableReference.cs |
1,863 | Variable references and address analysis |
Symbols/Source/SourceTypeSymbol.cs |
1,795 | Source type symbols |
Runtime/Conversions.cs |
1,603 | Type conversion runtime |
Comparison with AOT Compiler
| Dimension | PeachPie | AOT Compiler |
|---|---|---|
| Compilation target | PHP → CIL → .NET Assembly | PHP → C++ → binary |
| Compiler framework | Roslyn (C# compiler-as-a-library) | Self-built PHP AST → C++ string |
| IR form | BoundControlFlowGraph (Roslyn pattern) | PHP-Parser AST nodes |
| Type inference | FlowState + TypeRefMask bitset | SSA + manual type annotations |
| Symbol system | Roslyn Symbol hierarchy (complete) | Simplified ClassDef/FunctionDef |
| Output | .NET PE file (cross-platform) | Native binary (Linux/Mac/Windows) |
| Runtime | Peachpie.Runtime (PhpValue, PhpArray, PhpString) | phpx (C++ RAII wrapper around Zend API) |
| Cross-language interop | First-class citizen — PHP ⇄ C# bidirectional calls | FFI only (swoole_cc/cpp extension loading) |
| Parallel compilation | Parallel.ForEach function-level parallelism | Serial execution |
| Code size | ~272k lines of C# (including runtime) | ~15k lines of PHP |
| MSBuild integration | Complete SDK (dotnet build) |
None |
2. Reusable Innovative Designs
2.1 Roslyn-Based Compiler Architecture
Files: all of Peachpie.CodeAnalysis/
PeachPie's most central design decision is to be fully built on the Microsoft Roslyn compiler platform. This means:
- Reuse Roslyn's symbol system:
TypeSymbol,MethodSymbol,NamedTypeSymbol, and other standard Roslyn types - Reuse Roslyn's metadata emission:
PEModuleBuilder,PEAssemblyBuilderdirectly generate PE files - Reuse Roslyn's diagnostics system:
DiagnosticBag, standardized Error/Warning mechanism - Native MSBuild integration: PHP projects are standard .NET projects (
.csprojstyle), compilable withdotnet build
AOT adoption priority: P2
Currently AOT built all compiler infrastructure from scratch. PeachPie's approach is a useful reference, but not suitable for direct porting (AOT's goal is to generate C++ rather than CIL). What can be adopted:
- Decompose the compiler into standard stages — Syntax → Semantic → IR → CodeGen — with clear interfaces per stage
- Separate Preprocessor (symbol collection) into an independent Analyzer stage, similar to Roslyn's Compilation concept
- Define a unified
Diagnostictype instead of scatteredfatalError()/SyntaxErrorcalls
2.2 TypeRefMask: 64-bit Type Bitset
File: FlowAnalysis/TypeRef/TypeRefMask.cs
PeachPie uses ulong (64-bit) as the type mask:
public struct TypeRefMask {
ulong _mask;
// bits 0-61: type index (up to 62 different types)
// bit 62: IncludesSubclasses (type may include subclasses)
// bit 63: IsRef (value is a reference/alias)
}
Characteristics:
- O(1) type comparison:
(mask & type_bit) != 0detects a type - Union types:
mask1 | mask2= contains both types - Type narrowing:
mask & ~excluded_type_bit= excludes a type - IsRef marker: tracks whether a value is assigned by reference, affecting alias analysis
- IncludesSubclasses: distinguishes
exactly ClassvsClass or subclass
Each TypeRefContext maintains a type registry mapping concrete .NET types (such as System.Int64, Pchp.Core.PhpString) to bit indices.
AOT adoption priority: P0
This is consistent with HHVM's trep idea — using a bitset to represent types. AOT's discrete constants like TYPE_INT, TYPE_STRING can be replaced with a bitset:
class TypeMask {
const BINT = 1 << 0;
const BFLOAT = 1 << 1;
const BSTRING = 1 << 2;
const BBOOL = 1 << 3;
const BARRAY = 1 << 4;
const BOBJECT = 1 << 5;
// Extension markers
const BEMPTY = 1 << 60; // empty value marker
const BSUBCLASS = 1 << 61; // allow subclasses
const BREFERENCE = 1 << 62; // reference marker
}
Advantage: int|string = BINT|BSTRING, type narrowing = & ~excluded_bits.
2.3 FlowState + Worklist Dataflow Analysis
Files: FlowAnalysis/FlowState.cs, FlowAnalysis/Worklist.cs
PeachPie uses the classic dataflow worklist algorithm for type inference:
class FlowState {
TypeRefMask[] _varsType; // type mask for each variable
ulong _initializedMask; // whether variables are initialized
HashSet<NoteData> _notes; // additional info (such as function return points)
}
Merge operation: when two CFG paths converge, the FlowState(state1, state2) constructor computes:
- The union of type masks (all possible types)
- The union of initialization masks (initialized in either branch counts as initialized)
- The intersection of notes (only info present on both paths is retained)
The Worklist processes blocks in topological order, re-enqueueing when state changes.
AOT adoption priority: P1
Currently AOT's SSA analysis does some type inference, but lacks:
- Structured FlowState: a unified variable type-state representation
- Standard merge operation: type merging at join points
- Worklist iteration: an iterative analysis framework reaching a fixed point
2.4 ConditionBranch-Aware Type Narrowing
Files: FlowAnalysis/ConditionBranch.cs, FlowAnalysis/AnalysisFacts.cs
PeachPie's type analysis is aware of the conditional branch of the current context:
enum ConditionBranch {
AnyResult = 0, // ordinary evaluation
ToTrue = +1, // branch where the expression result is true
ToFalse = -1, // branch where the expression result is false
}
In conditional expressions, the analyzer carries the branch direction to propagate type info:
// if ($x instanceof MyClass) { ... }
// In the ToTrue branch:
// $x's type is narrowed, excluding types that cannot be MyClass
// In the ToFalse branch:
// $x's type is narrowed, excluding MyClass
// if (is_int($x)) { ... }
// In the ToTrue branch:
// $x's type is narrowed to int
AnalysisFacts.HandleSpecialFunctionCall() registers type-check functions like is_int, is_string, is_array, is_callable, function_exists, class_exists, automatically narrowing variable types in branches.
AOT adoption priority: P1
Currently SsaTypeOptimizer does some instanceof narrowing, but:
- Does not support narrowing via built-in type-check functions like
is_int()/is_string() - Does not support constant folding of existence checks like
class_exists()/function_exists() - Can directly adopt
AnalysisFacts's "known type-check function registry" pattern
2.5 PhpValue Tagged Union Design
File: Peachpie.Runtime/PhpValue.cs
PeachPie's runtime value type uses an ingenious C# tagged union:
[StructLayout(LayoutKind.Sequential)]
public readonly partial struct PhpValue {
readonly PhpTypeCode _type; // 1-byte type tag
// Explicit-layout union: two fields occupy the same memory
[StructLayout(LayoutKind.Explicit)]
struct ValueField {
[FieldOffset(0)] public bool @bool;
[FieldOffset(0)] public long @long;
[FieldOffset(0)] public double @double;
}
[StructLayout(LayoutKind.Explicit)]
struct ObjectField {
[FieldOffset(0)] public object @object;
[FieldOffset(0)] public string @string;
[FieldOffset(0)] public PhpString.Blob blob;
[FieldOffset(0)] public PhpArray array;
[FieldOffset(0)] public PhpAlias alias;
}
readonly ValueField _value; // value-type storage
readonly ObjectField _obj; // reference-type storage
}
Memory layout: PhpValue = PhpTypeCode (1 byte) + padding + ValueField (8 bytes) + ObjectField (8 bytes, pointer) ≈ 24 bytes.
Advantages of this design:
- readonly struct — no GC overhead, allocatable on the stack
- Explicit union — value types and reference types share space, compact
- PhpAlias mechanism — abstracts PHP references (copy-on-write) via
PhpAliasrather than directly copying values - MutableString — distinguishes immutable string from writable MutableString (for string concatenation optimization)
AOT adoption priority: P3
Currently AOT uses Variant (based on Zend zval) as the dynamic type. What can be adopted:
- PhpString's MutableString separation (string builder pattern)
- PhpAlias's reference semantics abstraction
- But replacing
Variantentirely is a huge effort, so low priority
2.6 GhostMethodBuilder: PHP Method ⇄ C# Method Adaptation
File: CodeGen/GhostMethodBuilder.cs
PeachPie's most distinctive feature is automatically generating ghost stub methods so PHP methods can be called directly from C#:
// Generate C#-callable wrappers for PHP methods:
// - Handle parameter type conversion (PhpValue → CLR type)
// - Handle return type conversion (CLR type → PhpValue)
// - Build and pass PhpContext
// - Support explicit interface override
static MethodSymbol CreateGhostOverload(
MethodSymbol original, NamedTypeSymbol containingtype,
PEModuleBuilder module, DiagnosticBag diagnostic,
TypeSymbol ghostreturn, ImmutableArray<ParameterSymbol> ghostparams,
bool phphidden = false, MethodSymbol explicitOverride = null)
Ghost methods enable:
- Type-safe interfaces when C# calls PHP methods
- PHP implementing C# interfaces (IMethod, INotifyPropertyChanged, etc.)
- PHP classes usable as .NET generic parameters
AOT adoption priority: P4
Currently AOT does not support PHP calling C++ (or vice versa). If a bidirectional interop layer is needed in the future, the ghost stub pattern is worth referencing.
2.7 DelayedTransformations: Parallel-Safe Deferred Transformations
File: FlowAnalysis/Passes/DelayedTransformations.cs
During the parallel analysis phase, certain transformations (such as marking unreachable functions, promoting conditional functions to unconditional ones) cannot directly modify shared state. PeachPie uses the deferred transformation pattern:
class DelayedTransformations {
ConcurrentBag<SourceRoutineSymbol> UnreachableRoutines;
ConcurrentBag<SourceTypeSymbol> UnreachableTypes;
ConcurrentBag<SourceFunctionSymbol> FunctionsMarkedAsUnconditional;
// Collected thread-safely during parallel analysis
// Applied serially via Apply() after analysis completes
}
Analysis threads only place the objects to be transformed into the ConcurrentBag, and after analysis completes a single thread calls Apply().
AOT adoption priority: P2
AOT is currently serial and doesn't need this. But if parallel compilation is introduced in the future (see KPHP review 2.3), deferred transformation is the foundational pattern for thread safety.
2.8 Native MSBuild Integration (Peachpie.NET.Sdk)
File: Peachpie.NET.Sdk/
PeachPie is not just a compiler — it is a complete .NET SDK:
dotnet new classlibrary -o MyPhpLib # create a PHP class library project
dotnet build # compile PHP → .NET DLL
dotnet run # run the compiled program
dotnet publish # publish as a self-contained app
Implemented via MSBuild targets/props:
build/peachpie.targets— defines the compilation taskPeachpie.NET.Sdk.nuspec— NuGet package definitionBuildTask.cs— MSBuild compilation task
This means PHP projects can seamlessly use the .NET ecosystem: NuGet package references, project references, conditional compilation, multi-targeting, etc.
AOT adoption priority: P2
AOT currently uses php bin/tpc.php <project> on the command line. What can be adopted:
- Create a Composer plugin or CLI phar package for the AOT compiler
- Define a JSON Schema for
project.yml(similar to.csproj) - Support a unified entry point like
composer buildorphp-aot build
2.9 Lazily Evaluable AnalysisFacts
File: FlowAnalysis/AnalysisFacts.cs
PeachPie performs constant evaluation of a large number of PHP runtime functions at compile time:
| Function | Evaluation strategy |
|---|---|
function_exists(X) |
Check if symbol X exists in the PE assembly → fold to true |
class_exists(X) |
Check if type X exists in the PE assembly → fold to true/false |
method_exists(X, M) |
Check if method M exists in type X → fold to true/false |
defined(CONST) |
Check if the constant exists → fold to true/false |
is_callable(F) |
Check if F is unconditionally declared → fold to true |
dirname(__FILE__) |
Compile-time path computation → __DIR__ |
basename(__FILE__) |
Compile-time filename extraction → string constant |
These evaluations leverage PeachPie's "PE assembly" concept — already-compiled .NET assemblies contain complete type/method/constant metadata that can be queried at compile time.
AOT adoption priority: P1
Currently FuncCallOptimizer only does the most basic constant folding (strlen("abc") → 3). It can be extended to compile-time evaluation of reflective functions like function_exists, class_exists, defined — provided a complete symbol table is built in Preprocessor.
2.10 Conditional Declaration Detection and Unreachable Code Elimination
Files: FlowAnalysis/Passes/DelayedTransformations.cs, FlowAnalysis/Passes/TransformationRewriter.cs
PeachPie can detect conditional declarations (if (condition) { function foo() {} }) and optimize them:
- If analysis proves the condition is always true → the function is marked as unconditionally declared
- If analysis proves the condition is always false → the function/class is marked as Unreachable and not compiled
This enables writing C-like conditional compilation patterns in PHP:
if (PHP_VERSION_ID >= 80000) {
function newFeature() { ... } // not compiled on lower versions
}
AOT adoption priority: P2
Currently AOT compiles every scanned function regardless of reachability. For projects with multi-version PHP compatibility code, conditional declaration elimination can reduce compilation artifact size.
3. Toolchain Analysis
3.1 Test Infrastructure
PeachPie has 529 test files (PHP files) distributed across functional directories:
| Directory | Content |
|---|---|
tests/arrays/ |
Array operation tests (including a lazy_copy subdirectory) |
tests/classes/ |
Class/object tests |
tests/functions/ |
Function call tests |
tests/generators/ |
Generator/yield tests |
tests/strings/ |
String operation tests |
tests/operators/ |
Operator tests |
tests/transformations/ |
Compiler transformation/optimization tests |
tests/constants/ |
Constant tests |
tests/constructs/ |
Language construct tests |
tests/traits/ |
Trait tests |
tests/reflection/ |
Reflection tests |
tests/spl/ |
SPL tests |
tests/bcmath/ tests/hash/ tests/pcre/ |
Extension tests |
tests/pdo/ tests/ftp/ tests/openssl/ |
Database/network extensions |
tests/gd/ tests/xml/ tests/zip/ |
Graphics/XML/ZIP extensions |
tests/web/ tests/scripting/ |
Web/scripting integration tests |
Test execution: compiled assemblies are executed through the .NET test framework (xUnit/NUnit).
AOT adoption:
- The
tests/transformations/directory specifically tests compiler optimizations — AOT can build similar optimization-correctness tests tests/arrays/lazy_copy/specifically tests copy-on-write behavior — AOT can also build dedicated COW tests
3.2 Deep Visual Studio Integration
PeachPie provides a complete IDE experience:
- Visual Studio Extension — project management, IntelliSense, debugging, performance analysis
- VS Code / Rider support — via OmniSharp / LSP
- NuGet package management — PHP libraries can be published and referenced as NuGet packages
AOT adoption:
- Can provide a VSCode extension (Task integration + project templates)
- AOT's current
project.ymlcan use a JSON Schema to provide IDE auto-completion
3.3 Command-Line Toolchain
# PeachPie's CLI experience
dotnet peach build # compile a PHP project
dotnet peach run # run the compiled program
dotnet peach publish # self-contained publishing
dotnet peach add <package> # add a dependency
AOT adoption:
# A similar CLI could be designed
php-aot build # compile a project
php-aot run # compile and run
php-aot new <name> # create a new project
4. Type System and Interop Analysis
4.1 PeachPie Type Mapping
| PHP type | PeachPie runtime type | .NET CLR type |
|---|---|---|
| null | PhpTypeCode.Null |
null (any reference type) |
| bool | PhpTypeCode.Boolean |
bool |
| int | PhpTypeCode.Long |
long |
| float | PhpTypeCode.Double |
double |
| string | PhpTypeCode.String / MutableString |
string / PhpString |
| array | PhpTypeCode.PhpArray |
PhpArray |
| object | PhpTypeCode.Object |
object (concrete class) |
| reference | PhpTypeCode.Alias |
PhpAlias |
4.2 PHP ⇄ C# Interop
PeachPie's bidirectional interop is its most prominent differentiator:
C# calling PHP:
// Compiled PHP classes become .NET classes; C# can directly new and call them
var phpObj = new MyPhpClass(ctx);
phpObj.someMethod(arg1, arg2);
PHP calling C#:
// .NET types can be used directly in PHP
$list = new \System\Collections\Generic\List<int>;
$list->Add(42);
Interop implementation relies on:
GhostMethodBuildergenerating adapter methodsConversionsExtensionshandling automatic PhpValue ↔ CLR type conversionDynamicOperationFactoryhandling dynamic method call forwarding
4.3 Syntax Differences from the AOT Compiler
| Feature | PeachPie | AOT Compiler |
|---|---|---|
| Base PHP version | 8.0+ (target) | 8.2+ |
| Type annotations | Optional (gradually enriched) | Optional (phpstan annotations) |
| Namespaces | Standard PHP | Standard PHP |
| Generics | None | None |
| C# interop | Complete (first-class citizen) | FFI extension only |
| .NET ecosystem | Fully compatible | Not relevant |
| MSBuild integration | Complete | None |
| Reflection | Partial support | Not supported |
| yield/generator | Supported | Supported |
5. Summary and Priority Recommendations
| Priority | Technique | Difficulty | Benefit | Notes |
|---|---|---|---|---|
| P0 | TypeRefMask bitset type system | Medium | Very high | Union types, type narrowing, non-null markers — foundation for all optimization passes |
| P1 | ConditionBranch type narrowing | Low | High | Automatic narrowing via is_int()/is_string() check functions, not covered by current SSA |
| P1 | AnalysisFacts compile-time evaluation | Medium | High | Compile-time folding of function_exists/class_exists/defined |
| P1 | FlowState worklist analysis framework | Medium | High | Structured variable type state, standard merge operation |
| P2 | Roslyn-style compiler layering | High | Medium | Clear Syntax→Semantic→IR→CodeGen layering; current Preprocessor/CompilerBase responsibilities are mixed |
| P2 | Conditional declaration detection | Low | Medium | Eliminate unreachable functions/classes, reduce compilation artifact size |
| P2 | MSBuild integration / CLI unification | Low | Medium | Standardized project configuration, CI-friendly |
| P3 | PhpValue tagged union | High | Medium | Compact memory layout, but replacing Variant/zval is a large effort |
| P3 | DelayedTransformations | Low | Low | Only meaningful under parallel compilation |
| P4 | GhostMethodBuilder interop | Very high | Low | Requires .NET or a similar FFI runtime |
| P4 | Deep IDE integration | High | Low | Limited ROI for a VSCode extension |
Key Takeaways
-
PeachPie's biggest advantage is .NET ecosystem integration — this is a natural advantage from its architectural choice rather than any single technical innovation. AOT can adopt its "compiler SDK + standardized CLI" thinking without porting specific techniques.
-
TypeRefMask bitset is the most directly portable design — representing types with a 64-bit bitset while supporting union types, subclass markers, and reference markers. This is a common feature of HHBBC's trep and KPHP's type analysis, indicating that the bitset type lattice is the best practice for PHP compilers.
-
The ConditionBranch pattern is a low-cost, high-benefit enhancement — carrying "expected result" info in conditional branch analysis enables type-check functions to automatically narrow variable types. AOT's SSA analysis can adopt it immediately.
-
GhostMethodBuilder reveals a general interop pattern — generating adapter/thunk methods to bridge the calling conventions of two languages. Although AOT targets C++ rather than .NET, if cross-language call needs arise (such as PHP calling C extensions), this pattern can be reused.
-
PeachPie's roughly 530 tests are notably fewer than KPHP (75+ directories) and HHVM (14,675), indicating relatively lower maturity. But its way of categorizing tests by functional module and optimization type is worth referencing.