diff --git a/docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md b/docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md new file mode 100644 index 00000000..6b3f9a4a --- /dev/null +++ b/docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md @@ -0,0 +1,154 @@ +# PHP Incompatibility Classification + +This document classifies TypePHP AOT incompatibilities by their cause and +expected future direction. + +The goal is to distinguish: + +- Hard limits that should not be promised as fully compatible PHP behavior. +- Intentional TypePHP language rules. +- Features that are implementable but not supported yet. +- Partial support where the current behavior is known to differ from PHP. + +The main compatibility checklist remains `INCOMPATIBLE_PHP_FEATURES.md`. This +document explains how those items should be interpreted. + +## Categories + +### Hard Limit + +The feature conflicts with the current AOT execution model, or exact PHP +compatibility would require unreasonable runtime mirroring, heavy dynamic state, +or semantics that are not naturally visible from compiled C++ frames. + +This does not always mean "theoretically impossible", but TypePHP should not +promise full PHP compatibility for these cases. + +### Intentional Rule + +The feature could be implemented, but TypePHP intentionally rejects or restricts +it to keep the AOT model explicit, predictable and optimizable. + +These are language or product rules, not missing implementation work. + +### Pending + +The feature is technically implementable and should be described as currently +unsupported, not as impossible. + +These items usually need better IR, symbol binding, runtime helpers, or codegen +lowering. + +### Partial + +The feature exists but does not fully match standard PHP behavior in all edge +cases. + +These items should be documented with the exact boundary. + +## Hard Limits and Non-Promised Compatibility + +| Feature | Classification | Reason | +|---|---|---| +| `eval()` accessing AOT-compiled local variables | Hard Limit | AOT locals are C++ stack variables. PHP code executed by Zend VM through `eval()` cannot naturally access that compiled stack frame. Exact compatibility would require a locals mirror and synchronization layer. | +| Standard PHP variable deletion semantics for `unset($nativeTypedVar)` | Hard Limit / Intentional Rule | Native typed locals are C++ variables, not entries in a PHP symbol table. They cannot be deleted like PHP zval variables. | +| Fully standard uninitialized / `unset()` semantics for fixed native typed object properties | Hard Limit / Intentional Rule | Fixed native property storage conflicts with PHP's dynamic property state, uninitialized state and unset behavior. | +| `Closure::bind()` with static closures accessing private members across AOT/native boundaries | Hard Limit / Partial | This depends on Zend closure scope, private visibility checks and AOT native method/property access. Partial support may be possible, but complete equivalence is difficult. | +| Non-UTF-8 source files | Intentional Rule | Other encodings could be converted before compilation, but TypePHP requires UTF-8 to keep parsing and generated code deterministic. | +| `declare(encoding=...)` values other than `UTF-8` | Intentional Rule | Same reason as source file encoding. | + +## Intentional TypePHP Rules + +| Feature | Classification | Reason | +|---|---|---| +| No executable statements in global scope | Intentional Rule | A global execution block could be generated, but it complicates initialization order, side effects and include-like behavior. TypePHP requires executable code to be under functions or methods. | +| Binary mode requires global `main()` | Intentional Rule | This defines the binary entry ABI. | +| `main()` only accepts no parameters or `(int $argc, array $argv)` | Intentional Rule | Keeps the entry ABI explicit and stable. | +| `main()` must return `void` | Intentional Rule | An integer exit-code convention could be added later, but current TypePHP rules reject return values. | +| `declare(strict_types=...)` only supports `strict_types=1` | Intentional Rule | Supporting mixed strict/weak typing is possible, but TypePHP keeps strict behavior predictable. | +| Default parameter before required parameter | Intentional Rule | PHP allows this legacy pattern but ignores the default. TypePHP rejects it to avoid misleading declarations. | +| Child class overriding parent private property | Intentional Rule / Pending if dynamicized | PHP stores private properties by declaring class. TypePHP native/fixed layouts make this expensive. Rejecting it keeps property layout predictable. | +| `__construct()` return value | Intentional Rule | PHP constructors should not return values. TypePHP rejects this explicitly. | +| Reassigning a statically inferred native local to an incompatible type | Intentional Rule | This is the cost of native type optimization. Use dynamic zval variables when PHP-style type changes are required. | +| Native `std::int` overflow and integer division behavior | Intentional Rule | Native numeric types trade PHP compatibility for performance and C++ storage. | +| `__CLASS__` outside class context and `__TRAIT__` outside trait context | Intentional Rule | PHP returns an empty string for legacy compatibility. TypePHP rejects this as a clearer rule. | + +## Implementable but Currently Unsupported + +| Feature | Classification | Implementation Direction | +|---|---|---| +| `yield` / `yield from` | Pending | Lower generator functions to state machines and provide a Generator runtime object. | +| Variable variables (`$$var`) | Pending | Add a function-local symbol table mirror for dynamic locals, and disable or synchronize native locals that escape into dynamic lookup. | +| PHP 8.4 property hooks | Pending | Add parser and AST support, then lower property read/write paths to hook calls. | +| Closure or arrow function returning by reference | Pending | Closure metadata and wrappers must preserve return-by-reference and emit `ReturnRef`. | +| Closure and arrow function by-reference parameters | Pending | Closure arginfo must preserve by-reference parameters and call lowering must pass reference slots. | +| By-reference variadic parameters (`&...$args`) | Pending | Variadic storage must preserve references instead of copying values. | +| By-reference parameters with default values | Pending | Need PHP-compatible handling for omitted arguments using temporary default values while still binding references for passed arguments. | +| Reference assignment from complex static property expressions | Pending | Static property reference targets need complete lowering and lifetime handling. | +| Dynamic calls automatically converting by-reference arguments | Pending | Runtime callable metadata or reflection can identify by-reference parameters and build reference arguments dynamically. | +| Calls with unpack plus trailing named arguments staying native | Pending | Normalize and reorder call arguments in IR before native-call selection. | +| Dynamic `parent::method()` name | Pending | Needs runtime parent method lookup with correct call scope. | +| Private typed property access on cloned objects through variables | Pending / Partial | Requires a complete declaring-class-aware access resolver. | +| `ReflectionProperty::isPromoted()` for constructor-promoted properties | Pending | Generated class metadata should record promoted-property flags. | +| `echo` with assignment expressions | Pending | Requires expression lowering that preserves evaluation order and returns the assigned value. | +| Nested `match` expressions in arm conditions | Pending | Requires recursive match lowering and temporary value ordering. | +| `foreach` by-reference value targets beyond simple variables | Pending | Requires explicit lvalue/reference target modeling. | +| `foreach` by-reference with list destructuring | Pending | Requires by-reference foreach value lowering followed by destructuring assignment. | +| Dynamic `ClassName::class` | Pending | Runtime class-name resolution can be used when the class expression is dynamic. | +| `static::class` in runtime contexts | Pending / Partial | Runtime contexts can use called-class lookup. True compile-time constant contexts should remain unsupported. | +| Dynamic property chains, class names, function names and callbacks in native-optimized paths | Pending | A unified dynamic runtime path should handle these cases; native paths should be optimization only. | +| First-class callable stored in nullable `Closure` typed property | Pending / Partial | Requires stable runtime lifetime, refcount and typed-property write handling. | +| Attribute arguments containing arrays or `new` expressions | Pending | Requires full constant-expression and attribute metadata generation support. | +| Static analysis of union, intersection and nullable types | Pending optimization | Requires a real union/intersection type lattice instead of treating these as `mixed/any` during static analysis. | + +## Partial Support and Behavioral Differences + +| Feature | Classification | Boundary | +|---|---|---| +| `eval()` | Partial / Hard Limit | `eval()` can execute PHP code through Zend VM, but it cannot access compiled local variables. Use return values or `$GLOBALS` for data exchange. | +| Dynamic calls and callbacks | Partial | Many cases can fall back to Zend dynamic calls, but by-reference argument conversion and native-call optimization are limited. | +| Dynamic properties and dynamic property chains | Partial | Simple dynamic property paths may work; complex chains may be rejected or fall back to slower runtime paths. | +| Native typed properties | Partial / Intentional Rule | Fast native paths may not preserve every PHP dynamic state transition. Unknown or incompatible values can fall back to `setProperty()`. | +| Reflection metadata | Partial | Runtime declarations exist, but some AOT-specific metadata such as promoted-property flags may be incomplete. | + +## Outdated or Ambiguous Items in `UNSUPPORTED_SYNTAX.md` + +`UNSUPPORTED_SYNTAX.md` contains historical material and should not be treated as +the authoritative current compatibility list without verification. + +Items that need review: + +- Attributes are not necessarily wholly unsupported. The current limitation is + narrower: some attribute argument forms, such as arrays and `new`, are not + supported. +- Traits may no longer be purely "planned support"; current implementation and + tests should be checked before documenting them as unsupported. +- `foreach` by-reference support is partial, not absent. +- `break N` and `continue N` should be rechecked against current compiler + behavior before keeping them in the unsupported list. +- DOM or `innerHTML` is not PHP language syntax and should not be listed as a + core AOT syntax incompatibility. +- Duplicate function or class names should be documented carefully. Some cases + are PHP fatal errors, while conditional declarations are a separate dynamic + declaration problem. + +## Documentation Rule + +When documenting a compatibility difference, use one of these labels: + +- `Hard Limit` +- `Intentional Rule` +- `Pending` +- `Partial` + +Avoid using only "unsupported" unless the reason is also clear. + +Recommended wording: + +- "Hard limit: not promised to match PHP exactly." +- "Intentional TypePHP rule." +- "Currently unsupported; implementable in a future compiler/runtime revision." +- "Partially supported with the following boundary." + +This distinction helps users know whether they should rewrite code permanently, +wait for future support, or disable native optimization for a specific path. diff --git a/docs/UNSUPPORTED_SYNTAX.md b/docs/UNSUPPORTED_SYNTAX.md deleted file mode 100644 index 85b7d1c5..00000000 --- a/docs/UNSUPPORTED_SYNTAX.md +++ /dev/null @@ -1,2846 +0,0 @@ -# PHP AOT 编译器语法支持规范 - -## 概述 - -本文档记录 PHP AOT 编译器对 PHP 语法的支持情况,包括已支持、不支持和尚待支持的语法特性。 - ---- - -## 📚 编译模式 - -PHP AOT 编译器支持两种编译模式,每种模式有不同的要求和使用场景。 - ---- - -## 💾 变量类型优化 - -AOT 编译器提供两种变量类型系统,理解它们的差异对于性能优化至关重要。 - -### 默认模式:ZVAL 类型(PHP 原生) - -**声明方式**: -```php -$a = 100; // 默认使用 ZVAL -``` - -**特点**: -- **内存占用**: 16 字节 (zval 结构体) -- **类型安全**: ✅ 自动类型转换 -- **精度保证**: ✅ 除法自动转为浮点型 -- **溢出保护**: ✅ 超过 INT_MAX 自动转 float -- **性能**: 标准 PHP 性能 - -**示例**: -```php -$a = 10; -$b = $a / 3; // $b = 3.3333... (自动转为浮点型) - -$a = PHP_INT_MAX; -$a += 10000; // 自动转为 float,不会溢出 - -var_dump($a); // float(9223372036854775807) -``` - -**优点**: -- ✅ 类型安全,不易出错 -- ✅ 自动处理边界情况 -- ✅ 与标准 PHP 行为一致 - -**缺点**: -- ❌ 内存占用较大 (16 字节) -- ❌ 性能开销较高 -- ❌ 需要类型检查和转换 - ---- - -### 优化模式:原生 C++ 类型(zend_long) - -**声明方式**: -```php -$a = std::int(100); // 使用原生 int 类型 -``` - -**特点**: -- **内存占用**: 8 字节 (zend_long) -- **类型安全**: ⚠️ 需要手动管理 -- **精度**: ⚠️ 整数除法会截断 -- **溢出**: ⚠️ 可能溢出(遵循 C++ 规则) -- **性能**: ⚡ 高性能(直接寄存器运算) - -**示例**: -```php -$a = std::int(10); -$b = $a / 3; // $b = 3 (整数除法,截断小数) - -$a = std::int(PHP_INT_MAX); -$a += 10000; // ⚠️ 溢出!相当于 INT64_MAX + 10000 - -var_dump($a); // 溢出的值 -``` - -**优点**: -- ✅ 内存节省 50% (8 字节 vs 16 字节) -- ✅ 性能提升显著(直接写入寄存器) -- ✅ 适合密集数值运算 - -**缺点**: -- ❌ 可能溢出 -- ❌ 小数位丢失 -- ❌ 需要手动处理边界 - ---- - -### 性能对比 - -| 场景 | ZVAL (默认) | zend_long (std::int) | 提升 | -|------|------------|---------------------|------| -| **内存占用** | 16 字节 | 8 字节 | 50% ↓ | -| **加法运算** | ~10ns | ~3ns | 3.3x ⚡ | -| **乘法运算** | ~15ns | ~4ns | 3.75x ⚡ | -| **类型检查** | 需要 | 不需要 | - | -| **寄存器使用** | 间接 | 直接 | - | - ---- - -### 使用建议 - -#### ✅ 适合使用 std::int() 的场景 - -1. **循环计数器** - ```php - for ($i = std::int(0); $i < 1000000; $i++) { - // 高性能循环 - } - ``` - -2. **数组索引** - ```php - $index = std::int(0); - $value = $array[$index]; - ``` - -3. **密集数值运算** - ```php - function calculate_sum($numbers) { - $sum = std::int(0); - foreach ($numbers as $num) { - $sum += std::int($num); - } - return $sum; - } - ``` - -4. **标志位和状态码** - ```php - $status = std::int(0); // 成功 - $error_code = std::int(404); - ``` - -#### ❌ 不适合使用 std::int() 的场景 - -1. **需要精确除法的场景** - ```php - // ❌ 错误示例 - $price = std::int(100); - $average = $price / 3; // 结果:33,期望:33.33... - - // ✅ 正确做法 - $price = 100; // 使用 ZVAL - $average = $price / 3; // 结果:33.333... - ``` - -2. **大数运算** - ```php - // ❌ 可能溢出 - $large = std::int(PHP_INT_MAX); - $large += 10000; // 溢出! - - // ✅ 使用 ZVAL - $large = PHP_INT_MAX; - $large += 10000; // 自动转为 float - ``` - -3. **混合类型运算** - ```php - // ❌ 不推荐 - $a = std::int(10); - $b = 3.14; - $c = $a + $b; // 需要类型转换 - - // ✅ 保持 ZVAL - $a = 10; - $b = 3.14; - $c = $a + $b; // 自动处理 - ``` - ---- - -### 最佳实践 - -#### 1. 局部优化策略 - -```php -function fibonacci($n) { - // 使用原生类型优化性能 - $a = std::int(0); - $b = std::int(1); - - for ($i = std::int(0); $i < $n; $i++) { - $temp = $a; - $a = $b; - $b = $temp + $b; - } - - return $a; -} -``` - -#### 2. 混合使用策略 - -```php -function process_data($data) { - // 索引使用原生类型 - $count = std::int(count($data)); - - for ($i = std::int(0); $i < $count; $i++) { - // 数据本身使用 ZVAL - $value = $data[$i]; - - // 计算时转为原生类型 - $result = std::int($value) * 2; - } -} -``` - -#### 3. 类型转换技巧 - -```php -// ZVAL → zend_long -$native = std::int($zval_value); - -// zend_long → ZVAL -$zval = (string)$native; // 或其他类型转换 - -// 检查溢出 -if ($a > std::int(PHP_INT_MAX - 10000)) { - // 即将溢出,采取措施 -} -``` - ---- - -### 注意事项 - -⚠️ **警告 1: 整数溢出** -```php -$a = std::int(PHP_INT_MAX); -$a++; // 溢出!变为负数 -``` - -⚠️ **警告 2: 除法截断** -```php -$a = std::int(10); -$b = $a / 3; // 结果:3,不是 3.333... -``` - -⚠️ **警告 3: 类型不一致** -```php -$a = std::int(10); -$b = 5.5; // ZVAL -$c = $a + $b; // 需要类型转换,可能有性能损失 -``` - ---- - -### 总结 - -| 特性 | ZVAL (默认) | zend_long (std::int) | -|------|------------|---------------------| -| **内存** | 16 字节 | 8 字节 | -| **性能** | 标准 | 高性能 | -| **安全性** | 高 | 中 | -| **易用性** | 简单 | 需谨慎 | -| **适用场景** | 通用业务 | 数值密集计算 | - -**推荐策略**: -- 默认使用 ZVAL(安全、简单) -- 在性能瓶颈处使用 `std::int()` 优化 -- 了解两种类型的特性和风险 -- 进行充分的测试验证 - ---- - -## 🔒 类型系统对比 - -### 动态类型系统(ZVAL - 默认) - -**特点**: 变量可以在运行时自由改变类型 - -**示例**: -```php -id = $id; - $this->price = $price; - $this->name = $name; - } - - // ✅ 类型安全的 getter/setter - public function getPrice(): std::float { - return $this->price; - } - - // ❌ 错误:类型不匹配 - public function setPrice(std::int $price) { - $this->price = $price; // 编译错误 - } -} -``` - -#### 场景三:循环和计数器 - -```php - std::int(PHP_INT_MAX - 1000)) { - // 转为 ZVAL 处理大数 - return (int)$a / (int)$b; - } - - return std::float($a) / std::float($b); -} -``` - -#### 3. 渐进式迁移 - -```php -= $right) { - return; - } - - $pivot_index = partition($arr, $left, $right); - quicksort_optimized($arr, $left, $pivot_index - 1); - quicksort_optimized($arr, $pivot_index + 1, $right); -} - -function partition(array &$arr, int $left, int $right): int { - $pivot = $arr[$right]; - $i = std::int($left - 1); - - for ($j = std::int($left); $j < $right; $j++) { - if (std::int($arr[$j]) <= $pivot) { - $i++; - // 交换... - } - } - - return $i + 1; -} -``` - ---- - -### 注意事项 - -#### ⚠️ 类型不匹配警告 - -```php - 0 && $b > PHP_INT_MAX / $a) { - throw new OverflowException("Multiplication overflow"); - } - return $a * $b; -} -``` - ---- - -### 性能测试基准 - -#### 测试环境 -- CPU: Intel i7-10700K -- RAM: 32GB DDR4 -- PHP: 8.1 -- 编译器:GCC 11 - -#### 基准测试结果 - -| 测试项目 | Zend VM | AOT (无类型) | AOT (原生类型) | 提升倍数 | -|---------|---------|-------------|---------------|---------| -| Fibonacci(40) | 3200ms | 1600ms | **12ms** | **266x** | -| Pi (1 亿次) | 5100ms | 210ms | **16ms** | **318x** | -| 矩阵乘法 (1000x1000) | 8900ms | 450ms | **35ms** | **254x** | -| 素数筛选 (100 万) | 2100ms | 180ms | **8ms** | **262x** | -| 阶乘 (10000) | 1500ms | 120ms | **5ms** | **300x** | - ---- - -### 决策树 - -``` -是否需要高性能计算? -├─ 否 → 使用 ZVAL (默认) -└─ 是 → 参数是否类型明确? - ├─ 否 → 使用 ZVAL - └─ 是 → 使用原生类型声明 - ├─ 整数 → int $param - ├─ 浮点 → float $param - └─ 布尔 → bool $param -``` - ---- - -### 总结 - -**核心要点**: - -1. ✅ **函数参数声明为 `int`/`float`/`bool` 会自动使用原生类型** -2. ⚡ **性能提升 100-300 倍**(相比 Zend VM) -3. 💾 **内存占用减少 50%** -4. 🎯 **适合数值密集型和递归算法** -5. ⚠️ **需要注意类型匹配和溢出风险** - -**推荐实践**: - -```php - **在 AOT 编译器中,函数参数的类型声明决定性能上限。** - ---- - -### 1. 扩展模式 (Extension Mode) - -**编译命令示例**: -```bash -bin/compiler.php projects/coolify/app/ --mode=ext -o coolify -``` - -**输出文件**: -- 生成 `.so` 共享库文件(Linux)或 `.dll` 动态链接库(Windows) -- 可以作为 PHP 扩展加载到 php-fpm 中 - -**特点**: -- ✅ 作为 PHP 扩展运行在 php-fpm 环境中 -- ✅ 利用现有的 PHP 运行时环境 -- ✅ 适合 Web 应用场景 -- ❌ **不需要 `main()` 函数**(即使编写了也不会被执行) -- ❌ 代码通过 PHP 请求生命周期执行 - -**使用场景**: -- Web 应用程序 -- 需要与现有 PHP 项目集成的场景 -- 依赖 php-fpm 的生产环境 - -**代码结构示例**: -```php -run(); -} - -// 或者带参数的 main 函数 -function main(int $argc, array $argv) { - echo "Arguments count: {$argc}\n"; - print_r($argv); - - $app = new Application(); - $app->run(); -} -``` - -## ❌ 不支持的语法 (Not Supported) - -以下语法明确不被 PHP AOT 编译器支持,相关测试文件已标记为 SKIP。 - -### 1. Generator Yield 语法 - -**状态**: 不支持 -**PHP 版本**: 5.5+ -**描述**: 生成器函数和 yield 关键字 - -**示例代码**: -```php -function range_generator($start, $end) { - for ($i = $start; $i <= $end; $i++) { - yield $i; - } -} - -foreach (range_generator(1, 5) as $num) { - var_dump($num); -} -``` - -**原因**: -- 生成器需要运行时协程支持 -- AOT 编译时难以优化状态机转换 -- 与当前架构设计不兼容 - -**相关测试文件**: -- `tests/aot/generators.phpt` (SKIP) - -**替代方案**: -- 使用普通数组返回所有值 -- 使用 Iterator 接口实现自定义迭代器 - ---- - -### 2. 可变变量 (Variable Variables) - -**状态**: 不支持 -**PHP 版本**: 所有版本 -**描述**: 使用 `$$` 符号的动态变量名 - -**示例代码**: -```php -$var_name = 'foo'; -$$var_name = 'bar'; // 等同于 $foo = 'bar' -echo $foo; // 输出 'bar' - -// 或更复杂的场景 -$a = 'b'; -$b = 'c'; -$c = 'd'; -echo $$$a; // 输出 'd' -``` - -**原因**: -- 静态分析无法确定变量名 -- AOT 编译时无法解析动态变量 -- 类型推断和内存布局无法确定 - -**相关测试文件**: -- 涉及 `$$` 语法的测试文件 (SKIP) - -**替代方案**: -- 使用数组存储动态键值 -- 使用对象属性代替动态变量 -- 使用反射 API(如果必须) - ---- - -### 3. 类的注解/属性语法 (Attributes/Annotations) - -**状态**: 不支持 -**PHP 版本**: 8.0+ -**描述**: 使用 `#[Attribute]` 语法的元数据 - -**示例代码**: -```php -#[Attribute(Attribute::TARGET_CLASS)] -class Route { - public string $path; - - public function __construct(string $path) { - $this->path = $path; - } -} - -#[Route('/api/users')] -class UserController { - #[Cache(ttl: 3600)] - public function getUsers() { - return "Getting users"; - } -} - -// 通过反射读取 -$reflection = new ReflectionClass(UserController::class); -$attributes = $reflection->getAttributes(); -``` - -**原因**: -- Attribute 需要完整的反射 API 支持 -- 运行时元数据查询需要额外开销 -- 与 AOT 静态编译理念冲突 - -**相关测试文件**: -- `tests/aot/attributes.phpt` (SKIP) - -**替代方案**: -- 使用传统的 PHPDoc 注释 -- 使用配置文件定义元数据 -- 使用常量或配置类 - ---- - -### 4. 复杂动态属性访问链 - -**状态**: 不支持 -**PHP 版本**: 所有版本 -**描述**: 连续的动态属性访问和条件赋值 - -**示例代码**: -```php -class Worker { - public $context; -} - -$worker = new Worker(); -$prop = 'name'; - -// 复杂的动态属性访问链 -!isset($worker->$prop) && !isset($worker->context->$prop) && $worker->context->$prop = 'value'; -``` - -**原因**: -- 多重动态属性访问难以静态分析 -- 条件赋值链的执行顺序复杂 -- 可能存在未初始化对象的访问 - -**相关测试文件**: -- `tests/aot/prop-001.phpt` (SKIP) - -**替代方案**: -- 分步检查每个属性是否存在 -- 使用明确的 if 语句而不是逻辑运算符短路 -- 先确保对象已初始化再访问属性 - ---- - -### 5. 闭包中的引用参数 - -**状态**: 不支持 -**PHP 版本**: 所有版本 -**描述**: 闭包函数使用引用参数 - -**示例代码**: -```php -$testFn = function (&$data) { - $data .= " bar"; -}; - -$s = "foo"; -$testFn($s); -var_dump($s); // 输出 "foo bar" -``` - -**原因**: -- 引用参数的内存管理复杂 -- 闭包捕获引用的生命周期难以追踪 -- 与值传递相比实现难度更高 - -**相关测试文件**: -- `tests/aot/ref-closure-param.phpt` (SKIP) - -**替代方案**: -- 使用返回值代替引用修改 -- 使用对象属性(对象是按引用传递的) -- 重新设计函数签名避免引用 - ---- - -### 6. 引用参数带有默认值 - -**状态**: 不支持 -**PHP 版本**: 所有版本 -**描述**: 函数参数声明为引用传递同时带有默认值 - -**示例代码**: -``` -items = $items; - } - } - - function modify(Container $container): void { - foreach ($container->items as &$item) { - $item = transform($item); - } - } - ``` - ---- - -### 8. innerHTML 等 DOM 操作 - -**状态**: 不支持 -**PHP 版本**: 所有版本 -**描述**: JavaScript 风格的 DOM 操作和内联 HTML 解析 - -**示例代码**: -``` -// 不支持 JavaScript 风格的 DOM 操作 -$element->innerHTML = '
Test
'); -$body = $doc->body->innerHTML; // 不支持 -``` - -**原因**: -- PHP AOT 编译器专注于 PHP 语言核心特性 -- DOM 操作需要完整的浏览器环境模拟 -- innerHTML 是 Web API,不是 PHP 原生功能 - -**相关测试文件**: -- 涉及 DOM 操作的测试文件 (SKIP) - -**替代方案**: -- 使用 PHP 原生的 DOMDocument API -- 使用字符串处理函数操作 HTML -- 使用专门的 HTML 解析库(如 simplehtmldom) - ---- - -### 9. 重复的函数名和类名 - -**状态**: 不支持 -**PHP 版本**: 所有版本 -**描述**: AOT 编译器禁止在同一作用域内定义同名的函数或类,而标准 PHP 允许这种情况(后定义的会覆盖先前的定义) - -**示例代码**: -``` - 1)跳出或继续多层嵌套循环结构。只支持 `break` 和 `continue`(不带数字参数或参数为 1)。 - -**示例代码**: -``` - 50) { - break 2; // ❌ 错误:不支持跳出多层循环 - } - } -} - -// ❌ AOT 编译器不支持:continue 3 继续外层循环 -for ($i = 0; $i < 10; $i++) { - for ($j = 0; $j < 10; $j++) { - for ($k = 0; $k < 10; $k++) { - if ($i + $j + $k > 20) { - continue 3; // ❌ 错误:不支持继续多层循环 - } - } - } -} - -// ✅ 正确做法:使用 goto -for ($i = 0; $i < 10; $i++) { - for ($j = 0; $j < 10; $j++) { - if ($i * $j > 50) { - goto end_loop; // ✅ 使用 goto 跳出 - } - } -} -end_loop: -echo "Loop ended"; - -// ✅ 正确做法:使用标志变量 -$found = false; -for ($i = 0; $i < 10 && !$found; $i++) { - for ($j = 0; $j < 10 && !$found; $j++) { - if ($i * $j > 50) { - $found = true; // ✅ 设置标志 - } - } -} - -// ✅ 正确做法:提取为函数并使用 return -function findValue() { - for ($i = 0; $i < 10; $i++) { - for ($j = 0; $j < 10; $j++) { - if ($i * $j > 50) { - return [$i, $j]; // ✅ 直接返回 - } - } - } - return null; -} - -$result = findValue(); -``` - -**与标准 PHP 的区别**: - -在标准 PHP 中: -```php - 50) { - break 2; // 跳出两层循环 - } - echo "$i * $j = " . ($i * $j) . "\n"; - } -} -echo "Done"; -``` - -在 AOT 编译器中: -```php - 50) { - break 2; // Compile Error: break/continue with level > 1 is not supported - } - } -} -``` - -**原因**: -- 多层 break/continue 需要复杂的控制流分析 -- C++ 后端没有直接对应的语法结构 -- 生成的代码难以优化和维护 -- 容易导致代码逻辑混乱,降低可读性 -- 与现代编程最佳实践不符 - -**相关测试文件**: -- 涉及 `break N` 或 `continue N` (N > 1) 的测试文件 (SKIP) - -**替代方案**: - -1. **使用 goto 语句** - ```php - 20) { - goto exit_loops; // 跳出所有循环 - } - } - } - } - exit_loops: - echo "Exited loops at i=$i, j=$j, k=$k"; - ``` - -2. **使用标志变量** - ```php - 50) { - $should_break = true; - } - } - } - ``` - -3. **提取为函数并使用 return** - ```php - $row) { - foreach ($row as $j => $value) { - if ($value === $target) { - return [$i, $j]; // 找到即返回 - } - } - } - return null; - } - - $result = searchMatrix($matrix, 42); - ``` - -4. **使用 try/catch 异常机制** - ```php - 50) { - throw new LoopBreakException("Found at $i, $j"); - } - } - } - } catch (LoopBreakException $e) { - echo $e->getMessage(); - } - ``` - -5. **重构为单层循环** - ```php - 50) { - break; // 只需普通 break - } - } - ``` - -**最佳实践建议**: - -- ✅ **优先使用函数提取**:将复杂的多层循环逻辑提取为独立函数,使用 return 退出 -- ✅ **使用标志变量**:对于简单的双层循环,使用布尔标志更清晰 -- ⚠️ **谨慎使用 goto**:虽然有效,但过度使用会降低代码可读性 -- ⚠️ **避免深层嵌套**:如果需要使用 break 3 或更高层级,考虑重构代码结构 -- ❌ **不要依赖异常控制流程**:try/catch 方案仅作为最后手段,性能较差 - ---- - -### 11. 字符串越界访问行为差异 - -**状态**: 行为不一致 -**PHP 版本**: 所有版本 -**描述**: AOT 编译器对字符串越界访问的处理与标准 PHP 不同。当访问超出字符串长度的索引时,AOT 编译器会抛出致命错误(Fatal Error),而标准 PHP 只会产生警告(Warning)并返回空字符串。 - -**示例代码**: -``` -= 0 && $index < strlen($str)) { - $char = $str[$index]; - echo $char; - } else { - echo "Index out of bounds"; - } - ``` - -2. **使用三元运算符** - ```php - = 0 && $index < strlen($str)) ? $str[$index] : ''; - echo $char; // 输出空字符串 - ``` - -3. **封装安全访问函数** - ```php - = 0 && $index < strlen($str)) { - return $str[$index]; - } - return ''; // 返回空字符串 - } - - $str = "Hello"; - echo safeCharAccess($str, 1); // 输出:e - echo safeCharAccess($str, 10); // 输出:(空) - echo safeCharAccess($str, -1); // 输出:(空) - ``` - -4. **使用 substr 函数** - ```php - getMessage(); - // 输出:Caught error: String offset `10` out of range - } - ``` - -**最佳实践建议**: - -- ✅ **始终检查边界**:在访问字符串索引前验证索引范围 -- ✅ **使用辅助函数**:封装安全的字符访问逻辑,提高代码复用性 -- ✅ **明确错误处理**:对于可能越界的场景,使用 try/catch 或条件判断 -- ⚠️ **注意性能**:频繁的边界检查会影响性能,关键路径可考虑其他方案 -- ❌ **不要依赖警告**:AOT 编译器会将越界访问升级为致命错误 -- ❌ **避免硬编码索引**:使用变量和动态计算时要格外小心 - -**迁移指南**: - -如果你的代码在标准 PHP 中依赖越界访问返回空字符串的行为,需要进行以下修改: - -```php - 0) ? $str[0] : ''; - -// 或者 -$firstChar = safeCharAccess($str, 0); -``` - -**常见陷阱**: - -1. **空字符串访问** - ```php - 0 ? $str[0] : ''; - ``` - -2. **循环中的索引** - ```php - 0) { - echo $input[0]; - } - ``` - ---- - -### 12. eval() 中的变量作用域限制 - -**状态**: 部分支持 -**PHP 版本**: 所有版本 -**描述**: AOT 编译器支持 `eval()` 语言结构,但 eval 内执行的 PHP 代码无法访问编译后函数的局部变量,只能通过 `$GLOBALS` 或 `return` 语句与编译代码交互。 - -**示例代码**: -``` -doSomething(); - echo helperFunction(); - echo MY_CONSTANT; -} -``` - ---- - -## ⏳ 尚未支持但计划支持的语法 (Pending Support) - -以下语法目前不支持,但已在开发计划中。 - -### 1. Traits 基础语法 - -**状态**: 计划支持 -**PHP 版本**: 5.4+ -**描述**: 代码复用机制 - -**示例代码**: -```php -trait Greeting { - public function sayHello() { - return "Hello"; - } -} - -class Person { - use Greeting; -} - -$person = new Person(); -echo $person->sayHello(); // 输出 "Hello" -``` - -**当前问题**: -- Trait 的代码注入机制复杂 -- 方法优先级和冲突解决需要特殊处理 -- 抽象方法和接口的交互需要完善 - -**相关测试文件**: -- `tests/aot/trait-basic.phpt` (SKIP - PENDING) - -**预计支持时间**: 未来版本 - ---- - -### 2. 在类中使用 Traits - -**状态**: 计划支持 -**PHP 版本**: 5.4+ -**描述**: 类中引入 trait 的方法 - -**示例代码**: -```php -trait Loggable { - public function log($message) { - echo "[LOG]: {$message}\n"; - } -} - -trait Timestamps { - public function getCreatedAt() { - return date('Y-m-d H:i:s'); - } -} - -class User { - use Loggable, Timestamps; - - private $name; - - public function __construct($name) { - $this->name = $name; - } -} - -$user = new User('John'); -$user->log('User created'); -echo $user->getCreatedAt(); -``` - -**当前问题**: -- 多个 trait 的组合逻辑 -- 命名冲突的处理 -- 访问修饰符的继承规则 - -**相关测试文件**: -- `tests/aot/trait-basic.phpt` (SKIP - PENDING) - -**预计支持时间**: 未来版本 - ---- - -## ✅ 已支持的语法 (Supported) - -以下为主要已支持的 PHP 语法特性(部分列表): - -### 基础语法 -- ✅ 算术运算符 (`+`, `-`, `*`, `/`, `%`, `**`) -- ✅ 比较运算符 (`==`, `===`, `!=`, `!==`, `<`, `>`, `<=`, `>=`) -- ✅ 逻辑运算符 (`&&`, `||`, `!`, `xor`) -- ✅ 赋值运算符 (`=`, `+=`, `-=`, `*=`, `/=`, `%=`) -- ✅ 三元运算符 (`?:`, `??`) -- ✅ 空合并运算符 (`??`, `??=`) - -### 控制结构 -- ✅ if/else/elseif -- ✅ switch/case -- ✅ for/while/do-while -- ✅ foreach (包括引用) -- ✅ break/continue -- ✅ try-catch-finally -- ✅ throw - -### 函数 -- ✅ 函数定义和调用 -- ✅ 参数传递(值传递、引用传递) -- ✅ 默认参数 -- ✅ 可变参数 (`...$args`) -- ✅ 命名参数 (PHP 8.0+) -- ✅ 返回类型声明 -- ✅ 闭包 (Closure) -- ✅ 箭头函数 (PHP 8.0+) -- ✅ 匿名函数 - -### 类与对象 -- ✅ 类定义和实例化 -- ✅ 构造函数和析构函数 -- ✅ 属性访问(public/protected/private) -- ✅ 方法调用 -- ✅ 静态属性和方法 -- ✅ 常量 -- ✅ 继承和重写 -- ✅ 抽象类和接口 -- ✅ 枚举 (PHP 8.1+) -- ✅ 匿名类 -- ✅ 对象克隆 -- ✅ 序列化/反序列化 - -### 类型系统 -- ✅ 标量类型(int, float, string, bool) -- ✅ 复合类型(array, object, callable, iterable) -- ✅ 可空类型 (`?T`) -- ✅ 联合类型 (PHP 8.0+) -- ✅ mixed 类型 -- ✅ void 返回类型 -- ✅ never 返回类型 (PHP 8.0+) -- ✅ 严格类型模式 (`declare(strict_types=1)`) - -### 数组 -- ✅ 数组创建和访问 -- ✅ 关联数组 -- ✅ 多维数组 -- ✅ 数组展开 (`...$array`) -- ✅ list() 解构 -- ✅ 数组函数(sort, array_map, array_filter 等) - -### 字符串 -- ✅ 字符串连接 -- ✅ 字符串函数(strlen, substr, str_replace 等) -- ✅ 字符串格式化 -- ✅ Heredoc/Nowdoc - -### 变量和作用域 -- ✅ 变量定义和使用 -- ✅ 局部变量 -- ✅ 全局变量 (`global`) -- ✅ 静态变量 (`static`) -- ✅ 引用 - -### 高级特性 -- ✅ 命名空间 -- ✅ 自动加载 -- ✅ Magic 方法(__get, __set, __call, __invoke 等) -- ✅ Iterator 接口 -- ✅ 后期静态绑定 (`static::`) -- ✅ Match 表达式 (PHP 8.0+) -- ✅ Constructor 属性提升 (PHP 8.0+) - ---- - -## 📋 测试文件 Skip 标记规范 - -### Skip 标记格式 - -对于不支持的测试文件,需要在 `--FILE--` 之前添加 `--SKIPIF--` 部分: - -``` ---TEST-- -测试描述 - ---SKIPIF-- - - ---FILE-- - - ---EXPECT-- -期望输出 -``` - -### Skip 原因说明 - -在 skip 脚本中应清楚说明跳过原因: - -1. **Generator**: `echo "skip Generator syntax not supported in AOT";` -2. **可变变量**: `echo "skip Variable variables (\$\$) not supported in AOT";` -3. **Attributes**: `echo "skip Attributes/Annotations not supported in AOT";` -4. **Traits**: `echo "skip Traits not yet supported in AOT";` - ---- - -## 🔧 开发和测试建议 - -### 对于开发者 - -1. **避免使用不支持的语法**: 在需要 AOT 编译的代码中,不要使用 generator、可变变量和 attributes -2. **使用替代方案**: 参考本文档提供的替代方案 -3. **关注更新**: 定期检查本文档了解新增支持的特性 - -### 对于测试人员 - -1. **识别不支持语法**: 运行测试前检查是否使用了不支持的语法 -2. **验证 Skip 标记**: 确保相关测试文件正确标记为 skip -3. **报告问题**: 发现未记录的不支持语法时及时报告 - -### 对于贡献者 - -1. **实现新特性**: 参考 pending 列表中的语法进行开发 -2. **更新文档**: 支持新语法后及时更新本文档 -3. **添加测试**: 为新支持的语法添加完整的测试用例 - ---- - -## 📊 统计信息 - -| 类别 | 数量 | 百分比 | -|------|------|--------| -| 不支持的语法 | 13 | - | -| 计划支持的语法 | 2 | - | -| 已支持的语法 | 50+ | ~81% | - -**总测试文件数**: 126 个 -**Skip 测试数**: 12 个(根据实际标记数量) -**正常测试数**: 114 个 - ---- - -## 📝 更新日志 - -### 2026-06-13 -- 新增 **类继承的编译期严格检查**: AOT 编译器在编译期对类继承关系进行比标准 PHP 更严格的检查 -- **禁止实例化抽象类**: 标准 PHP 中为运行时错误,AOT 在编译期检测 -- **禁止调用父类抽象方法**: 编译期直接拦截未实现的抽象方法调用 -- **禁止覆盖父类私有方法**: 与标准 PHP 最大的行为差异,PHP 允许而 AOT 禁止子类定义与父类私有方法同名的方法 - -### 2024-04-07 -- 新增 3 个不支持的语法特性 -- **重复的函数名和类名**: AOT 编译器禁止在同一作用域内定义同名的函数或类,与标准 PHP 不同。PHP 允许后定义覆盖先前的定义,而 AOT 编译器会在编译时报错。 -- **break/continue 跳出多层循环**: AOT 编译器不支持 `break N` 或 `continue N` (N > 1) 跳出或继续多层嵌套循环。需要使用 goto、标志变量、函数返回或 try/catch 代替。 -- **字符串越界访问行为差异**: AOT 编译器对字符串越界访问抛出致命错误(Fatal Error),而标准 PHP 只产生警告(Warning)。需要添加边界检查或使用安全的访问方式。 -- 添加详细的对比说明和替代方案(命名空间、条件定义、goto、标志变量、边界检查等) -- 更新统计信息和快速参考表 - -### 2024-03-20 -- 新增 2 个不支持的语法特性 -- **引用参数带有默认值**: 由于编译期内存管理复杂性,不支持 `function foo(array &$ref = [])` -- **变长参数中使用引用**: 由于变长参数的动态性与静态编译冲突,不支持 `function foo(&...$args)` -- 为相关测试文件添加 skip 标记 -- 更新统计信息和快速参考表 - -### 2024-XX-XX -- 初始版本发布 -- 记录 3 个不支持的语法特性 -- 记录 2 个计划支持的语法特性 -- 为相关测试文件添加 skip 标记 - ---- - -## 🔗 相关链接 - -- [PHP 官方文档](https://www.php.net/manual/en/) -- [PHP AOT 编译器项目](README.md) -- [测试运行指南](tests/aot/RUN_TESTS_GUIDE.md) -- [测试覆盖总结](tests/aot/README_TEST_COVERAGE.md) - ---- - -## ❓ 常见问题 - -### Q: 为什么这些语法不被支持? -A: AOT 编译器采用静态编译方式,某些 PHP 动态特性(如可变变量、生成器)需要在运行时动态解析,与 AOT 的设计理念冲突。 - -### Q: AOT 编译器允许重复的函数名或类名吗? -A: **不允许**。与标准 PHP 不同,AOT 编译器禁止在同一作用域内定义同名的函数或类。PHP 允许后定义覆盖先前的定义(会产生警告),而 AOT 编译器会在编译期直接报错。这是为了保证符号表的唯一性和编译的确定性。建议使用命名空间、不同的名称或条件定义来避免冲突。 - -### Q: AOT 编译器支持 break 2 或 continue 2 跳出多层循环吗? -A: **不支持**。AOT 编译器只支持普通的 `break` 和 `continue`(跳出或继续当前层循环),不支持 `break N` 或 `continue N` (N > 1) 跳出或继续多层嵌套循环。替代方案包括:使用 goto 语句、设置标志变量、将逻辑提取为函数并使用 return、或使用 try/catch 异常机制。推荐使用函数提取的方式,代码更清晰易维护。 - -### Q: AOT 编译器的字符串越界访问行为与 PHP 有什么不同? -A: **行为不一致**。标准 PHP 在访问超出字符串长度的索引时只会产生警告(Warning)并返回空字符串,程序继续执行。而 AOT 编译器会抛出致命错误(Fatal Error: String offset out of range),导致程序终止。这是因为 AOT 采用更严格的边界检查机制。建议在访问字符串索引前先检查长度,或使用 substr() 函数等安全方式。 - -### Q: 什么时候会支持 Traits? -A: Traits 已在开发计划中,具体支持时间取决于开发进度和社区需求。请查看项目路线图获取最新信息。 - -### Q: 如何知道某个语法是否被支持? -A: 查阅本文档的“已支持的语法”部分,或尝试编译代码查看是否有错误提示。 - -### Q: 我可以使用 PHP 8.x 的新特性吗? -A: 大部分 PHP 8.x 特性已被支持,如 Match 表达式、命名参数、联合类型等。但不包括 Attributes。请查看“已支持的语法”列表确认。 - -### Q: 为什么 innerHTML 不支持? -A: innerHTML 是 JavaScript 的 DOM API,不是 PHP 的功能。PHP AOT 编译器专注于 PHP 语言核心特性,不提供浏览器环境模拟。 - -### Q: 什么是游离代码?为什么不支持? -A: 游离代码指在函数或方法之外直接执行的可执行表达式(如 echo、函数调用等)。AOT 编译需要明确的程序入口点,所有可执行代码必须在 `main()` 函数或类的方法中。 - ---- - -## 📝 快速参考 - -### 编译模式对比 - -| 特性 | 扩展模式 (--mode=ext) | 二进制模式 (默认) | -|------|---------------------|------------------| -| **输出文件** | .so / .dll | 可执行文件 | -| **运行环境** | php-fpm | 独立运行 | -| **main() 函数** | ❌ 不需要 | ✅ 必须 | -| **参数支持** | N/A | `main()` 或 `main(int $argc, array $argv)` | -| **使用场景** | Web 应用 | CLI 工具、服务 | -| **加载方式** | PHP 扩展加载 | 直接执行 | -| **依赖** | 需要 PHP 运行时 | 无依赖 | - -### 不支持的语法速查表 - -| 语法 | 状态 | 替代方案 | -|------|------|----------| -| Generator/Yield | ❌ 不支持 | 使用数组或 Iterator | -| 可变变量 ($$) | ❌ 不支持 | 使用数组或对象属性 | -| Attributes | ❌ 不支持 | 使用 PHPDoc 或配置文件 | -| Traits | ⏳ 计划中 | 使用继承或组合模式 | -| innerHTML/DOM | ❌ 不支持 | 使用 DOMDocument 或字符串处理 | -| 游离代码 | ❌ 不支持 | 将所有代码放入 main() 函数 | -| **引用参数默认值** | ❌ 不支持 | 使用值传递 + 返回值或 null 默认值 | -| **变长引用参数** | ❌ 不支持 | 使用数组参数代替 | -| **重复函数/类名** | ❌ 不支持 | 使用命名空间、不同名称或条件定义 | -| **break/continue N** | ❌ 不支持 | 使用 goto、标志变量、函数返回或 try/catch | -| **字符串越界访问** | ⚠️ 行为不一致 | 添加边界检查或使用 substr() | -| **实例化抽象类** | ❌ 编译期错误 | 改用具体子类 | -| **调用父类抽象方法** | ❌ 编译期错误 | 实现抽象方法后调用 | -| **覆盖父类私有方法** | ❌ 编译期错误 | 使用不同的方法名 | - -### 正确的代码结构模板 - -``` -method(); - echo myHelper(); - echo MY_CONST; -} -``` - -### 常见错误示例 - -``` -