From 22160661a9835ba6d5aa38047ef12fb6cbe03225 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sun, 9 Aug 2026 21:01:10 +0800 Subject: [PATCH] docs(python): update design documentation and implementation plan - Move python/design.md to docs/python/design.md - Move python/implementation-plan.md to docs/python/implementation.md - Add clarification about unary operator limitations with dynamic ZendVM code - Update implementation status from in-progress to completed for first round CPython failure path audit - Add details about seventh round audit covering dynamic PHP Python operator protocols - Document performance benchmarks for module property and operator calls - Include memory gate results showing 0 invalid-access, 0 definite leak, 0 indirect leak - Update stage 6 final gate results with current test pass --- {python => docs/python}/design.md | 2 ++ {python => docs/python}/implementation-plan.md | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) rename {python => docs/python}/design.md (98%) rename {python => docs/python}/implementation-plan.md (80%) diff --git a/python/design.md b/docs/python/design.md similarity index 98% rename from python/design.md rename to docs/python/design.md index 8e85e957..f883c538 100644 --- a/python/design.md +++ b/docs/python/design.md @@ -669,6 +669,8 @@ $native = $pyInt->toPlainValue()->toInt() + 10; // 已显式转为 TypePHP int phpy 作为普通 PHP 扩展时,可以继续使用 Zend opcode handler 提供运算符重载兼容性;TypePHP 不依赖这些 handler。 +动态 ZendVM 代码存在一个明确保留的限制:Zend 会把 `-$value` / `+$value` 编译为乘以 `-1` / `1`,phpy 的 opcode handler 无法再识别源码中的一元运算。因此动态代码保持 `$value * -1` / `$value * 1` 的协议行为,不通过全局 AST hook 改写普通 PHP 代码;自定义 Python 对象的 `__neg__()` / `__pos__()` 与 `__mul__()` 不一致时,结果可能不同。TypePHP AOT 仍按照上表生成 `operator.neg()` / `operator.pos()`。外部用户文档 `python.md` 已明确说明这一限制。 + TypePHP 编译器在识别到静态类型为 `PyObject`、`PyDict` 等 phpy 对象时,把运算符改写为普通 Python module callable 调用: ```text diff --git a/python/implementation-plan.md b/docs/python/implementation-plan.md similarity index 80% rename from python/implementation-plan.md rename to docs/python/implementation-plan.md index e17cd27a..9658f10f 100644 --- a/python/implementation-plan.md +++ b/docs/python/implementation-plan.md @@ -68,7 +68,7 @@ phpy 同步完成了对象协议加固:`__set()` 转换引用释放、`__unset 5. 基准测试动态 Zend call、module map、参数转换和 `operator` module 调用;只优化被数据证明的热点。 6. 完整 PHPUnit、pytest、PHPT 和现有 TypePHP compiler 回归。 -实现状态:进行中。第一轮 CPython 失败路径审计已覆盖通用对象、list、dict、tuple、set 的构造和下标写入,以及 sequence/set 的 `contains()`。PHP 到 Python 的 key/value 转换失败现在会立即映射为 `PyError`,所有已取得的新引用均由作用域守卫释放;构造失败不再留下未处理的 CPython error indicator,`contains()` 的 `-1` 错误结果也不再被误判为 `true`。无效 UTF-8、unhashable set member、失败后容器仍可继续使用等路径已有 phpy PHPUnit 回归测试。 +实现状态:已完成。第一轮 CPython 失败路径审计已覆盖通用对象、list、dict、tuple、set 的构造和下标写入,以及 sequence/set 的 `contains()`。PHP 到 Python 的 key/value 转换失败现在会立即映射为 `PyError`,所有已取得的新引用均由作用域守卫释放;构造失败不再留下未处理的 CPython error indicator,`contains()` 的 `-1` 错误结果也不再被误判为 `true`。无效 UTF-8、unhashable set member、失败后容器仍可继续使用等路径已有 phpy PHPUnit 回归测试。 第二轮审计覆盖 module import、异常转换、callable 检查和显式 iterator API。`PyImport_ImportModule()`、`PyErr_Fetch()` 和 `PyIter_Next()` 转移给调用方的新引用现在都会在 Zend wrapper 取得独立引用后统一释放;重复 import、Python 异常或显式 iterator next 不再持续增加引用计数。调用非 callable 的 Python 属性或 `PyObject` 会稳定抛出 `PyError(TypeError)`,不再因为 `PyCallable_Check()` 未设置 error indicator 而静默返回 `null`。`PyCore::next()` 也会区分正常迭代结束和 iterator 异常。上述路径均先建立失败的 phpy PHPUnit 回归测试,其中对象调用行为另有 TypePHP PHPT 集成覆盖。 @@ -80,8 +80,18 @@ phpy 同步完成了对象协议加固:`__set()` 转换引用释放、`__unset 第六轮引用审计修复了 `PySequence::slice()` 的 new-reference 泄漏。切片在包装为 Zend `PyObject` 后会释放 CPython API 返回的原始所有权,同时保留 wrapper 自己持有的引用;由 `sys.getrefcount()` 压力测试验证重复创建并销毁切片不会继续增加元素引用计数。切片创建失败也会在接触空指针前转换为 `PyError`。 +第七轮审计覆盖动态 PHP 的 Python 运算符协议。phpy opcode handler 现在使用 CPython `PyNumber_*` / `PyObject_RichCompareBool()`,`/` 与 `/=` 使用 true division,复合赋值会用 in-place API 返回的对象更新 Zend 左值,并正确处理不可变 Python 对象、Zend 引用变量、表达式结果和失败后左值状态。`===` / `!==` 使用 Python object identity,bool cast、`!` 和条件分支使用 Python truth protocol;PHP 操作数转换、结果和异常路径的新引用统一由 RAII 守卫管理。TypePHP 的 `operators.phpt` 同时以 ZendPHP + opcode handler 和 AOT + `operator` module 运行,因此是两条实现的输出一致性门禁。 + +动态代码的一元正负运算已确定为兼容性边界:PHP 会把 `-$value` / `+$value` 编译成乘以 `-1` / `1`,opcode handler 无法区分它与源码中的显式乘法。动态 ZendVM 代码不再尝试改写 AST,而是明确保留 `$value * -1` / `$value * 1` 的行为。Python 内置数值和 NumPy 等常见对象的结果通常一致,但自定义对象的 `__neg__()` / `__pos__()` 可能与 `__mul__()` 不同。AOT TypePHP 保留原始 AST,仍分别 lowering 为 `operator.neg()` / `operator.pos()`,语义不受影响。外部用户文档 `python.md` 已将这一差异列入兼容性限制。 + +性能基准分别覆盖 module property、operator module call、已有 `PyObject` 参数和 PHP 标量参数转换。在当前未优化构建中,module property 约为 0.7–0.9 μs/op,operator call 约为 1.8–2.5 μs/op;参数是否预先包装为 `PyObject` 没有呈现稳定差异。尝试以 indirect zval wrapper 消除 module map 每次访问的引用计数后,A/B 中位数仍处于同一噪声区间,因此没有保留生命周期更敏感且收益未经证明的优化。 + +phpy 的 CMake Python-extension 目标同时完成了 out-of-tree 构建修复,并通过从 Zend 标准 cast handler 推导返回类型来兼容旧版 PHP 的 `int` ABI 与新版 PHP 的 `zend_result` ABI。PHP 8.1 和 PHP 8.4 均已完成全新构建验证。 + 内存门禁使用 Valgrind Memcheck 执行。测试关闭 Zend allocator 与 PCRE JIT,在最小独立进程中分别循环 100 次 PHP Closure kwargs 回调、可调用 PHP 对象 kwargs 回调、sequence slice 创建销毁、无效 Unicode 的对象字符串化、字典键转换和异常格式化。结果为 0 invalid-access、0 definite leak、0 indirect leak;进程退出时由 PHP/CPython 保留的 493,106 bytes 均为 still-reachable,不计为泄漏。ASan 扩展无法安全 `dlopen` 到当前启用了 `RTLD_DEEPBIND` 的非 ASan PHP,因此本轮采用不要求 PHP 同步重编译的 Valgrind 作为内存检查工具。 +阶段 6 最终门禁结果:phpy PHPUnit 135 tests / 469 assertions 通过(1 个既有 warning、1 个环境相关 skip),pytest 26/26 通过,TypePHP Python PHPT 14/14 通过,TypePHP PHPUnit 1103 tests / 2729 assertions 通过;TypePHP compiler 全量 PHPT 共 934 项,其中 932 PASS、2 SKIP、0 FAIL、0 WARN。动态 operator 压力测试在 Valgrind 下为 0 invalid access、0 definite leak、0 indirect leak。 + ## 阶段门禁 - 当前阶段的失败测试未先建立,不开始实现。