feat(compiler): implement typed reference system for native C++ integration

- Add five new reference types (INT_REF, STR_REF, FLOAT_REF, BOOL_REF, ARRAY_REF)
- Create typed reference ABI using C++ references for strict type safety
- Implement RefWrap<T> bridge for dynamic PHP reference compatibility
- Add getRawVarType() and update getVarType() to handle reference unwrapping
- Enforce strict typing rules preventing fixed storage corruption via references
- Support typed property and array element reference handling
- Implement exception-safe reference binding with proper
master
韩天峰 1 month ago
parent 2166e1a1a1
commit 50a8ce1570
  1. 15
      CHANGELOG.md
  2. 16
      docs/en/INCOMPATIBLE_PHP_FEATURES.md
  3. 11
      docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md
  4. 438
      docs/zh-cn/TYPED_NATIVE_REFERENCE_DESIGN.md
  5. 16
      src/CompilerBase.php

@ -27,9 +27,12 @@ to files that genuinely depend on Zend integer widening semantics.
Fixed-storage locals can no longer be converted to PHP references. This rule
applies to native scalars, strings, arrays, objects, streams, high-precision
values, and `std` containers. Zend references are untyped, so allowing one to
alias fixed C++ storage could corrupt the variable's type. Use `std::any()`
before reference operations and explicitly convert the result back afterward.
values, and `std` containers. A reference made from an ordinary local has no
Zend type source, so exposing fixed C++ storage through it could corrupt the
variable's type. Typed object/static properties remain reference-capable: Zend
records their property metadata as a type source. PHP array elements remain
dynamic reference-capable slots. Use `std::any()` when a local needs reference
semantics and explicitly convert the result back afterward.
TypePHP is always strict. Project sources no longer need
`declare(strict_types=1)`; the directive remains accepted as a redundant PHP
@ -98,8 +101,10 @@ should review the change log and run their full test suite before upgrading.
`use native_types`,并且只为真正依赖 Zend 整数扩展语义的文件添加新兼容模式。
固定存储的局部变量不再允许转换为 PHP 引用,包括原生标量、字符串、数组、对象、
stream、高精度值和 `std` 容器。Zend 引用没有类型约束,若允许其指向固定 C++ 存储,
可能破坏变量类型。需要引用操作时先使用 `std::any()`,操作完成后再显式转换回目标类型。
stream、高精度值和 `std` 容器。普通局部变量产生的引用没有 Zend type source,若允许
其指向固定 C++ 存储,可能破坏变量类型。Typed Property 仍然可以取引用:Zend 会把属性
元数据记录为 type source;PHP 数组元素也仍是可取引用的动态槽位。局部变量需要引用
语义时先使用 `std::any()`,操作完成后再显式转换回目标类型。
TypePHP 始终使用严格类型,项目源码不再需要 `declare(strict_types=1)`。该声明仍作为
冗余的 PHP 兼容语法被接受,而 `strict_types=0` 会被拒绝。

@ -105,13 +105,15 @@ incompatible with or more restrictive than standard PHP.
properties.
- A local with fixed storage (`Int`, `Float`, `Bool`, `Str`, `Array`, `Object`,
`Stream`, high-precision values, or a `std` container) cannot be made into a
PHP reference. Zend references are untyped and could replace such storage
with an incompatible value. Objects, streams, typed objects, and `std`
containers already use handle/reference-like value semantics, so adding a
PHP reference to the local variable is unnecessary as well. Initialize the
value with `std::any()` when PHP reference semantics are required, then
convert it back explicitly with a keyword such as `toArray()` or
`toString()`.
PHP reference. An ordinary local reference has no Zend type source and could
replace such storage with an incompatible value. Objects, streams, typed
objects, and `std` containers already use handle/reference-like value
semantics, so adding a PHP reference to the local variable is unnecessary as
well. Typed object/static properties remain reference-capable because Zend
attaches their property metadata to the reference; PHP array elements remain
dynamic reference-capable slots. Initialize a local with `std::any()` when
PHP reference semantics are required, then convert it back explicitly with a
keyword such as `toArray()` or `toString()`.
- A call that uses argument unpacking followed by named arguments falls back to
dynamic dispatch and cannot use the native call path.

@ -49,11 +49,12 @@
- 动态调用、闭包调用等编译期无法确定参数签名的调用,不能自动转换引用参数;需要显式使用 `std::ref()` 或等价关键词方法 `toRef()`。
- `std::ref()` / `toRef()` 只接受变量、数组元素或对象属性。
- 采用固定存储的局部变量(`Int`、`Float`、`Bool`、`Str`、`Array`、`Object`、
`Stream`、高精度值或 `std` 容器)不能转换为 PHP 引用。Zend 引用没有类型约束,
可能把固定存储替换成不兼容的值。对象、stream、typed object 与 `std` 容器本身
已采用句柄或引用式值语义,再对局部变量建立 PHP 引用也没有意义。确需 PHP 引用
语义时,应使用 `std::any()` 初始化,完成引用操作后再通过 `toArray()`、
`toString()` 等关键词显式转换回来。
`Stream`、高精度值或 `std` 容器)不能转换为 PHP 引用。普通局部引用没有 Zend
type source,可能把固定存储替换成不兼容的值。对象、stream、typed object 与
`std` 容器本身已采用句柄或引用式值语义,再对局部变量建立 PHP 引用也没有意义。
Typed Property 仍可取引用,Zend 会将属性元数据附加到引用上;PHP 数组元素仍是
可取引用的动态槽位。局部变量确需 PHP 引用语义时,应使用 `std::any()` 初始化,
完成引用操作后再通过 `toArray()`、`toString()` 等关键词显式转换回来。
- 带 unpack 且尾部追加 named arguments 的调用会退化为动态调用,不能使用 native call。
## 对象模型

@ -0,0 +1,438 @@
# 强类型原生引用设计
本文描述 TypePHP 0.8 的强类型引用模型。这里的“强类型引用”是 C++ 引用,
不是 `php::Ref` 的另一种名字。
## 目标
TypePHP 默认把 `int`、`string`、`float`、`bool`、`array` 保存为固定 C++
类型。引用不得迫使这些变量退化为 `php::Var`,也不得允许动态代码静默改变
它们的类型。
```php
$value = 100;
$alias =& $value;
```
上述变量的逻辑类型分别为 `int` 和 `int&`。在可证明安全的原生路径中,生成
等价的 C++ 引用,而不是创建 Zend reference:
```cpp
php::Int value = 100;
php::Int &alias = value;
```
## 类型系统
新增五种引用类型:
| TypePHP 类型 | 引用目标 | C++ ABI |
| --- | --- | --- |
| `INT_REF` | `int` | `php::Int &` |
| `STR_REF` | `string` | `php::Str &` |
| `FLOAT_REF` | `float` | `php::Float &` |
| `BOOL_REF` | `bool` | `php::Bool &` |
| `ARRAY_REF` | `array` | `php::Array &` |
`REF` 继续专指动态 PHP 引用,C++ 存储为 `php::Ref`。不能把 typed-ref 与
`REF` 合并,否则编译器会丢失引用目标类型,赋值、运算和静态调用都将退化为
动态 `zval` 操作。
以下帮助函数必须成为唯一的类型转换入口:
- `isTypedRefType($type)`:是否为五种 typed-ref;
- `getReferenceType($type)`:值类型转为 typed-ref;
- `getReferencedType($type)`:typed-ref 转为值类型;
- `getReferenceCppType($type)`:生成 `T &` ABI;
- `isAnyRefType($type)`:typed-ref 或动态 `REF`。
SSA 只能保留或缩窄引用目标类型,不得把 typed-ref 提升成 `VAR/REF`。
类型系统必须区分三个概念:
- **值类型**:表达式参与运算、普通赋值和按值传参时看到的 `INT/STR/...`;
- **绑定类型**:变量槽位是普通值、typed-ref,还是动态 `REF`;
- **引用根**:多个 typed-ref 最终指向的同一个固定存储变量。
例如 `$d =& $b` 中,`$b/$d` 的值类型都是 `INT`,绑定类型都是 `INT_REF`,
引用根都是 `$a`。绝大多数表达式只查询值类型;引用赋值、ABI、生命周期和移动优化
必须查询原始绑定类型,不能用解除引用后的类型代替。
建议在函数上下文中保存:
```text
localVars[$name] = INT | INT_REF | REF | ...
typedRefRoots[$name] = $rootName
typedRefBindings[$name] = $targetName
```
`getVarType()` 返回表达式值类型,`getRawVarType()` 返回绑定类型。
## 参数 ABI
带有五种明确类型的引用参数使用 C++ 引用:
```php
function increment(int &$value): void
{
$value++;
}
```
```cpp
void php_increment(php::Int &value);
```
无类型、`mixed`、`any`、union 或运行时才能确定的引用参数继续使用
`php::Ref`。对象、resource、stream、Native Object、typed object、Box 和
`std` container 不支持 typed-ref;这些值本来就使用句柄、指针或共享对象语义,
再对变量槽位取引用没有明确收益,反而允许调用方替换其静态类型。
## 属性和数组元素
普通对象属性及静态属性必须通过 Zend 属性 API 取得 `php::Ref`。Typed
Property 创建 reference 时,Zend 会把 `zend_property_info` 附加为 type
source,后续写入仍受属性声明类型约束。不能把属性读成临时值后再创建引用,
否则会丢失该 type source。
PHP 数组元素是动态 `zval` 槽位,引用统一使用 `php::Ref`。数组元素也可能与
Typed Property 共享同一个 `zend_reference`,因此桥接层必须保留原始 reference,
不得复制或解除引用。
当 typed-ref 参数接收属性或数组元素时,由 `php::RefWrap<T>` 在栈上把
`php::Ref` 转成 `T&`:
```cpp
php::Ref property = /* attrRef() */;
php::RefWrap<php::Str> bridge(property);
php_update_name(bridge.typed());
```
构造时必须验证当前值类型。`Int/Float` 可以直接引用 reference 内部 `zval`
payload;`Str/Array` 使用安全的 indirect wrapper,使原生函数的每次写入仍作用于
原始槽位。Zend 的 `bool` 只编码为 `IS_TRUE/IS_FALSE` 类型标签,并不存在可取地址
的 `bool` payload,因此 `Bool&` 必须使用栈上代理,并在本次调用结束时写回。
typed-ref 函数只能写入 `T`,因此不会破坏属性类型。
## typed-ref 转换为动态 PHP 引用
动态函数、动态方法和 Closure 的引用签名在编译期不可确定。调用者显式使用
`std::ref()` 时,`php::RefWrap<T>` 为 typed local 创建临时 `php::Ref`:
```cpp
php::Int value = 1;
php::RefWrap<php::Int> bridge(value);
php::call(callback, php::VarList{&bridge.ref()});
bridge.commit();
```
`commit()` 必须检查动态调用后的 zval 仍与 `T` 一致,然后写回 typed local;
类型不一致时抛出统一的 TypeError。不能依赖一个可能抛异常的析构函数完成检查。
桥接生命周期必须是“一次调用表达式”,不能是整个 PHP 语句。嵌套调用中,内层
调用返回后,必须在求值外层调用的下一个实参之前完成 `commit()`:
```php
outer(dynamicCall(std::ref($value)), nextArgument());
```
这里 `dynamicCall()` 的写回必须先于 `nextArgument()`。因此不能用语句级
`beforeStmtLines/afterStmtLines` 实现,而应把 wrapper、调用、成功写回和异常写回
统一放入一个立即执行的表达式 helper/lambda 中。多个 typed-ref 实参若指向同一变量,
必须复用同一个 bridge,不能创建内容互相覆盖的临时 reference。
调用抛出异常时,桥接层仍要按 PHP 的可观察语义处理已经发生的引用修改。生成代码
必须在重新抛出原异常前执行非破坏性的写回;若写回又发现类型错误,以类型错误替换
或链接到原异常的具体策略应由一个 PHPX helper 统一实现,不能在每个调用点复制
Zend 异常处理代码。
### `RefWrap` 的两种方向
`RefWrap<T>` 有两种互斥状态:
1. `T& -> php::Ref`:为动态调用创建临时 Zend reference,调用结束后验证并写回;
2. `php::Ref -> T&`:为静态 typed-ref 参数提供原生引用视图。
建议内部状态明确表示为 `NativeToZend`、`ConstrainedZendToNative`、
`DynamicZendToNative`,禁止用若干松散布尔值组合隐含方向。
| `T` | `php::Ref -> T&` 实现 | 是否需要写回代理 |
| --- | --- | --- |
| `Int` | 直接指向 `Z_LVAL_P(refval)` | 否 |
| `Float` | 直接指向 `Z_DVAL_P(refval)` | 否 |
| `Bool` | 栈上 `Bool` 代理 | 是 |
| `Str` | 指向 refval 的 indirect `Str` | 否 |
| `Array` | 指向 refval 的 indirect `Array` | 否 |
`Bool` 不能直接映射的原因来自 Zend ABI:布尔值由 zval 的 `IS_TRUE/IS_FALSE`
类型标签表达,没有独立的 `bool` payload。
“存在可寻址 payload”不代表任何 reference 都可以无条件直连。普通 PHP reference
可能还有其他动态别名;typed 函数内部若发生重入调用,动态代码可能把同一个 refval
改成其他类型。随后继续通过 `Int&/Float&` 访问已经不再活动的 union member 是不安全的。
因此 `php::Ref -> T&` 分成两种安全级别:
- reference 带有与 `T` 一致的 Zend typed-property type source:Zend 会阻止重入代码
改变类型,`Int/Float/Str/Array` 可以直接映射;
- 普通无 type source 的 reference:必须使用隔离代理。调用结束时比较原始 reference
与入口快照,检测重入别名修改;出现双方同时修改的冲突时抛出明确错误,不能静默覆盖。
`Bool` 在两种情况下都使用代理。直接映射前必须检查 reference 的全部 type sources,
不能仅凭当前 `Z_TYPE` 推断其以后不会改变。
### 调用表达式边界
C++17 对普通函数参数只保证参数之间不会交错求值,不保证从左到右:
```cpp
outer(first(), second()); // first/second 谁先执行未指定
```
而 braced initializer list 保证从左到右。TypePHP 固定参数容器已经使用
`php::VarList{...}`,typed-ref bridge 必须继续保留这一性质。实测 GCC 会先执行上述
示例的第二个参数,不能依赖某个编译器当前的选择。
临时对象通常在包含它的 full-expression 结束时析构。若把 `RefWrap` 临时对象直接
放入外层调用参数,它可能直到外层调用返回后才析构。正确结构是让每个需要桥接的
内层调用拥有独立的立即执行作用域:
```cpp
auto result = [&]() {
php::RefWrap<php::Int> bridge(value);
try {
auto retval = dynamic_call(bridge.ref());
bridge.commit();
return retval;
} catch (zend_object *) {
bridge.commitAfterException();
throw;
}
}();
```
这里 `commit()` 和 `bridge` 析构都发生在 lambda 产生结果之前。外层
`VarList{inner(), next()}` 才能保证 `inner()` 写回先于 `next()`。
### 异常路径
动态调用在修改引用后抛出异常时,PHP 会保留已经发生的修改。桥接也必须在重新抛出
原异常之前写回类型兼容的值:
```php
function mutateThenThrow(int &$value): never
{
$value = 99;
throw new RuntimeException();
}
```
若临时 reference 被改成不兼容类型,则不能写入固定存储。无异常返回时抛出 TypeError;
已有异常正在传播时,bridge 保留原异常并丢弃不兼容写回,避免在异常展开期间再次抛出
C++ 异常。这个规则必须集中在 helper 中,析构函数始终保持 `noexcept`。
### 别名与逃逸
同一次动态调用中,如果两个实参最终拥有相同引用根,必须复用一个 `RefWrap`:
```php
$b =& $a;
$d =& $b;
$callback(std::ref($b), std::ref($d));
```
否则两个临时 reference 会各自保存一份值,提交顺序将覆盖修改,也破坏 PHP 的别名
身份。编译器应按 `typedRefRoots` 对 bridge 去重。
动态 PHP 代码还可能把引用保存到全局、对象属性或 Closure 中,使其超过本次调用。
栈上 native storage 无法安全支持这种逃逸。`RefWrap` 应在调用返回时检测临时
`zend_reference` 是否出现额外持有者;检测到逃逸时抛出明确错误。该限制保证不会把
已经失效的 C++ 地址暴露给 ZendVM。需要长期 PHP 引用身份的变量必须从一开始使用
`std::any()`/`php::Ref`。
## 局部引用约束
固定局部变量之间的一次性引用绑定可生成 C++ 引用:
```php
$source = 1;
$alias =& $source;
```
引用传播遵循 PHP 的值/引用上下文区别:
```php
$a = 100;
$b =& $a; // $b: INT_REF,引用 $a
$c = $b; // $c: INT,按值复制并解除引用
$d =& $b; // $d: INT_REF,与 $b 一样引用 $a
```
因此,对 typed-ref 再取引用仍得到同一目标的 typed-ref;typed-ref 出现在普通赋值
右侧或普通值参数位置时必须物化为值。移动/last-use 优化不得消费 typed-ref 所指向的
原变量,尤其不能对 `Str&`、`Array&` 生成 `takeValue()` 或 `std::move()`。
### typed-ref 普通赋值
普通赋值修改引用目标,不会重新绑定引用:
```php
$a = 100;
$intRef =& $a;
$intRef = 101; // 修改 $a
$intRef = '100'; // 编译期错误
```
检查规则如下:
- RHS 类型已知且不满足引用目标类型:TypePHP 编译期 fatal error;
- RHS 类型已知且兼容:按目标值类型生成赋值;
- RHS 为 `VAR/REF`,无法静态确定:只求值一次,执行运行时严格类型检查后赋值;
- 禁止让不兼容表达式穿透到 C++ 编译阶段形成难以理解的运算符/模板错误。
`$c = $intRef` 属于值上下文,`$c` 推导为 `INT`;`$d =& $intRef` 属于引用上下文,
`$d` 推导为 `INT_REF` 并继承相同引用根。
由于 C++ 引用不能重新绑定,初版作如下限制:
- typed-ref 只允许在两个函数局部变量之间建立;LHS 必须是尚未声明的新变量,RHS
必须是已经存在的固定值 local 或 typed-ref;
- typed-ref 必须在编译器可证明的单一绑定点创建;
- 禁止在分支、循环中首次绑定 typed-ref;
- 禁止把已有 typed-ref 重新绑定到另一个变量;
- 禁止 `unset()` typed-ref;
- 当一个 fixed local 已成为其他 typed-ref 的引用根时,也禁止 `unset()` 或把该根
重新绑定为另一个引用;
- `$array[] =& $typedLocal`、`$object->prop =& $typedLocal`、`global/static` 等会让引用
超过 native local 生命周期的写法一律禁止;
- `$alias =& $object->typedProperty`、`$alias =& $array[$key]` 继续产生动态 `REF`,
不产生长期持有 `RefWrap` 的 C++ 引用;
- 禁止返回指向函数局部存储的 typed-ref;
- 禁止 Closure 按引用捕获 typed local,因为 Closure 可能超过栈帧生命周期;
- 参数 typed-ref 可以继续传给静态可解析的 typed-ref 参数。
Generator、可挂起 Fiber 回调以及任何可能超过调用者栈帧的执行体,不得持有指向调用者
native local 的 `T&`。初版应禁止这些函数使用 typed-ref 参数,而不是尝试推断实际
挂起路径。
返回引用暂不使用 `T&` ABI。对象属性、静态属性和数组元素的返回引用需要保留 Zend
reference/type source;局部 `T&` 又不能安全逃出当前栈帧。因此 `function &name()`
继续返回 `php::Ref`,并禁止返回 typed local/typed-ref 参数形成的临时 bridge。
无法证明稳定绑定时,用户必须显式使用 `std::any()` 选择动态 `php::Ref` 语义。
## 不变量
实现与优化器必须共同保持以下不变量:
1. **固定类型不变**:typed-ref 的目标在整个生命周期内始终是同一个 C++ 类型;
2. **绑定不变**:一个 C++ reference 构造后永不重新绑定;
3. **根唯一**:引用链被压缩到唯一 root,同一次调用中一个 root 只创建一个 bridge;
4. **只求值一次**:属性 receiver、数组 key、动态 callable 和每个参数表达式都不得重复求值;
5. **PHP 求值顺序**:参数及嵌套调用按照 PHP 的从左到右可观察顺序完成;
6. **调用级提交**:bridge 在本次调用完成后、下一个表达式开始前提交;
7. **不逃逸**:指向 native stack storage 的 Zend reference 不得在调用结束后存活;
8. **析构不抛异常**:所有失败通过显式 `commit()`/调用 helper 报告;
9. **值上下文解除引用**:普通赋值、按值参数、返回值和数组插入必须取得值快照;
10. **优化不得消费 root**:last-use、move、multi-return 和参数转发不得移动 typed-ref 的目标。
任何无法证明这些不变量的路径都应退回动态 `REF/VAR`(仅在源程序已经选择动态存储时),
或给出 TypePHP fatal error;不得生成“可能正确”的 C++。
## 语法到存储的决策表
| PHP 写法 | 结果 |
| --- | --- |
| 新 local `$b =& $a`,`$a` 是五种 fixed local | 对应的 `*_REF`,C++ `T&` |
| 新 local `$d =& $b`,`$b` 是 typed-ref | 相同 `*_REF`,引用根继承自 `$b` |
| `$c = $b`,`$b` 是 typed-ref | 对应值类型,按值复制 |
| `$b = $value`,`$b` 是 typed-ref | 检查后写入 root,不重新绑定 |
| `$b =& $other`,`$b` 已存在 | fatal error |
| `$b =& $property` / `$b =& $array[$key]` | 动态 `php::Ref` |
| `$property =& $typedLocal` / `$array[] =& $typedLocal` | fatal error,禁止 native 引用逃逸 |
| 精确 `int/string/float/bool/array &$arg` | native ABI `T&` |
| nullable/union/mixed/untyped/defaulted/variadic `&$arg` | Zend ABI `php::Ref` |
| object/resource/stream/container/typed-object `&$arg` | fatal error |
| 动态调用 `std::ref($typedLocal)` | 调用级 `RefWrap<T>` |
| 动态调用 `std::ref($property/arrayItem)` | 直接传原始 Zend reference |
## 调用分类
| 实参 | 静态 typed-ref 调用 | 动态调用/Closure |
| --- | --- | --- |
| typed local | 直接传 `T&` | `RefWrap<T>`,要求显式 `std::ref()` |
| typed-ref local/parameter | 直接传 `T&` | `RefWrap<T>`,要求显式 `std::ref()` |
| Typed Property | `Ref` → `RefWrap<T>::typed()` | 直接传原始 `php::Ref` |
| PHP 数组元素 | `Ref` → `RefWrap<T>::typed()` | 直接传原始 `php::Ref` |
| `php::Var/php::Ref` | 运行时校验后经 `RefWrap<T>` | 直接传 `php::Ref` |
## 性能模型
不能把正确性建立在“GCC 可能优化掉”之上。生成结构本身应让常见静态路径保持零成本,
编译器优化只负责消除栈对象和内联 helper。
| 路径 | 额外堆分配 | 预期开销 |
| --- | --- | --- |
| typed local -> TypePHP typed-ref 参数 | 0 | 直接 `T&`,与普通 C++ 调用一致 |
| typed-ref local/参数 -> TypePHP typed-ref 参数 | 0 | 直接转发相同 `T&` |
| local typed alias | 0 | C++ reference,优化后通常不占独立存储 |
| 已存在 Zend reference -> typed-ref | 0 | 栈上 wrapper/代理和引用计数操作 |
| property/array item 首次变成 Zend reference | 通常 1 | Zend 建立 `zend_reference`,PHP 自身也需要 |
| typed local -> 动态 Zend 引用调用 | 每个引用根通常 1 | 必须构造真实 `zend_reference`,明确慢路径 |
最后一项不能用栈上的伪造 `zend_reference` 替代。ZendVM、用户函数或扩展可能增加
引用计数并暂存该 reference;把 refcounted 对象放在 C++ 栈上会产生释放非法地址或悬空
引用。该分配只在用户显式 `std::ref($typedLocal)` 进入动态调用时发生,不得污染普通调用。
### 生成代码约束
- `RefWrap` 是 header-only、非虚类,不使用 `std::function`、`std::vector` 或 type erasure;
- wrapper、tuple/array 与立即执行 lambda 只使用栈存储;
- 同一调用按引用根去重,避免重复建立 `zend_reference`;
- 成功主路径不创建错误消息或临时字符串,异常检查放入 `UNEXPECTED` 冷分支;
- 不在循环或函数级缓存临时 Zend reference,避免引用身份错误和逃逸;
- 普通静态 typed-ref 调用不得生成方向判断、commit 或 refcount 操作;
- `Bool` 代理只用于 Zend bridge,native `Bool& -> Bool&` 仍是直接调用。
### 验证标准
重构完成后不仅运行功能测试,还要检查生成物:
1. 对纯 native typed-ref micro benchmark 生成 `-O3 -S` 汇编,确认 wrapper 和 lambda
不存在、调用可正常 inline;
2. 统计静态调用循环的 `malloc/emalloc` 次数,必须为 0;
3. 分别 benchmark `Int/Float/Bool/Str/Array` 的直接调用、property/array bridge 和动态
`std::ref()`;
4. 比较 `-O0` 生成 C++ 的尺寸,避免每个调用点展开大段异常处理;公共慢路径放在 PHPX
非模板 helper 中;
5. 使用 ASan/Valgrind 验证异常、逃逸检测和多别名调用没有泄漏或悬空引用。
目标不是让动态 Zend bridge 等同于直接 C++ 调用,而是保证零成本静态主路径,并把不可
避免的装箱和堆分配限制在用户显式选择的动态边界。
## 实施顺序
1. 增加五种类型及类型帮助函数,但暂不改变生成代码;
2. 将明确类型的引用参数 ABI 改为 `T&`,补齐 wrapper 入口;
3. 支持 typed local 的静态直接调用;
4. 实现属性、静态属性、数组元素到 `T&` 的 `RefWrap`;
5. 实现 typed local 到动态 `php::Ref` 的 `RefWrap` 与异常安全写回;
6. 最后支持受限的一次性局部 alias,并让 SSA/控制流检查拒绝重绑定。
每一步都必须同时覆盖命名函数、方法、构造函数、named arguments、静态调用、
动态调用、Closure、异常路径,以及实例/静态 Typed Property 和数组元素。
## PHP 8.5 与 C++17 验证结果
实际探针得到以下结论:
- PHP 按从左到右顺序求值调用参数;内层引用调用的修改在下一个参数求值前可见;
- 引用调用修改值后再抛异常,修改仍然保留;
- `$a/$b/$d` 在 `$b =& $a; $d =& $b` 后拥有相同的 `ReflectionReference` ID;
- `$c = $b` 得到独立值,后续修改 `$c` 不影响 `$a/$b/$d`;
- PHP 严格模式允许把普通 `int` 变量传给 `float&`,并把调用方变量改成 `float`。
这与固定 `Int` 存储冲突,TypePHP typed-ref 应拒绝这种跨类型引用转换;
- Typed Property 创建的 reference 保留类型约束,不兼容写入立即抛出 TypeError;
- C++17 普通参数顺序未指定;列表初始化从左到右;放在外层参数中的临时 bridge
可能直到外层函数返回后才析构;
- zval 的 `Int/Float` 有可寻址 payload,`Bool` 没有。

@ -977,7 +977,7 @@ class CompilerBase implements PropertyAccessContext
return $this->getPlatform()->removeCommonPrefix($short, $long);
}
protected function getVarType(string $name): string
protected function getRawVarType(string $name): string
{
if ($this->hasLocalVar($name)) {
return $this->context->localVars[$name];
@ -989,6 +989,16 @@ class CompilerBase implements PropertyAccessContext
return Type::VAR;
}
/**
* Return the value type visible to expressions. A native C++ reference has
* the same operators and assignment rules as its referenced value; only
* ABI/binding code should inspect getRawVarType().
*/
protected function getVarType(string $name): string
{
return Type::getReferencedType($this->getRawVarType($name));
}
/**
* Resolve the ClassDef for an object expression (variable or $this).
*/
@ -4984,7 +4994,9 @@ class CompilerBase implements PropertyAccessContext
if ($toType === Type::VAR or $fromType === Type::VAR) {
return true;
}
// References currently carry no type information, so treat them as var.
$toType = Type::getReferencedType($toType);
$fromType = Type::getReferencedType($fromType);
// Dynamic references carry no compile-time target type, so treat them as var.
if ($toType === Type::REF or $fromType === Type::REF) {
return true;
}

Loading…
Cancel
Save