TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

9.2 KiB

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.

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:

%value = load_local $value
%class = load_local $name
%ok = instanceof %value, %class
return %ok

For a more complex expression:

%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:

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:

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:

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:

--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.