优先级说明:🔴 高优先级(重要功能/修复)|🟡 中优先级(功能完善)|🟢 低优先级(研究/优化)
- 常量支持: 支持用户定义的持久化常量(包括
define()和类常量)。 - 多版本兼容性: 支持 PHP 8.2、8.3、8.4、8.5 版本。
- 🟡 PHP 8.5 OPcache 兼容: 在 PHP 8.5 上实现深度 OPcache 集成——临时恢复原始
compile_file绕过 OPcache 钩子,编译后恢复。 - 🟢 JIT 优化支持: 研究 OpKit 与 JIT 的集成方案。JIT 是运行时热点代码编译优化技术,需探索与 Zend OPcache JIT 的共存机制或实现独立的 JIT 编译器。
- phpc 配置支持: 支持通过
opkit.json配置文件管理编译参数。 - 🟡 phpc 配置增强: 支持多路径配置(数组形式)、通配符匹配(如
src/*/Controller.php)及exclude忽略路径配置。 - 增量编译: 基于源码修改时间、System ID 和 Magic 值的智能增量编译。
- 交互式信息查看: 增强
phpc -i以支持列出详细的方法签名和类属性。- 🟡 显示类属性的完整类型信息(如
public string $name)
- 🟡 显示类属性的完整类型信息(如
- Composer 支持: 提供 Composer 插件,支持自动编译及扩展的编译安装。
- 静态分析支持: 增强
opkit_get_info导出详细元数据,实现phpc --stubs生成 PHP 定义存根,新增phpc analyze指令用于冲突检测。 - 🔴 完善常量与属性测试: 增加对普通常量(
define()/const)、类属性(含类型、默认值、访问修饰符)、类常量(含可见性修饰符)的全面测试用例。- 新增
tests/20_constants_comprehensive.phpt- 测试 namespace const、define()、类常量(含可见性修饰符)、trait 常量、interface 常量 - 新增
tests/21_properties_comprehensive.phpt- 测试类型属性、可见性、静态属性、readonly、联合类型、默认值 - 新增
tests/22_constants_properties_integration.phpt- 测试继承、抽象类、final 类、常量作为属性默认值
- 新增
- 🟢 全面基准测试套件: 开发标准基准测试,用于评估和展示不同类型应用程序的性能提升。
- 内存池优化: 实现 Metadata、Code、Data、Misc 四分区布局,减少碎片并提高 CPU 缓存命中率。
- 并发安全性: 实现原子写入(临时文件 + 重命名)和文件锁,确保多进程环境下文件不会损坏。
- 性能分析:
phpc提供详细的单个文件编译时间和总耗时统计。 - 友好错误提示: 编译失败时捕获并显示具体错误原因,支持跳过错误文件并继续批量编译。
- 🔴 opkit_boot 返回值: 当前返回类型为
mixed,当入口函数无返回值时会返回 NULL。应修复为只返回int,无返回值或返回非 int 时返回 0。 - 🔴 新增已加载检测函数: 新增函数如
opkit_is_loaded(string $filename): bool用于检查指定的.phpc文件是否已被加载,避免重复加载导致冲突。 - 🟡 优化 entry.php 生成格式:
opkit_gen_entry_file生成的入口文件存在多余换行,需优化代码生成逻辑使输出更紧凑。
- 🟢 Windows 支持: 验证并修复 Windows 系统上的兼容性问题(路径处理、文件锁定等)。
- 归档优化: 支持 Phar 压缩(GZip、BZip2)和数字签名。
✅ 嵌套数组常量序列化 (Test 20)
- 测试文件:
20_constants_comprehensive.phpt - 原始问题: 嵌套数组常量
['key' => [1,2,3]]的内部数组在序列化时发生字符串转换 (Array) - 根因: 嵌套数组的持久化路径不完整
- 修复:
zend_persist_zval中对IS_ARRAY类型已实现完整的递归持久化(通过zend_hash_persist和zend_persist_zval递归处理数组元素)。测试 20 和 22 在 PHP 8.2/8.3/8.4/8.5 下均通过。
✅ IS_CONSTANT_AST 内存泄漏 (原 Test 22 崩溃)
- 测试文件:
22_constants_properties_integration.phpt - 原始问题: 运行时崩溃 (Termsig=11) 及 4×32-byte
zend_ast_ref内存泄漏 - 根因: PHP 编译器 arena 在
zend_compile()返回前被销毁,但IS_CONSTANT_AST值(属性/参数默认值中的常量引用)仍指向已释放的 arena 内存。persist 阶段通过zend_persist_ast()调用efree(GC_AST(old_ref))释放了子指针而非old_ref本身,导致泄漏。 - 修复: 在
opkit_compile_file()中,注册完文件级常量后,遍历所有结构(类属性表、静态成员表、类常量、方法/函数 literals)调用zval_update_constant_ex()将IS_CONSTANT_AST解析为实际值。这样 persist 阶段不会遇到已释放的 arena 指针。
✅ phpc 配置增强
- 问题: 仅支持单个
src路径,不支持通配符和排除 - 修复:
src支持字符串或数组,支持glob()通配符模式(*,?,[]),新增exclude配置项(fnmatch匹配),配置路径可相对于 opkit.json 所在目录
✅ opkit_boot 返回值限定为 int
- 问题: 返回类型为
mixed,入口函数无返回值时返回 NULL - 修复: 两处调用
main()的位置均检查返回值类型,仅IS_LONG直接返回,其他情况返回 0。同时更新 stub.php 和 arginfo 的返回类型声明为int。
✅ Enum case AST 内存泄漏(编译含 enum 的代码库时泄漏)
- 问题: 编译
neuron-core(360 个文件)时报告 603 个内存泄漏,其中 562 个来自zend_string.h,30 个来自zend_ast.c - 根因 1 (enum):
opkit_update_constant_safe()对ZEND_AST_CONST_ENUM_INIT调用opkit_copy_ast_ref()复制 AST 后,未释放 PHP 编译器通过zend_ast_copy()分配的原始堆上 AST,导致每个 enum case 泄漏一个zend_ast_ref(含内部字符串) - 根因 2 (doc_comment):
src/opkit_zend_persist.c中对doc_comment的持久化被#if PHP_VERSION_ID < 80400错误包裹。PHP 8.4/8.5 的zend_op_array、zend_class_entry、zend_class_constant仍保留doc_comment字段,导致 doc comment 字符串既未持久化也未释放 - 修复:
src/opkit_compile.c: 替换 enum AST 前调用zend_ast_destroy(ast); efree(Z_AST_P(zv));src/opkit_zend_persist.c:op_array->doc_comment和zend_class_constant->doc_comment移除错误版本限制;zend_class_entry->doc_comment按版本区分(8.4+ 在 union 外,8.2/8.3 在info.user内);zend_property_info->doc_comment保持< 80400(PHP 8.4 确实移除了该字段)src/opkit_zend_persist.c: AST 持久化从zend_shared_memdup_put_free改为zend_shared_memdup,因为 arena 上的 AST 不应被efree;堆分配的 AST ref 在IS_CONSTANT_AST处理分支中通过zval_ptr_dtor_nogc释放src/opkit_wrapper.h: 补充定义zend_shared_memdup→_opkit_shared_memdup_put
- 验证: PHP 8.2-8.5 全版本编译
neuron-core后 0 泄漏,测试全部通过
✅ Persistence 后堆资源清理(dynamic_func_defs / static_variables)
- 问题:
zend_persist_op_array_ex通过_opkit_shared_memdup_put_free_*()释放了 opcodes/arg_info 等,但literals、dynamic_func_defs、static_variablesHashTable 结构仍留在堆上,导致大量字符串和对象泄漏 - 根因:
zend_hash_persist会释放 HashTable 的arData,导致持久化后无法安全遍历function_table/class_table来定位并清理这些残留资源 - 修复:
src/opkit_compile.c: 新增opkit_op_array_list收集器,在zend_accel_script_persist()之前收集所有 op_array 指针(main_op_array + file-level functions + class methods + property hooks)- 持久化完成后调用
opkit_destroy_op_array_safe():递归释放dynamic_func_defs数组、释放static_variablesHashTable 结构本身(arData 已被zend_hash_persist释放,不可二次释放) - 新增
opkit_free_ast_ref_list()统一释放opkit_copy_ast_ref创建的堆上 AST ref - 成功路径和
store_failure失败路径均执行清理,避免异常退出时泄漏
✅ phpc CLI 类自动加载
- 问题: 编译包含跨文件类引用的代码(如 enum case 或
new默认参数)时,zval_update_constant_ex触发zend_lookup_class,若类未加载会导致 fatal error - 修复:
bin/phpc在编译前预扫描所有源文件,构建 FQCN → 文件路径映射,注册spl_autoload_register回调按需require_once,解决编译时类依赖问题
| PHP 版本 | neuron-core | 单元测试 | 状态 |
|---|---|---|---|
| PHP 8.5.4 | 360/360 ✅ | 31 PASS / 0 FAIL ✅ | 已验证 |
| PHP 8.2.22 | 360/360 ✅ | TBD (php 路径不匹配) | 功能验证通过 |
(8.3/8.4 待验证)
tests/31_compile_file_basepath.phpt-opkit_compile_file显式 base_path 保留目录结构tests/32_compile_file_no_basepath.phpt-opkit_compile_file无 base_path 向后兼容(扁平输出)tests/33_enum_basic.phpt- Enum 支持(backed/unbacked,默认值)
tests/05_triple_des.phpt- 需要 openssl 扩展tests/18_property_hooks.phpt- PHP 8.4+ 专属(在 8.2/8.3 跳过)tests/24_php85_fcc_const.phpt- PHP 8.5+ 专属(在 8.2/8.3/8.4 跳过)tests/27_fork_shm.phpt/tests/29_shm_reset_fork.phpt- 需要 pcntl 扩展
✅ 编译失败路径全局表清理
- 问题: 批量编译时某个文件编译失败触发 bailout,
CG(function_table)/CG(class_table)/EG(zend_constants)中残留该文件添加的条目,导致后续编译冲突 - 修复:
opkit_compile_file()的!op_array分支中,通过zend_hash_del_bucket反向遍历删除超出orig_*_count的残留条目,同时调用opkit_free_ast_ref_list()释放已积累的 AST ref
✅ 持久化后 AST ref 列表 use-after-free
- 问题:
opkit_free_ast_ref_list()在持久化成功后访问node->ref,但持久化阶段zend_persist_zval()已通过efree(old_ref)释放了该 ref,导致 use-after-free - 修复: 新增
opkit_clear_ast_ref_list()仅释放追踪节点本身(不触碰 ref),在持久化成功路径和store_failure路径调用;编译失败路径保持opkit_free_ast_ref_list()(持久化未运行,ref 仍有效)
✅ 联合类型 arena 检查(zend_persist_type)
- 问题: 联合类型
A|B(两个类引用)的类型列表在编译阶段由 arena 分配。zend_compile()返回后 arena 已销毁,持久化时_opkit_shared_memdup_put_free_ms尝试efree()arena 指针,破坏 ZendMM 堆 - 修复:
zend_persist_type()增加ZEND_TYPE_USES_ARENA(*type) || zend_accel_in_shm(old_list)判断,arena 类型使用_opkit_shared_memdup_put_ms(拷贝后不释放)
✅ 字符串 Enum FQN >= 41 字符崩溃
- 问题: 3-case string-backed enum 在 FQN >= 41 字符时
zend_mm_heap corrupted(如NeuronAI\Chat\Enums\AttachmentContentType41字符) - 根因:
zend_persist_zval_calc的IS_CONSTANT_AST分支仅处理ZEND_AST_ZVAL/ZEND_AST_CONSTANT,跳过ZEND_AST_CONST_ENUM_INIT,导致未为 enum case AST(含zend_ast_ref包装、AST 节点、3 个子节点)预留内存。持久化阶段写入时溢出共享内存块 - 修复:
zend_persist_zval_calc增加 else 分支调用zend_persist_ast_calc处理其他 AST 类型(含 enum init);zend_persist_zval补充GC_SET_REFCOUNT/GC_ADD_FLAGS(GC_IMMUTABLE)/efree(old_ref)与 OPcache 对齐
✅ 编译顺序导致的跨文件类依赖(已修复,见 2026-05-14 #3)
问题:opkit_compile_file编译每个文件后将类/函数从全局表中zend_accel_move_user_*移出,导致后续文件编译时找不到之前的类影响: neuron-core 编译时 11 个文件报 Class not found- 修复:
bin/phpc编译前预扫描所有源文件构建 FQCN→路径映射,注册spl_autoload_register。编译期间遇到未知类时,autoloader 调用require_once加载依赖文件,类被注册到CG(class_table)后主编译继续。无需改动 C 代码
CLI 编译进程内存泄漏
- 现象:
phpc批量编译 360 个文件后有 ~572 个 ZendMM 内部泄漏(~100KB) - 来源: autoloader 的
require_once触发 PHP 原始编译器,产生的字符串/AST 分配未在 OpKit 清理路径中释放 - 影响: 仅 CLI 编译进程,运行时加载
.phpc无泄漏;进程退出后 OS 回收 - 降低措施: 新增
opkit_globals_mark/cleanupPHP 函数在编译结束后清理全局表;zend_persist_zval释放 enum AST 堆子节点
✅ phpc autoloader
- 问题: 跨文件类/枚举引用导致 "Class not found" 编译失败
- 修复:
bin/phpc编译前预扫描源文件,extract_fqcn()提取命名空间+类名,构建映射表,注册spl_autoload_register - 新增:
opkit_globals_mark()/opkit_globals_cleanup()PHP 函数,清理 autoloader 引入的全局表条目 - 新增:
zend_persist_zval在持久化后efreeenum AST 的zend_ast_zval堆子节点
✅ 1. arena 位清除顺序错误 (7e6a8b2)
zend_persist_type中ZEND_TYPE_FULL_MASK(*type) &= ~_ZEND_TYPE_ARENA_BIT在ZEND_TYPE_USES_ARENA(*type)检查前执行 → arena 类型列表走efree()破坏 ZendMM- 修复: 清除移到检查后,对齐所有 PHP 版本 OPcache
✅ 2. class method calc scope 去重对齐 (4fb640d)
zend_persist_class_method_calc无条件 xlat_table 去重,但 persist 对scope == ce不去重- 亲子类处理顺序不同时 calc 少算
sizeof(zend_op_array)= 256 bytes - 修复: calc 对齐 persist:
scope != ce查 xlat,scope == ce不计 xlat
✅ 3. persist 补齐 opcodes-in-xlat + cached-class 早返回 (5a7ba48)
zend_persist_op_array_ex缺少 calc 已有的两处早返回 → 共享 op_array 重复分配- 修复: persist 也检查
zend_shared_alloc_get_xlat_entry(op_array->opcodes)和ZEND_ACC_CACHED
✅ 4. phpc 单文件 autoloader + buffer margin (b4fa5bb)
- 单文件编译不注册 autoloader → 枚举类 "Class not found"
- calc/persist 残余溢出需 margin 防护
- 修复: phpc 单文件也扫描父目录;buffer 从 +64 → +4096
1. 残余 persist_calc 溢出(49 文件,64~432 bytes/files)
- 根因:
zend_persist_op_array_calc_ex与zend_persist_op_array_ex之间仍有去重逻辑不一致(同 fix #2/#3 模式但牵涉不同结构: dynamic_func_defs, static_variables, warnings, 继承属性) - 现象:
persisted_script->size > memory_used(现在 +4096 margin 防崩溃) - 计划:
- 对
zend_persist_op_array_calc(standalone funcs + dynamic_func_defs)→ 对齐 xlat_table 去重逻辑 - 对
zend_persist_property_info_calchooks → 对齐 persist 的zend_shared_memdup_put内隐去重 - 对
zend_persist_class_constant_calc→ 对照 persist 逐行查 skip 点 - 添加 persist 侧 per-partition 计数,与 calc 分区对比打印差异定位
- 目标: margin 回退到 +64
- 对
2. 编译进程内存泄漏(约 140+ leaks,shutdown 报告)
- ✅ 已确认为非真实泄漏: valgrind
--leak-check=full0 bytes definitely lost。PHP 的 shutdown 报告来自 ZendMM 进程退出时有意不释放的内存(OS 回收),与 OPCache 行为一致。 - ✅ 无需修复
3. PHP 8.2 验证
- PHP 8.2 有已知未初始化字段问题(
cache_size,num_dynamic_func_defs,static_variables等) - 当前仅在 PHP 8.5 测试通过;用户原始错误在 PHP 8.2
- 计划:
- 用
php-src/php-8.2.30/的 phpize 重编译 OpKit - 运行测试套件确认 29 pass / 0 fail
- 编译 neuron-core 360 文件验证无 crash
- 针对性修复 8.2 未初始化字段 guard(已有 sanity check 但仍需加固)
- 用
4. 边缘 case(来自 AGENTS.md)
- Class constant array keys 用
self::CONST,class 未链接时不可解析 →zval_update_constant_ex对 unresolved class 行为不确定 - 常量引用其他文件的常量 → 不在同批次编译时 runtime 才解析
- 计划:
- 为
self::CONST添加延迟解析(仿ZEND_AST_CONST_ENUM_INIT→ 拷贝到堆,运行时解析) - 对跨文件常量引用,利用 autoloader 提前加载(已完成),剩余 edge case 需 runtime 兜底
- 为