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.
154 lines
25 KiB
154 lines
25 KiB
# 2026-08-04
|
|
|
|
## code-wiki 渲染路径修正
|
|
|
|
- 用户将 code-wiki skill 的 render.py 渲染目录从 `.workbuddy/wiki` 改为 `.agents/wiki`。
|
|
- 实测发现:render.py 的**文档注释**改了,但 `render()`(第243行)与 `browse()`(第328行)函数体仍硬编码 `.workbuddy`。已把这两处改为 `.agents` 并重新渲染成功。
|
|
- 结论:code-wiki 产物目录现统一为 **`.agents/wiki/`**(catalog.xml 在 `.agents/wiki/data/`,页面 md 在 `.agents/wiki/pages/`)。下次 render 直接 `python render.py render <ws> --lang zh`,无需再复制。
|
|
- 重新渲染结果:`[done] rendered 22 pages -> .agents/wiki/index.html`。
|
|
|
|
## 修复 Windows 资源编译 RC2135
|
|
|
|
- 报错:`app_resource.rc(1) : error RC2135 : file not found: by`(资源编译阶段)。
|
|
- 根因:`src/Build/ResourceCompilationTrait.php` 写 rc 文件时强制加了 UTF-8 BOM(`"\xEF\xBB\xBF" . $rcContent`),而 `src/Generator/ResourceFileGenerator.php` 生成的注释用 `//`。BOM 顶在文件头导致 rc.exe 第1行识别不出 `//` 注释,把 `Generated by TypePHP` 当语句、`by` 当文件名 → RC2135(老版/部分 Windows SDK 的 rc.exe 有此坑)。
|
|
- 修复:① 去掉 BOM(直接写 `$rcContent`);② 生成内容里的 `//` 注释全改为 `;`(rc 全版本支持的注释符)。中文靠 `#pragma code_page(65001)` 正常处理,无需 BOM。`.h` 头文件里的 `//` 不动(rc 不处理)。
|
|
- 重要:此修复需**重新编译 tpc.exe** 才生效——当前运行的预编译 `tpc.exe` 每次构建都会按自身逻辑重新生成 rc 文件,改 build/app_resource.rc 不持久。
|
|
- 临时绕过(不重编也能跑通):把 project.yml 的 `resource:` 整段(第10~24行 icon + version-info)注释掉,`hasResource()` 返回 false,资源编译被跳过,可先验证后续链接;编好带修复的 tpc.exe 后再恢复该段即可重新带图标/版本。
|
|
- 已应用临时绕过:project.yml 的 `resource:` 整段已改为全注释(带"待带修复的 tpc.exe 重新编译后恢复"标记)。用户可立即重跑 tpc.exe 验证链接链路。
|
|
|
|
## LNK1104 链接失败(输出 tpc.exe 被锁)
|
|
|
|
- 报错:`LINK : fatal error LNK1104: 无法打开文件"tpc.exe"`(乱码是 GBK 被当 UTF-8 解码的"无法打开文件")。
|
|
- 根因:project.yml `name: tpc` → 输出 `tpc.exe`;`name` 无路径 → `outputDir` 为空,TypePHP 把最终 exe 输出到 cwd 相对路径(`getTargetFileName()` 返回 `'tpc.exe'`,`/OUT:"tpc.exe"`)。用户从编译器自身目录 `D:\git\php\tpc_v1095_windows_x86_64` 启动 tpc.exe(cwd=该目录),于是 `/OUT:"tpc.exe"` 落到正在运行的 `D:\git\php\tpc_v1095_windows_x86_64\tpc.exe` 上,系统拒绝改写正在执行的 exe → LNK1104。
|
|
- 佐证:无残留 tpc 进程;`build\tpc.exe` 不存在;`build\` 可写;中间产物 rc 在 `tpc_v1095/build/` → cwd 即编译器目录。
|
|
- 修复:从项目目录 `D:\git\php\aot-compiler` 启动编译器(cd 过去再用绝对路径调 tpc.exe),输出落到 `aot-compiler/tpc.exe`,避开与运行中编译器同名冲突。备选:给 project.yml 换输出名/输出目录(不建议改 `name`,会影响生成符号)。
|
|
|
|
## tpc.exe 编译 hello.php 时 OOM(VirtualAlloc 失败)
|
|
|
|
- 现象:`.\tpc.exe examples/hello.php` 跑到打印第3条 cl 命令(typephp_fiber_generator.cc)后,`VirtualAlloc() failed [0x8 内存资源不足]` x2,随后 `Fatal error: Out of memory (allocated 8388608) (tried to allocate 140736835400432 ≈128TB) in src/compiler.php on line 4`。
|
|
- 诊断结论:**不是机器内存不够**。实测本机 96GB 物理内存(空闲 74GB)、页面文件空闲 81GB、20 核。`VirtualAlloc` 拒绝是因为请求了一个被算坏的 ~128TB 尺寸(符号扩展/未初始化),OOM 报告把这个坏尺寸打印出来——属症状非根因。
|
|
- Windows 上 `Platform/Windows::supportsPcntlParallelCompile()` 返回 false(`Windows.php:226`),`Translator.php:1441` 走 `compileSourceFile()` **顺序编译**(`passthru` 逐个跑 cl),**不并行**,`-j` 在此无效。
|
|
- 隔离测试:`--dry`(仅 prepare+convert 生成 C++)能干净退出(exit 0,不 OOM)→ bug 只在 **compile/build(链接)阶段**。源码 `compiler.php:8` 已 `ini_set('memory_limit','-1')`,且报错走的是 Zend MM "out of system memory" 路径(OS 拒 VirtualAlloc),非撞 memory_limit。
|
|
- 诡异点:该 tpc.exe 能编译整个 tpc 工程(project.yml)却编不了 151B 的 hello.php → 疑似"单文件 .php→exe"模式的专属 bug(parseArgv 对单文件默认也是 BUILD_MODE_BIN,与 project.yml 同模式,故非 buildMode 差异)。
|
|
- 待办/建议给用户:① `.\tpc.exe project.yml -O2 -j 8`(从 aot-compiler 目录)确认工程模式仍正常,隔离是否单文件模式专属;② 用 v1095 预编译 `tpc.exe` 跑 `examples/hello.php` 对比,判断是"当前源码回归"还是"v1095 误编译";③ 若需进一步定位,查单文件 build/link 路径里计算分配尺寸的代码(读取文件/字符串长度处)。
|
|
|
|
## 确认:崩的是"S1+S2 优化版",v1095 基础版不崩
|
|
|
|
- 用户确认:`tpc_v1095`(基础版,未做编译速度优化)编译 `examples/hello.php` **不崩**;刚编出来的 tpc.exe(带 S1+S2:ac7813d)崩 128TB。→ 回归在 S1+S2,或 v1095 误编译了 S1+S2 源码。
|
|
- S1+S2(ac7813d)只改 3 文件:`CompilerBase.php`/`Preprocessor.php`/`Translator.php`。compile 阶段新增代码**只有** S2 的 generated 缓存:`hasGeneratedObjectFileCache`/`getGeneratedObjectCacheKey`/`getGeneratedHeaderDependencies`/`isGeneratedSourceFile`/`compileFile` 的 `$isGenerated` 分支。
|
|
- 已逐行审 S2 缓存代码:均读小 metadata、算 sha256、拼字符串,**无直接大块分配**;且前两个 generated 文件(hello.cc/extension-hello.cc)已成功跑过缓存检查+编译(日志里 cl 命令已打印),说明新函数本身能跑。
|
|
- **S2 设计缺陷(确定要修)**:`getGeneratedHeaderDependencies()` 用 `FilesystemIterator` 扫描整个 `build/include` 收集所有 `*_arginfo.h`。但 `build/include` 被**上一次 tpc 工程编译的残留污染**(含 `php_src_CompilerBase_arginfo.h` 423KB、`php_tpc_func_decl.h`、`php_src_Translator_arginfo.h` 等几十个 tpc 工程头)。后果:① 编译 hello.php 时缓存 key 竟依赖无关 tpc 工程头文件(增量缓存正确性错误);② 每个 generated 单元算 key 都要 `hash_update_file` 这些大残留头,浪费且可能触发异常路径。
|
|
- **本机 Bash 无法复现**:缺 vcvars/cl 时该 AOT 二进制静默 exit 0 无输出,必须在用户带 VS 环境的命令行里测。
|
|
- 给用户的两个隔离测试(在 VS 命令行执行):
|
|
- 测试1:`.\tpc.exe examples/hello.php --force`(关掉两套缓存检查)。若通过→缓存路径是触发点。
|
|
- 测试2(若测试1通过):`rmdir /s /q build` 后 `.\tpc.exe examples/hello.php` 跑两次。若第1次(缓存miss)过、第2次(缓存hit,完整算key并 hash_update_file 污染头)崩→bug 在缓存命中路径的 key 计算;若第1次就崩→在 compile/link 本身(更可能是 v1095 误编译)。
|
|
- **用户实测测试2:清空 build 后首次(缓存 miss)就崩** → 直接排除"缓存命中路径"(缓存命中路径只在第2次才触发 `hash_update_file` 扫描污染头;首次 `$objectFile` 不存在,`hasGenerated/hasMiscObjectFileCache` 在 1215/1123 行 early-return false,根本走不到 key 计算)。
|
|
- 进一步逐行审查首次 compile 路径全部可能大分配点:
|
|
- `writeFile` 改返回 bool:7 处调用(gen_stub/ResourceCompilationTrait/Translator 498/663/736/1100/1785)均安全,无副作用。
|
|
- `parseCachedAst`/`cloneAst`:仅 prepare/convert 用,`--dry` 已验证正常。
|
|
- `compileFile` 缓存 `if` 块(1354-1360):首次恒 false,benign。
|
|
- `NativeBuilder::compile()`(31-41):Windows 顺序模式走 `passthru($command,$status)`,**不把 cl 输出累积进数组**,无"读输出撑爆"路径。
|
|
- `compileSourceFile` 循环(1489-1532):每文件 `compileFile(false)` → miss → `compile()` → passthru 跑 cl → 打印命令。前3个文件成功(cl 命令已打印=status 0),崩在第4个文件进入时。
|
|
- **最终结论(高置信)**:S1+S2 在首次 compile 路径上没有任何大分配来源,128TB 在 PHP 源码层无出处;而 v1095 基础版编 hello.php 正常、我们的版本崩 → **判定为 v1095 编译器对我们当前源码(含 S1+S2)的误编译(miscompilation)**:v1095 在 AOT 编译改动后的 `Translator.php` 时,把 `compileFile`/编译循环邻近某处 size 符号扩展/算坏(典型 ~2⁴⁷)。`--dry` 不进 compile 循环故不触发。
|
|
- **决定性验证 = Bootstrap 自举**:用当前 tpc.exe(它编工程成功,编 hello.php 崩)重编 tpc 工程得自举版 tpc.exe-B,再用它编 hello.php。
|
|
- 通过 → 确认 v1095 误编译,自举换了编译上下文/偏移绕开,后续用自举版做 typephp-think 速度对比即可。
|
|
- 仍崩 → 源码确有隐藏 bug,需在 `compileFile` 入口加 STDERR 文件名日志 + `NativeBuilder::compile` 前后标记,再编一次精确定位第4个文件及崩因。
|
|
- 待修(不影响崩溃):`getGeneratedHeaderDependencies()` 通配 `*_arginfo.h` 会纳入 build/include 残留 tpc 工程头(`php_src_*`),应限定为当前 `$this->targetName` 前缀(`str_starts_with($name,'php_'.$targetName.'_') && str_ends_with($name,'_arginfo.h')`)。等自举成功后再单独修并验证,避免重编混入变量。
|
|
|
|
## Bootstrap 自举也崩 → 推翻"纯误编译"早期判断
|
|
|
|
- 用户实测:`.\tpc.exe project.yml -O2 -j 8`(用我们的 tpc.exe 重编工程,即自举)**同样崩在 typephp_fiber_generator.cc**,与基础版崩溃签名完全一致(2⁴⁷)。
|
|
- 关键修正:自举崩**不能区分**"v1095 误编译"与"源码真 bug"——因为自举二进制由我们的 tpc.exe(本身可能已被 v1095 误编译)再编一次,缺陷会继承传递。所以自举崩对两种假设都"符合",不是决定性证据。
|
|
- 进一步精读定位:
|
|
- `getLanguageFromExtension('.cc')` 返回 **null** → misc 文件走 `getCompileCommandOptions()` → 合法 `cl /c` 命令(解释了日志里 misc 文件没抛 RuntimeException,而是正常打印 cl 命令)。
|
|
- `getMiscObjectCacheKey()` 只做 `hash('sha256', buildCompileFileCommand(...) . "\0" . serialize($abi))`;而 `buildCompileFileCommand`(generated 文件也用)在 generated 路径已验证正常 → **misc 专属 metadata 写入路径不是 2⁴⁷ 来源**。
|
|
- 仓库真实状态:HEAD=ac7813d(S1+S2);工作树仅我的 RC 修复 + project.yml 注释,无其它碰编译链路的改动。
|
|
- S1 是前端 AST 缓存(misc 文件不是 PHP,根本不进前端)→ 已排除。S2 是唯一落在 compile 阶段的改动。
|
|
|
|
## 二分开关:已禁用 S2 缓存逻辑(compileFile)
|
|
|
|
- 在 `src/Translator.php` 的 `compileFile()` 里,把 S2 增量对象缓存逻辑**整段去掉**(缓存命中跳过 + `invalidateMisc/GeneratedObjectCache` + `writeMisc/GeneratedObjectCacheMetadata`),退回 S2 之前的纯编译行为。加了 `// [bisect]` 注释标记,定位清楚后删除。
|
|
- 目的:若用 v1095 重新编译(带此开关的源码)后 `.\tpc.exe examples/hello.php` **不再崩** → 坐实"S2 那段代码被 v1095 AOT 误编译",下一步改写 S2 避开该结构(如去掉 SPL `RecursiveIteratorIterator`、改用更简单 size 计算)。
|
|
- 若仍崩 → 不是 S2,需扩大范围(revert 全部 S1+S2、或排查 S1+S2 之外的更早提交/phpx 依赖更新 2129145)。
|
|
- 用户重编命令(必须用 v1095,因为我们的 tpc.exe 也会崩,不能用来重编):
|
|
- 从 `D:\git\php\aot-compiler` 目录:`D:\git\php\tpc_v1095_windows_x86_64\tpc.exe project.yml -O2 -j 8`(RC 修复已在源码、resource 已注释,且无输出冲突 → 可编译+链接成功)。
|
|
- 再测:`.\tpc.exe examples/hello.php`。
|
|
|
|
## 坐实:S2 触发 v1095 误编译 + 已保守化重写
|
|
|
|
- 用户实测:用 v1095 重编"二分开关版"(S2 在 compileFile 的调用整段去掉)源码,产出的 `tpc2.exe` 编 `examples/hello.php` **不再崩,成功产出 hello.exe**(7 文件编译+链接全过)。→ **坐实 S2 那段增量对象缓存代码被 v1095 AOT 误编译**(去掉调用即不再触发 2⁴⁷)。S1 已确认安全(--dry 正常)。
|
|
- 首次编译实际会执行的 S2 高危路径:`writeGeneratedObjectCacheMetadata` → `getGeneratedObjectCacheKey`。该函数原版用了 `hash_init`/`hash_update`/`hash_update_file`/`hash_final` 哈希**资源流**,并调用 `getGeneratedHeaderDependencies()`(含 `FilesystemIterator` SPL 迭代器)。这两类结构在 v1095 AOT 编译下最易被误编译成坏 size(2⁴⁷ 符号扩展)。`hasMiscObjectFileCache` 原版也含 `RecursiveIteratorIterator`/`RecursiveDirectoryIterator`。
|
|
- **已重写 S2 为保守版**(src/Translator.php),消除全部高危结构:
|
|
1. `getGeneratedObjectCacheKey`:去掉 `hash_init/update/update_file/final` 流,改用 `hash('sha256', implode("\0", $parts))` 单次 + 每个依赖头 `hash_file('sha256', $header)` 单次。
|
|
2. `getGeneratedHeaderDependencies`:去掉 `FilesystemIterator`,改用 `scandir` 一层遍历;并把 `*_arginfo.h` 通配**限定为当前 target 前缀**(`str_starts_with($name,'php_'.$targetName.'_')`),顺手修了"扫描整个 build/include 含 tpc 工程残留头"的设计缺陷。
|
|
3. `hasMiscObjectFileCache`:去掉 `RecursiveIteratorIterator`,新增 `collectHeaderFiles()`(scandir 递归,无 SPL 迭代器)替代。
|
|
4. `getMiscObjectCacheKey` 保持原 `hash('sha256', ...)` 单次调用(已验证安全)。
|
|
5. 恢复 `compileFile` 里的 S2 调用(缓存命中跳过 + invalidate + writeMetadata),删除二分 `[bisect]` 标记。
|
|
- 验证:`php -l src/Translator.php` 无语法错误;Grep 确认 Translator.php 内已无 RecursiveIteratorIterator/RecursiveDirectoryIterator/FilesystemIterator/hash_init/hash_update/hash_update_file/hash_final 残留。
|
|
- **未动的同类结构**:`FileScanner.php`、`Build/PrecompiledHeaderManager.php`、`gen_stub.php` 仍有 SPL 迭代器 + hash 资源流,但**不是本次崩溃元凶**(v1095 编整个工程时这些文件都被正常 AOT 编译且没崩),保持最小改动原则暂不碰。
|
|
- **下一步验证(用户执行,必须用 v1095 重编,因为我们的 tpc.exe 自己也崩)**:
|
|
```
|
|
cd /d D:\git\php\aot-compiler
|
|
D:\git\php\tpc_v1095_windows_x86_64\tpc.exe project.yml -O2 -j 8 # 产新 tpc.exe:含保守版S2 + 源码内修复版ResourceFileGenerator(自身无图标,因resource注释)
|
|
rmdir /s /q build
|
|
.\tpc.exe examples\hello.php # 第一次:应不崩出 hello.exe(验证S2修复)
|
|
.\tpc.exe examples\hello.php # 第二次:应命中缓存([cache] skip),更快(验证S2加速)
|
|
```
|
|
- 若第一次仍崩 → 误编译源不在已改这几处,需更细二分(如逐个恢复 hasMisc/hasGenerated 与 write*Metadata)。
|
|
- 若两次都过 → S2 修复生效。之后可恢复 project.yml 的 resource 段,用新 tpc.exe 编 project.yml 验证 RC2135 也修好且带图标。
|
|
|
|
## 关键反转:保守化重写的 S2 仍崩 128TB → 判定为 v1095 跨函数误编译
|
|
|
|
- 用户实测:用 v1095 重编"保守版 S2"(去 SPL 迭代器 + 去 hash 资源流,compileFile 已恢复 S2 调用)源码,产出的 `tpc2.exe` 编 `examples/hello.php` **仍然崩 128TB**,崩溃签名与原始版完全一致(`tried to allocate 140736835400432 ≈2⁴⁷`)。
|
|
- 这**推翻了"是 SPL 迭代器 / hash 资源流被误编译"的假设**。进一步定位:崩溃发生在**首次编译** `hello.cc`(generated 文件)打印 cl 命令后、进入 `getNativeBuilder()->compile()` 时。首次编译对 `hello.cc` 实际执行的 S2 代码只有 `isPhpxMiscFile()`、`isGeneratedSourceFile()`、`hasGeneratedObjectFileCache()`(obj 不存在→early-return false)、`invalidateGeneratedObjectCache()`;而我新写的保守 `hash()`/`hash_file()` 此时**还没轮到**(它在编译后才写的 metadata 里)。→ 崩点根本到不了哈希,说明它不是某个 helper 的内部写法问题。
|
|
- **最终判定:v1095 在 AOT 编译 `compileFile` 里那段 S2 包裹代码时,连带把后续 `getNativeBuilder()->compile()` 编坏了**(典型跨函数误编译:A 函数误编→B 函数运行时崩)。窄重写 helper 救不了,因为触发点是"这段 S2 代码被编译/被调用"这件事本身,而不是具体哪行语句。
|
|
- **这是 v1095 编译器(AOT)的 bug,不是我们源码的逻辑 bug**。在源码层面做局部改写无法可靠规避。
|
|
- **决策:禁用 S2**(从 `compileFile` 移除全部 S2 调用,退回纯编译态,等同于已证明稳定的 bisect 版),保留 S1(合并两次解析,主要提速点,已确认安全)。S2 的 helper 方法(hasMisc/hasGenerated/getXxxCacheKey/writeXxxMetadata/collectHeaderFiles)作为"死代码"**保留**(已证明:定义但不被 compileFile 调用就安全),待编译器修复后复用。
|
|
- 当前 `compileFile`(src/Translator.php ~1378)已加注释明确 S2 禁用原因;`php -l` 通过。
|
|
- **后续可选项(暂不阻塞)**:
|
|
1. 用 v1095 重编当前源码(S1 开、S2 关)→ 应稳定,可正常编 hello.php 与 project.yml。
|
|
2. 想保留 S2 加速的可试验:用**更低优化级别**(如 `project.yml -O1` 而非 `-O2`)重编 tpc.exe。若 v1095 的误编译是优化级别相关,低级别可能绕开 → 届时再恢复 compileFile 的 S2 调用测试。但 -O1 会让 tpc.exe 自身变慢,需权衡。
|
|
3. 向 v1095 上游报告该 AOT 误编译(含最小复现:带 S2 包裹的 compileFile → 2⁴⁷ VirtualAlloc)。
|
|
- 当前 HEAD 仍为 ac7813d 之上的未提交改动(RC2135 修复 + S2 禁用 + resource 段已注释)。待用户验证稳定后,建议提交 S1 部分(已稳定)并将 S2 作为"被编译器 bug 阻塞"的待办保留。
|
|
|
|
## typephp-think 编译报 Duplicate class(重复类 fatal)
|
|
|
|
- 现象:`.\tpc.exe`(S1 开/S2 关的稳定版)编译 `D:\git\php\typephp-think` 时,`Preprocessor->prepareFile` 报 `Duplicate class think\exception\ClassNotFoundException`(首次撞在 `think-container/src/exception/ClassNotFoundException.php`)。
|
|
- **不是 S1/S2 引入**:`Preprocessor.php:697` 的重复检测 `isset($this->symbolDeclInFile[...])` 是 prepare 阶段的**全局符号表**(第733行赋值、无 resetClass 重置、ac7813d 未动过它)。我们的编译器对跨文件重名类是全局 fatal,比 v1095(别的电脑能编过的预编译版)更严格 → 这正是"别的电脑能编过"的原因(v1095 容忍重复类)。
|
|
- **根因 = 工程的重名类 + 补丁机制不会解决它们**:`typephp-think` 有 `patches/` 目录镜像 `vendor/`,由 `patch.php`(`copyPatchesSafely()`)在 `composer post-autoload-dump` 时把 `patches/*` **覆盖拷贝**到 `vendor/*` 并写 `.patches_applied`。但 `patches/topthink/` 只含 `framework/think-dumper/think-filesystem/think-orm/think-trace`,**不含 think-container**;覆盖拷贝不会删文件。所以重名类依然存在。用户"今天只更新代码没打补丁"使 vendor 回到未打补丁态(需重跑 `php patch.php`)。
|
|
- **vendor/ 内真实重名类只有 4 个**(编译器实际会撞的;patches/vendor 成对的不算,因编译器不扫 patches/):
|
|
1. `think\exception\ClassNotFoundException`:framework vs **think-container**(两份字节一致,删 think-container 那份安全)。
|
|
2. `think\Exception`:framework vs **think-orm/stubs**/Exception.php(stub,删 stub 安全)。
|
|
3. `think\Facade`:think-container vs **think-orm/stubs**/Facade.php(stub,删 stub 安全)。
|
|
4. `think\route\Dispatch`:framework 自身两份(`route/Dispatch.php` 规范基类 vs `route/dispatch/Dispatch.php` 命名空间写错成 `think\route`、全工程无引用 `think\route\dispatch\Dispatch` → 死代码,删后者安全)。
|
|
- **决策:放宽编译器重复检测,而非删 vendor**(为公平对比:v1095 容忍重复类,我们的 tpc.exe 也应如此,避免改输入导致 our-tpc vs v1095 不对等)。改 `Preprocessor.php`:
|
|
- `prepareClass`(~697):重复时 `$this->warning(...)` + `resetClass()` + `return ''`(不再 fatal,保留首个声明)。
|
|
- `parseInterface`(~1507):重复时 `$this->warning(...)` + `return;`(不再 fatal)。
|
|
- warning 方法签名 `warning(Node $node, string $msg): void`(CompilerDiagnosticTrait)。`php -l` 通过。
|
|
- 验证:重编 tpc.exe(v1095)→ 编 typephp-think,应越过该 Duplicate class 继续。若再撞别的重名,说明还有未覆盖的 vendor 内部重复(再扫描确认)。
|
|
- 备选(不推荐):直接删上述 4 个冗余 vendor 文件。缺点:composer 会还原 + 让我们与 v1095 的输入不一致,破坏速度对比公平性。
|
|
|
|
## typephp-think 编译报 Cannot re-assign $str(string→array 类型冲突 fatal)
|
|
|
|
- 现象:`.\tpc.exe` 编 `typephp-think` 走到 `symfony/var-dumper/Dumper/CliDumper.php:207` 时,`Preprocessor/Translator` 在 convert 阶段 fatal:`Cannot re-assign $str from php::Array to php::Str`。该行是 `dumpString(Cursor $cursor, string $str, bool $bin, int $cut)` 内 `$str = $bin && str_contains($str,"\0") ? [$str] : explode("\n",$str);` —— 形参 `string $str` 先当字符串用,后在 207 行被赋值为数组。
|
|
- **根因 = 我们的编译器对局部变量/形参做了"单静态类型"假设**:`AssignOpTrait::parseAssignFinally`(~337)对已存在变量调用 `checkVarAssignExpr($left, 现有类型, 新值类型)`;`CompilerBase::checkVarAssignExpr` 对"不兼容"的两次赋值直接 fatal。但 PHP 是动态类型,变量中途换类型是合法的——v1095(别的电脑能编过的预编译版)对此类变量用运行时 variant(`php::Var`)承载,所以不崩。我们之前对 vendor 的容忍(Duplicate class→warning)没覆盖到这种"标量→数组"类型变更。
|
|
- **关键正确性约束**:不能简单把 fatal 降级为 warning——否则 C++ 里 `php::Str str;` 后面接 `str = <数组>;` 会是类型错误甚至**静默误编译**。正确做法是:被重新赋以不兼容类型的变量,从一开始(声明处)就声明为运行时 variant `php::Var`。
|
|
- **修复(src 三处)**:
|
|
1. `CompilerBase.php`:新增 `protected array $variantVars = [];`(resetFunction 里清空);新增 `areTypesAssignable($existing,$new): bool`,与 `checkVarAssignExpr` 的兼容规则逐字一致(VAR/REF/同类型/双 native/big 类型 都兼容,其余冲突),仅返回 bool 不 fatal,供预扫描判定。
|
|
2. `Translator.php`:新增 `computeVariantVars()` + `collectAssignTypes()/collectAssignTypesNode()`(递归走函数体,跳过嵌套 function/closure/arrow,收集每个简单变量赋值 RHS 的 detectTypeOfExpr 类型,并以形参 argInfo->type 作种子);`parseFunction()` 在 SSA 之后调用它,若某变量类型集合存在不兼容对 → 记入 `variantVars`,并把这些形参的 `argInfo->type` 改为 `php::Var`、重拼 `functionDef->params`(用 `genArgumentDeclaration`)。
|
|
3. `Parser/AssignOpTrait.php`:`parseAssignFinally` 首次声明局部变量时,若 `$var` 在 `variantVars` 中 → `finalVarType = Type::VAR`(声明即 variant),后续赋值走 variant 路径、`checkVarAssignExpr` 因 VAR 提前返回,不再 fatal。
|
|
- **兼容性**:决策逻辑与旧 `checkVarAssignExpr` 等价——原来能通过的不兼容赋值(int↔float、同类型重复、含 VAR/big)仍不触发;原来会 fatal 的(str↔arr、str↔int 等)现在正确升级为 variant,而非静默误编译。已用独立脚本验证决策矩阵。
|
|
- **限制**:预扫描只覆盖 `Function_`/`ClassMethod`(走 parseFunction);`Closure`/`ArrowFunction` 走 `parseStmts` 不经过 computeVariantVars,其形参若中途换类型仍可能 fatal(vendor 里少见,遇到再补)。`php -l` 三个文件均通过。
|
|
- **验证状态**:本机无法端到端重编验证——缺 PHP/Zend 头文件(php.h 不存在),且当前 `PHP_HOME=D:\git\php\tpc_v1092_windows_x86_64`(陈旧残留,非 PHP 安装)导致 `detectPhpLibs` 找不到 php8ts.lib。用户的真实构建环境(含 PHP dev 头 + 正确的 PHP_HOME)在另一台机器,已多次成功重编。须由用户执行重编+重跑验证(见下条命令)。
|
|
- **用户重编+重跑序列(必须用 v1095,因我们的 tpc.exe 自身也需它编译;S2 已禁用故稳定)**:
|
|
```
|
|
cd /d D:\git\php\typephp-think && php patch.php
|
|
cd /d D:\git\php\aot-compiler
|
|
set PHP_HOME=<用户正确的 PHP TS dev 安装目录> # 例 D:\Program Files\PhpWebStudy-Data\app\php-8.3.24-ts(且其 dev\php8ts.lib 需在 $phpDir/lib 或 $phpDir 下,必要时拷贝)
|
|
D:\git\php\tpc_v1095_windows_x86_64\tpc.exe project.yml -O2 -j 8
|
|
rmdir /s /q build
|
|
.\tpc.exe examples\hello.php # 确认自举稳定
|
|
cd /d D:\git\php\typephp-think
|
|
D:\git\php\aot-compiler\tpc.exe project.yml -O2 -j 8 # 应越过 Duplicate class 与 Cannot re-assign,继续编译
|
|
```
|
|
- **注意**:本机实测 `php-8.3.24-ts` 根目录有 `php8embed.lib`、但 `php8ts.lib` 只在 `dev/` 子目录;`detectPhpLibs` 只查 `$phpDir/$phpDir\SDK\lib/$phpDir\lib`,故需把 `dev\php8ts.lib` 拷到 `php-8.3.24-ts\lib\`(或根)才能让 v1095 的 tpc.exe 找到核心库——这是用户环境的常规布局操作。
|
|
|