From fc5ad87f555fd8fc3d79df42d960ec143adf8a4d Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 10 Jul 2026 14:38:59 +0800 Subject: [PATCH] docs(backend): add backend-neutral IR design document - Document recommended pipeline from PHP Parser AST to backend-specific code generation - Define Bound AST structure preserving PHP syntax shape with resolved semantic information - Specify PHP Semantic IR close to PHP runtime semantics with suggested instruction families - Outline Runtime ABI operations for consistent --- docs/BACKEND_NEUTRAL_IR.md | 307 +++++++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 docs/BACKEND_NEUTRAL_IR.md diff --git a/docs/BACKEND_NEUTRAL_IR.md b/docs/BACKEND_NEUTRAL_IR.md new file mode 100644 index 00000000..59399a8d --- /dev/null +++ b/docs/BACKEND_NEUTRAL_IR.md @@ -0,0 +1,307 @@ +# Backend-Neutral IR for C++, WASM and JavaScript + +TypePHP currently lowers PHP AST directly to C++ code. This works for the +current C++/Zend backend, but it couples PHP semantic analysis, temporary value +management, reference behavior, runtime calls and C++ code formatting in one +stage. + +If TypePHP needs to support targets such as WASM or JavaScript, the compiler +should introduce a backend-neutral semantic IR before backend-specific code +generation. + +## Recommended Pipeline + +```text +PHP Parser AST + -> Bound AST + -> PHP Semantic IR + -> Backend Lowering + -> C++/Zend Runtime IR -> C++ + -> JavaScript Runtime IR -> JS + -> WASM Runtime IR -> wasm/wat/import calls +``` + +The important rule is that the semantic IR must describe PHP behavior, not C++ +implementation details. + +It should not contain: + +- `php::Var` +- `php::Ref` +- `php::Array` +- `zend_*` +- `zval*` +- C++ expression fragments + +Those belong to the C++ backend only. + +## Bound AST + +The Bound AST should preserve PHP syntax shape while attaching resolved semantic +information. + +It should contain: + +- Resolved names for functions, classes, methods, properties and constants. +- Scope information for locals, globals, statics and closure captures. +- Declared and inferred types. +- By-reference information for parameters, returns and assignments. +- Call target classification: AOT function, native call, internal function, + dynamic call or unsupported target. +- L-value and R-value classification for reads, writes, `isset`, `unset` and + reference access. +- Target capability diagnostics, such as whether `eval`, `include`, resources or + reflection are allowed for the selected backend. + +Most unsupported syntax should be rejected in this phase or during IR +validation, not during backend code generation. + +## PHP Semantic IR + +The semantic IR should be close to PHP runtime semantics. It should not be too +low-level at first, because PHP references, arrays, dynamic calls and exception +flow are difficult to recover after early lowering. + +Example: + +```text +%value = load_local $value +%class = load_local $name +%ok = instanceof %value, %class +return %ok +``` + +For a more complex expression: + +```text +%services = load_prop this, "services" +%closure = make_closure closure#1 captures [$name] +%filtered = call_func "array_filter", [%services, %closure, ARRAY_FILTER_USE_BOTH] +%values = call_func "array_values", [%filtered] +%result = coalesce_dim %values, 0, null +return_checked %result, ?object +``` + +Suggested instruction families: + +- `LoadLocal`, `StoreLocal` +- `LoadGlobal`, `StoreGlobal` +- `LoadProp`, `StoreProp`, `PropRef` +- `LoadDim`, `StoreDim`, `DimRef` +- `CallFunc`, `CallMethod`, `CallStatic` +- `NativeCall` +- `MakeClosure` +- `InstanceOf` +- `Isset`, `Empty`, `Coalesce` +- `Cast`, `TypeCheck`, `TypeAssert` +- `ToReferenceExact` +- `Return`, `ReturnRef`, `ReturnChecked` +- `Throw`, `TryCatchFinally` +- `Branch`, `Jump`, `Label` +- `ForeachInit`, `ForeachNext` +- Explicit unsupported nodes for `eval`, `include`, `resource` operations and + other target-dependent features. + +## Runtime ABI + +Multiple backends should implement the same PHP semantic runtime ABI. + +Example ABI operations: + +```text +rt_call_function(name, args) +rt_call_method(receiver, method, args) +rt_call_static(class, method, args) +rt_get_property(object, name) +rt_set_property(object, name, value) +rt_get_property_ref(object, name) +rt_get_dim(value, key) +rt_set_dim(value, key, value) +rt_get_dim_ref(value, key) +rt_to_bool(value) +rt_instanceof(value, class_name) +rt_make_reference(value) +rt_deref(value) +rt_throw(value) +``` + +The C++ backend can map these operations to the existing `php::Var`, `php::Ref` +and Zend-based runtime. JavaScript and WASM can map them to their own runtime +representations. + +## Value Model + +The IR should use abstract value identities instead of physical backend types. + +Useful concepts: + +- `ValueId` +- `RefId` +- `ArrayId` +- `ObjectId` +- `ClassId` +- `FunctionId` +- `CallableId` +- `ExceptionId` + +The backend decides how these are represented. For example: + +- C++/Zend can use `php::Var`, `php::Ref`, `zval*` and `zend_class_entry*`. +- JavaScript can use tagged JS objects and boxed references. +- WASM can use handles and runtime imports. + +## Semantics That Must Stay Explicit + +The following PHP semantics must remain explicit in the Bound AST or semantic +IR. They should not be lowered too early: + +- `isset` is not the same as a normal read. +- `empty` is not just `!toBool(value)`. +- `??` uses `isset` semantics, not only null comparison. +- `foreach` by value and by reference are different. +- Return by reference must be represented as `ReturnRef`. +- Call-by-reference arguments must preserve reference slots. +- Array element and object property references must use explicit `DimRef` and + `PropRef` operations. +- PHP arrays are ordered maps, not native JS arrays or C++ vectors. +- Object property visibility and typed property checks are runtime semantics. +- Dynamic calls must preserve symbol lookup behavior. +- `eval`, `include`, resources, reflection and extension-dependent behavior must + be explicit so each backend can accept, reject or emulate them. + +## Target Capability Matrix + +Each backend should declare a feature set. The compiler should validate IR +against the selected target before code generation. + +Example: + +```text +Feature C++/Zend JavaScript WASM +function call yes yes yes +class/object yes partial partial +array COW Zend runtime runtime +reference Zend boxed refs boxed refs +internal PHP funcs yes shim/import import +resource yes no/partial no +eval/include partial no/partial no +reflection yes partial no/partial +extension classes yes shim no +``` + +This should be represented in code as target capability metadata, for example: + +```text +target: cpp-zend | js | wasm +``` + +Unsupported behavior should produce deterministic diagnostics in the validation +phase. + +## JavaScript Backend Notes + +JavaScript is a practical first non-C++ backend because it is dynamic and can +host a PHP-like runtime in user space. + +Main runtime requirements: + +- PHP array as ordered map. +- PHP references as boxes. +- PHP object/class model with visibility checks. +- PHP weak typing and cast helpers. +- PHP exception model. +- Closure capture semantics. +- Dynamic function, method and static method calls. +- Binary-safe string representation, because JS strings are not byte strings. +- Integer handling policy, because JS numbers are doubles. + +The initial JS backend should target a strict PHP subset before trying to support +resources, reflection, extensions, `eval` or real stream behavior. + +## WASM Backend Notes + +WASM is less suitable for directly expressing dynamic PHP semantics. It should be +planned as a runtime-backed target. + +Possible routes: + +1. PHP -> C++ -> WASM + + This is the shortest path and reuses the current C++ backend, but Zend + embedding is heavy and many OS/resource features are limited in WASM + environments. + +2. PHP Semantic IR -> WASM + runtime imports + + This is cleaner but more expensive. The generated WASM should call imported + runtime functions for dynamic PHP operations such as arrays, objects, + references and dynamic calls. + +The second route is preferable long term. The first route may be useful for an +early proof of concept. + +## Migration Strategy + +Do not rewrite the whole compiler at once. + +Recommended steps: + +1. Introduce target capability metadata. +2. Add Bound AST metadata without changing existing C++ output. +3. Add semantic IR data structures and `--dump-ir`. +4. Lower a small expression subset to IR first. +5. Implement `Semantic IR -> C++` for that subset. +6. Keep mixed mode: unsupported IR nodes can temporarily fall back to the old + direct AST-to-C++ path. +7. Gradually migrate high-risk constructs: + - `??` + - ternary + - `instanceof` + - nullsafe access + - function, method and static calls + - `return` + - `foreach` + - `try/catch/finally` + - reference assignment and return-by-reference + +The current `beforeStmtLines` and `afterStmtLines` mechanism should eventually +be replaced by explicit IR instruction ordering. This avoids hidden side effects +inside expression parsing and prevents formatting or comment text from changing +generated program behavior. + +## Testing Strategy + +Add IR snapshot tests in addition to PHPT output tests. + +Example: + +```text +--IR-- +function main(): void + %0 = load_local $value + %1 = load_local $name + %2 = instanceof %0, %1 + return %2 +``` + +This separates frontend semantic correctness from backend code generation +correctness. + +Recommended test categories: + +- PHP AST -> Bound AST diagnostics. +- Bound AST -> Semantic IR snapshots. +- Semantic IR -> C++ output behavior. +- Target capability rejection tests. +- Backend-specific runtime conformance tests. + +## Key Design Constraint + +The central IR must be a PHP semantic IR, not a C++ builder IR. + +If the IR is designed around `php::Var`, `php::Ref` and Zend symbols, C++ will +remain the compiler's semantic core and future JavaScript/WASM targets will be +hard to implement. + +If the IR describes PHP behavior and leaves physical representation to backend +lowering, C++, JavaScript and WASM can evolve as separate backends.