Compare commits

...

8 Commits

Author SHA1 Message Date
yangweijie eb523afbf0 编译加速 3 weeks ago
yangweijie e0581f8ccc 忽略同名类 3 weeks ago
yangweijie fbaac125ae 优化 3 weeks ago
yangweijie e79ec2cf73 编译报错修复 3 weeks ago
yangweijie 7fa91622e2 rc文件生成 3 weeks ago
yangweijie 473bd284ab 添加文档 3 weeks ago
yangweijie ac7813d7b2 perf: cache AST across prepare/convert and add generated object cache 3 weeks ago
yangweijie 449afc1516 add onepiece-ddz game example 3 weeks ago
  1. 179
      .agents/wiki/data/catalog.json
  2. 43
      .agents/wiki/data/catalog.xml
  3. 19
      .agents/wiki/index.html
  4. 26
      .agents/wiki/pages/_welcome.html
  5. 39
      .agents/wiki/pages/backend.html
  6. 45
      .agents/wiki/pages/backend.md
  7. 47
      .agents/wiki/pages/build-pipeline.html
  8. 56
      .agents/wiki/pages/build-pipeline.md
  9. 38
      .agents/wiki/pages/build-speed.html
  10. 46
      .agents/wiki/pages/build-speed.md
  11. 46
      .agents/wiki/pages/compilation-modes.html
  12. 54
      .agents/wiki/pages/compilation-modes.md
  13. 37
      .agents/wiki/pages/compile-time-attributes.html
  14. 38
      .agents/wiki/pages/compile-time-attributes.md
  15. 44
      .agents/wiki/pages/gen-extension.html
  16. 47
      .agents/wiki/pages/gen-extension.md
  17. 41
      .agents/wiki/pages/gen-functions-classes.html
  18. 48
      .agents/wiki/pages/gen-functions-classes.md
  19. 37
      .agents/wiki/pages/generator.html
  20. 51
      .agents/wiki/pages/generator.md
  21. 47
      .agents/wiki/pages/high-precision-types.html
  22. 58
      .agents/wiki/pages/high-precision-types.md
  23. 38
      .agents/wiki/pages/incompatible-features.html
  24. 45
      .agents/wiki/pages/incompatible-features.md
  25. 42
      .agents/wiki/pages/incremental-cache.html
  26. 46
      .agents/wiki/pages/incremental-cache.md
  27. 43
      .agents/wiki/pages/native-types.html
  28. 55
      .agents/wiki/pages/native-types.md
  29. 36
      .agents/wiki/pages/optimizer.html
  30. 37
      .agents/wiki/pages/optimizer.md
  31. 35
      .agents/wiki/pages/overview.html
  32. 32
      .agents/wiki/pages/overview.md
  33. 45
      .agents/wiki/pages/parser.html
  34. 66
      .agents/wiki/pages/parser.md
  35. 48
      .agents/wiki/pages/pipeline.html
  36. 55
      .agents/wiki/pages/pipeline.md
  37. 41
      .agents/wiki/pages/platform.html
  38. 45
      .agents/wiki/pages/platform.md
  39. 45
      .agents/wiki/pages/preprocessor.html
  40. 56
      .agents/wiki/pages/preprocessor.md
  41. 53
      .agents/wiki/pages/quickstart.html
  42. 68
      .agents/wiki/pages/quickstart.md
  43. 44
      .agents/wiki/pages/runtime.html
  44. 50
      .agents/wiki/pages/runtime.md
  45. 37
      .agents/wiki/pages/transform.html
  46. 45
      .agents/wiki/pages/transform.md
  47. 38
      .agents/wiki/pages/type-check.html
  48. 41
      .agents/wiki/pages/type-check.md
  49. 2024
      .agents/wiki/script/mermaid.min.js
  50. 1
      .gitignore
  51. 76
      .workbuddy/memory/2026-08-03.md
  52. 154
      .workbuddy/memory/2026-08-04.md
  53. 224
      docs/COMPILATION_PERFORMANCE_ANALYSIS.md
  54. 124
      examples/onepiece-doudizhu-win32/README.md
  55. 52
      examples/onepiece-doudizhu-win32/build.bat
  56. 402
      examples/onepiece-doudizhu-win32/cpp-src/win32.cc
  57. 87
      examples/onepiece-doudizhu-win32/main.php
  58. 252
      examples/onepiece-doudizhu-win32/php-src/doudizhu/Ai.php
  59. 68
      examples/onepiece-doudizhu-win32/php-src/doudizhu/Card.php
  60. 90
      examples/onepiece-doudizhu-win32/php-src/doudizhu/Character.php
  61. 252
      examples/onepiece-doudizhu-win32/php-src/doudizhu/Combo.php
  62. 61
      examples/onepiece-doudizhu-win32/php-src/doudizhu/Deck.php
  63. 48
      examples/onepiece-doudizhu-win32/php-src/doudizhu/Faction.php
  64. 501
      examples/onepiece-doudizhu-win32/php-src/doudizhu/Game.php
  65. 1570
      examples/onepiece-doudizhu-win32/php-src/doudizhu/GameController.php
  66. 151
      examples/onepiece-doudizhu-win32/php-src/doudizhu/MoveGenerator.php
  67. 49
      examples/onepiece-doudizhu-win32/php-src/doudizhu/PlayerState.php
  68. 261
      examples/onepiece-doudizhu-win32/php-src/doudizhu/Render.php
  69. 103
      examples/onepiece-doudizhu-win32/php-src/doudizhu/Skill.php
  70. 107
      examples/onepiece-doudizhu-win32/php-src/doudizhu/Sound.php
  71. 41
      examples/onepiece-doudizhu-win32/php-src/win32.stub.php
  72. 7
      examples/onepiece-doudizhu-win32/project.yml
  73. 32
      project.yml
  74. 5
      src/Build/ResourceCompilationTrait.php
  75. 103
      src/CompilerBase.php
  76. 10
      src/Generator/ResourceFileGenerator.php
  77. 6
      src/Parser/AssignOpTrait.php
  78. 21
      src/Preprocessor.php
  79. 309
      src/Translator.php

@ -0,0 +1,179 @@
{
"language": "zh",
"sections": [
{
"name": "项目概览",
"topics": [
{
"title": "项目简介",
"level": 1,
"slug": "overview",
"group": null
},
{
"title": "编译流水线",
"level": 1,
"slug": "pipeline",
"group": null
},
{
"title": "编译模式",
"level": 1,
"slug": "compilation-modes",
"group": null
},
{
"title": "快速开始",
"level": 1,
"slug": "quickstart",
"group": null
}
],
"groups": []
},
{
"name": "前端:解析与符号",
"topics": [
{
"title": "解析器与 AST",
"level": 1,
"slug": "parser",
"group": null
},
{
"title": "预处理与符号表",
"level": 1,
"slug": "preprocessor",
"group": null
},
{
"title": "AST 转换(Lowering)",
"level": 1,
"slug": "transform",
"group": null
}
],
"groups": []
},
{
"name": "类型系统",
"topics": [
{
"title": "原生类型映射",
"level": 1,
"slug": "native-types",
"group": null
},
{
"title": "类型检查与兼容",
"level": 1,
"slug": "type-check",
"group": null
},
{
"title": "高精度数值类型",
"level": 1,
"slug": "high-precision-types",
"group": null
}
],
"groups": []
},
{
"name": "代码生成",
"topics": [
{
"title": "生成器总览",
"level": 1,
"slug": "generator",
"group": null
},
{
"title": "函数与类生成",
"level": 1,
"slug": "gen-functions-classes",
"group": null
},
{
"title": "扩展模块生成",
"level": 1,
"slug": "gen-extension",
"group": null
}
],
"groups": []
},
{
"name": "后端与构建",
"topics": [
{
"title": "编译器后端",
"level": 1,
"slug": "backend",
"group": null
},
{
"title": "构建流水线",
"level": 1,
"slug": "build-pipeline",
"group": null
},
{
"title": "增量编译缓存",
"level": 1,
"slug": "incremental-cache",
"group": null
},
{
"title": "跨平台支持",
"level": 1,
"slug": "platform",
"group": null
}
],
"groups": []
},
{
"name": "优化与性能",
"topics": [
{
"title": "优化器",
"level": 1,
"slug": "optimizer",
"group": null
},
{
"title": "编译期属性",
"level": 1,
"slug": "compile-time-attributes",
"group": null
},
{
"title": "构建速度与性能",
"level": 1,
"slug": "build-speed",
"group": null
}
],
"groups": []
},
{
"name": "运行时与边界",
"topics": [
{
"title": "PHPX 运行时集成",
"level": 1,
"slug": "runtime",
"group": null
},
{
"title": "不支持的 PHP 特性",
"level": 1,
"slug": "incompatible-features",
"group": null
}
],
"groups": []
}
]
}

@ -0,0 +1,43 @@
<section>
项目概览
<topic level="1" slug="overview">项目简介</topic>
<topic level="1" slug="pipeline">编译流水线</topic>
<topic level="1" slug="compilation-modes">编译模式</topic>
<topic level="1" slug="quickstart">快速开始</topic>
</section>
<section>
前端:解析与符号
<topic level="1" slug="parser">解析器与 AST</topic>
<topic level="1" slug="preprocessor">预处理与符号表</topic>
<topic level="1" slug="transform">AST 转换(Lowering)</topic>
</section>
<section>
类型系统
<topic level="1" slug="native-types">原生类型映射</topic>
<topic level="1" slug="type-check">类型检查与兼容</topic>
<topic level="1" slug="high-precision-types">高精度数值类型</topic>
</section>
<section>
代码生成
<topic level="1" slug="generator">生成器总览</topic>
<topic level="1" slug="gen-functions-classes">函数与类生成</topic>
<topic level="1" slug="gen-extension">扩展模块生成</topic>
</section>
<section>
后端与构建
<topic level="1" slug="backend">编译器后端</topic>
<topic level="1" slug="build-pipeline">构建流水线</topic>
<topic level="1" slug="incremental-cache">增量编译缓存</topic>
<topic level="1" slug="platform">跨平台支持</topic>
</section>
<section>
优化与性能
<topic level="1" slug="optimizer">优化器</topic>
<topic level="1" slug="compile-time-attributes">编译期属性</topic>
<topic level="1" slug="build-speed">构建速度与性能</topic>
</section>
<section>
运行时与边界
<topic level="1" slug="runtime">PHPX 运行时集成</topic>
<topic level="1" slug="incompatible-features">不支持的 PHP 特性</topic>
</section>

@ -0,0 +1,19 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<title>Project Wiki</title><style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg)}
#app{display:flex;height:100vh}
#side{width:300px;flex:0 0 300px;background:var(--card);border-right:1px solid var(--border);overflow:auto;padding:16px}
#side h2{font-size:15px;color:#fff;margin:18px 0 8px;padding-bottom:6px;border-bottom:1px solid var(--border)}
#side a{display:block;padding:5px 8px;border-radius:6px;color:var(--fg);text-decoration:none;font-size:13px}
#side a:hover{background:#222633}
#side a.lvl2{margin-left:12px;font-size:12px;color:var(--muted)}
#main{flex:1;background:var(--bg)}
#main iframe{width:100%;height:100%;border:none}
.meta{color:var(--muted);font-size:11px;margin-top:20px;border-top:1px solid var(--border);padding-top:10px}
</style></head>
<body><div id="app">
<nav id="side"><h2>项目概览</h2><a class="lvl1" href="pages/overview.html" target="view">项目简介</a><a class="lvl1" href="pages/pipeline.html" target="view">编译流水线</a><a class="lvl1" href="pages/compilation-modes.html" target="view">编译模式</a><a class="lvl1" href="pages/quickstart.html" target="view">快速开始</a><h2>前端:解析与符号</h2><a class="lvl1" href="pages/parser.html" target="view">解析器与 AST</a><a class="lvl1" href="pages/preprocessor.html" target="view">预处理与符号表</a><a class="lvl1" href="pages/transform.html" target="view">AST 转换(Lowering)</a><h2>类型系统</h2><a class="lvl1" href="pages/native-types.html" target="view">原生类型映射</a><a class="lvl1" href="pages/type-check.html" target="view">类型检查与兼容</a><a class="lvl1" href="pages/high-precision-types.html" target="view">高精度数值类型</a><h2>代码生成</h2><a class="lvl1" href="pages/generator.html" target="view">生成器总览</a><a class="lvl1" href="pages/gen-functions-classes.html" target="view">函数与类生成</a><a class="lvl1" href="pages/gen-extension.html" target="view">扩展模块生成</a><h2>后端与构建</h2><a class="lvl1" href="pages/backend.html" target="view">编译器后端</a><a class="lvl1" href="pages/build-pipeline.html" target="view">构建流水线</a><a class="lvl1" href="pages/incremental-cache.html" target="view">增量编译缓存</a><a class="lvl1" href="pages/platform.html" target="view">跨平台支持</a><h2>优化与性能</h2><a class="lvl1" href="pages/optimizer.html" target="view">优化器</a><a class="lvl1" href="pages/compile-time-attributes.html" target="view">编译期属性</a><a class="lvl1" href="pages/build-speed.html" target="view">构建速度与性能</a><h2>运行时与边界</h2><a class="lvl1" href="pages/runtime.html" target="view">PHPX 运行时集成</a><a class="lvl1" href="pages/incompatible-features.html" target="view">不支持的 PHP 特性</a><div class="meta">rendered by code-wiki · static HTML</div></nav>
<main id="main"><iframe name="view" src="pages/overview.html"></iframe></main>
</div></body></html>

@ -0,0 +1,26 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Project Wiki</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>Project Wiki</h1>
<p>This wiki was generated by <strong>code-wiki</strong> from the repository at <code>D:\git\php\aot-compiler</code>.</p>
<p>Select a topic from the left navigation to read its documentation.</p></div></body></html>

@ -0,0 +1,39 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>编译器后端</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>编译器后端</h1>
<p>TypePHP 不直接调 <code>g++</code>/<code>cl</code>,而是抽象出 <strong>平台(Platform)+ 编译器后端(Backend)</strong> 两层,由工厂在启动时自动探测。这样同一份生成代码能在 GCC / Clang / MSVC 下编译。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L164-L190">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Backend">src/Backend/</a></p>
<h2>抽象结构</h2>
<table><thead><tr><th></th><th>角色</th></tr></thead><tbody><tr><td><code>PlatformFactory</code></td><td>按 OS 创建 <code>Windows</code> / <code>Linux</code> / <code>macOS</code> 平台对象</td></tr><tr><td><code>CompilerFactory</code></td><td><code>detectCompilerName()</code> / <code>createByName()</code> / <code>autoDetect()</code> 选择后端</td></tr><tr><td><code>CompilerBackend</code>(接口)</td><td>定义编译/链接命令抽象</td></tr><tr><td><code>GccLikeBackend</code></td><td>GCC / Clang 共用逻辑</td></tr><tr><td><code>Gcc</code> / <code>Clang</code></td><td>具体后端</td></tr><tr><td><code>Msvc</code></td><td>MSVC(Windows)后端</td></tr></tbody></table>
<p><code>detectPlatform()</code> 先建平台,再探测编译器名,最后 <code>CompilerFactory::createByName()</code> 生成后端;若失败则告警并回退到旧逻辑(<code>initializeNewArchitecture()</code>)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L164-L190">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L538-L560">src/Translator.php</a></p>
<h2>命令生成</h2>
<p>后端只负责「给定源/目标/选项 → 返回命令字符串」。真实的进程执行在 <a href="pages/build-pipeline.html">构建流水线</a><code>NativeBuilder</code> 中。例如:</p>
<ul><li><code>compileCommand()</code> / <code>linkCommand()</code> 产出可复现的命令行;</li><li>命令行本身被写进<strong>缓存键</strong>,确保编译参数变化能触发重编译。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1408-L1417">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1644-L1647">src/Translator.php</a></p>
<h2>预编译头</h2>
<p>后端是否支持 PCH 由 <code>supportsPrecompiledHeaders()</code> 决定;支持的会在构建期预编译 <code>phpx.h</code> 等全局头,显著加速后续编译单元。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1449-L1487">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Build\PrecompiledHeaderManager.php">src/Build/PrecompiledHeaderManager.php</a></p>
<h2>相关阅读</h2>
<ul><li>命令如何被执行与排队 → <a href="pages/build-pipeline.html">构建流水线</a></li><li>缓存如何依赖编译命令 → <a href="pages/incremental-cache.html">增量编译缓存</a></li><li>平台差异(Windows SDK / PHP lib) → <a href="pages/platform.html">跨平台支持</a></li></ul></div></body></html>

@ -0,0 +1,45 @@
<blog>
# 编译器后端
TypePHP 不直接调 `g++`/`cl`,而是抽象出 **平台(Platform)+ 编译器后端(Backend)** 两层,由工厂在启动时自动探测。这样同一份生成代码能在 GCC / Clang / MSVC 下编译。
Sources: [src/Translator.php](src/Translator.php#L164-L190) · [src/Backend/](src/Backend/)
## 抽象结构
| 类 | 角色 |
|---|---|
| `PlatformFactory` | 按 OS 创建 `Windows` / `Linux` / `macOS` 平台对象 |
| `CompilerFactory` | `detectCompilerName()` / `createByName()` / `autoDetect()` 选择后端 |
| `CompilerBackend`(接口) | 定义编译/链接命令抽象 |
| `GccLikeBackend` | GCC / Clang 共用逻辑 |
| `Gcc` / `Clang` | 具体后端 |
| `Msvc` | MSVC(Windows)后端 |
`detectPlatform()` 先建平台,再探测编译器名,最后 `CompilerFactory::createByName()` 生成后端;若失败则告警并回退到旧逻辑(`initializeNewArchitecture()`)。
Sources: [src/Translator.php](src/Translator.php#L164-L190) · [src/Translator.php](src/Translator.php#L538-L560)
## 命令生成
后端只负责「给定源/目标/选项 → 返回命令字符串」。真实的进程执行在 [构建流水线](build-pipeline) 的 `NativeBuilder` 中。例如:
- `compileCommand()` / `linkCommand()` 产出可复现的命令行;
- 命令行本身被写进**缓存键**,确保编译参数变化能触发重编译。
Sources: [src/Translator.php](src/Translator.php#L1408-L1417) · [src/Translator.php](src/Translator.php#L1644-L1647)
## 预编译头
后端是否支持 PCH 由 `supportsPrecompiledHeaders()` 决定;支持的会在构建期预编译 `phpx.h` 等全局头,显著加速后续编译单元。
Sources: [src/Translator.php](src/Translator.php#L1449-L1487) · [src/Build/PrecompiledHeaderManager.php](src/Build/PrecompiledHeaderManager.php)
## 相关阅读
- 命令如何被执行与排队 → [构建流水线](build-pipeline)
- 缓存如何依赖编译命令 → [增量编译缓存](incremental-cache)
- 平台差异(Windows SDK / PHP lib) → [跨平台支持](platform)
</blog>

@ -0,0 +1,47 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>构建流水线</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>构建流水线</h1>
<p><code>Translator::compile()</code> 把生成的 C++ 与 phpx 运行时源文件编译为对象文件,<code>build()</code> 再链接成最终产物。两者都委托给 <code>src/Build/NativeBuilder</code>,它负责真正派发编译/链接进程。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1419-L1447">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1649-L1680">src/Translator.php</a></p>
<h2>compile() 步骤</h2>
<pre class="mermaid">flowchart TD
A[&quot;compile(sourceFiles)&quot;] --&gt; B[&quot;追加 phpx misc 源\nfiber_generator / helper / main / cli_title&quot;]
B --&gt; C[&quot;preparePhpXPrecompiledHeader()&quot;]
C --&gt; D[&quot;compileResourceFile() (Windows)&quot;]
D --&gt; E{&quot;支持 pcntl 且 -j&gt;1?&quot;}
E -- &quot;&quot; --&gt; F[&quot;compileSourceFile() 串行&quot;]
E -- &quot;&quot; --&gt; G[&quot;compileWithPcntl() 并行&quot;]</pre>
<p>phpx misc 源(<code>typephp_fiber_generator.cc</code><code>typephp_helper.cc</code><code>typephp_main.cc</code><code>php_cli_process_title.c</code><code>ps_title.c</code>)按模式追加:embed 加 <code>typephp_main.cc</code>,bin 加 cli 标题源。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1423-L1434">src/Translator.php</a></p>
<h2>并行编译</h2>
<p><code>compileWithPcntl()</code> 在 Unix/Linux/macOS 用 <code>pcntl_fork</code> 多进程编译,进度条实时刷新;无 <code>pcntl</code> 扩展时自动降级为串行。每个 <code>compileFile()</code> 调用前都会先查 <a href="pages/incremental-cache.html">增量缓存</a>,命中则跳过。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1584-L1637">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1349-L1392">src/Translator.php</a></p>
<h2>NativeBuilder 与命令选项</h2>
<table><thead><tr><th>组件</th><th>职责</th></tr></thead><tbody><tr><td><code>NativeBuilder</code></td><td>真正执行 <code>compile</code> / <code>link</code>,返回 <code>command</code>/<code>status</code>/<code>output</code></td></tr><tr><td><code>NativeCommandOptionsTrait</code></td><td>汇总编译/链接选项</td></tr><tr><td><code>CompileOptions</code> / <code>LinkOptions</code> / <code>CommandOptions</code></td><td>选项数据对象</td></tr><tr><td><code>FileScanner</code></td><td>识别原生源文件(C/C++/汇编/ObjC)</td></tr><tr><td><code>SourceCompileQueue</code> / <code>SourcePipelineTrait</code></td><td>编译队列与流水线调度</td></tr><tr><td><code>ResourceCompilationTrait</code></td><td>Windows <code>.rc</code><code>.res</code></td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Build\NativeBuilder.php">src/Build/NativeBuilder.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Build">src/Build/</a></p>
<h2>build() 链接</h2>
<p><code>build()</code><code>NativeBuilder::link()</code>,Windows 模式下把 <code>.res</code> 资源文件并入目标;链接失败或产物未生成则 <code>error()</code> 退出。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1649-L1680">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1653-L1659">src/Translator.php</a></p>
<h2>相关阅读</h2>
<ul><li>后端如何产出命令 → <a href="pages/backend.html">编译器后端</a></li><li>缓存如何避免重复编译 → <a href="pages/incremental-cache.html">增量编译缓存</a></li><li>平台相关的链接差异 → <a href="pages/platform.html">跨平台支持</a></li></ul></div></body></html>

@ -0,0 +1,56 @@
<blog>
# 构建流水线
`Translator::compile()` 把生成的 C++ 与 phpx 运行时源文件编译为对象文件,`build()` 再链接成最终产物。两者都委托给 `src/Build/NativeBuilder`,它负责真正派发编译/链接进程。
Sources: [src/Translator.php](src/Translator.php#L1419-L1447) · [src/Translator.php](src/Translator.php#L1649-L1680)
## compile() 步骤
```mermaid
flowchart TD
A["compile(sourceFiles)"] --> B["追加 phpx misc 源\nfiber_generator / helper / main / cli_title"]
B --> C["preparePhpXPrecompiledHeader()"]
C --> D["compileResourceFile() (Windows)"]
D --> E{"支持 pcntl 且 -j>1?"}
E -- "否" --> F["compileSourceFile() 串行"]
E -- "是" --> G["compileWithPcntl() 并行"]
```
phpx misc 源(`typephp_fiber_generator.cc`、`typephp_helper.cc`、`typephp_main.cc`、`php_cli_process_title.c`、`ps_title.c`)按模式追加:embed 加 `typephp_main.cc`,bin 加 cli 标题源。
Sources: [src/Translator.php](src/Translator.php#L1423-L1434)
## 并行编译
`compileWithPcntl()` 在 Unix/Linux/macOS 用 `pcntl_fork` 多进程编译,进度条实时刷新;无 `pcntl` 扩展时自动降级为串行。每个 `compileFile()` 调用前都会先查 [增量缓存](incremental-cache),命中则跳过。
Sources: [src/Translator.php](src/Translator.php#L1584-L1637) · [src/Translator.php](src/Translator.php#L1349-L1392)
## NativeBuilder 与命令选项
| 组件 | 职责 |
|---|---|
| `NativeBuilder` | 真正执行 `compile` / `link`,返回 `command`/`status`/`output` |
| `NativeCommandOptionsTrait` | 汇总编译/链接选项 |
| `CompileOptions` / `LinkOptions` / `CommandOptions` | 选项数据对象 |
| `FileScanner` | 识别原生源文件(C/C++/汇编/ObjC) |
| `SourceCompileQueue` / `SourcePipelineTrait` | 编译队列与流水线调度 |
| `ResourceCompilationTrait` | Windows `.rc``.res` |
Sources: [src/Build/NativeBuilder.php](src/Build/NativeBuilder.php) · [src/Build/](src/Build/)
## build() 链接
`build()``NativeBuilder::link()`,Windows 模式下把 `.res` 资源文件并入目标;链接失败或产物未生成则 `error()` 退出。
Sources: [src/Translator.php](src/Translator.php#L1649-L1680) · [src/Translator.php](src/Translator.php#L1653-L1659)
## 相关阅读
- 后端如何产出命令 → [编译器后端](backend)
- 缓存如何避免重复编译 → [增量编译缓存](incremental-cache)
- 平台相关的链接差异 → [跨平台支持](platform)
</blog>

@ -0,0 +1,38 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>构建速度与性能</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>构建速度与性能</h1>
<p>TypePHP 的「编译速度」包含两个维度:<strong>编译期(PHP→C++→二进制)自身耗时</strong><strong>生成产物的运行性能</strong>。本章聚焦前者——哪些环节最慢,以及已落地的优化。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\AOT_BUILD_SPEED_RESEARCH.md">docs/AOT_BUILD_SPEED_RESEARCH.md</a> · <a href="file:///D:\git\php\aot-compiler\docs\aot-optimization-priority.md">docs/aot-optimization-priority.md</a></p>
<h2>时间都花在哪</h2>
<p>编译期主要成本:</p>
<ol><li><strong>PHP 解析</strong>:每个文件被多次解析(预处理一次、转换一次)——这是主要冗余;</li><li><strong>C++ 翻译</strong><code>doConvert()</code> 遍历 AST 并发射 C++;</li><li><strong>C++ 编译</strong>:大量 <code>.cc</code> 单元编译,受 PCH 与并行度影响;</li><li><strong>链接</strong>:最终链接往往是大项目瓶颈之一。</li></ol>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\AOT_BUILD_SPEED_RESEARCH.md">docs/AOT_BUILD_SPEED_RESEARCH.md</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2455-L2459">src/Translator.php</a></p>
<h2>已落地的优化</h2>
<table><thead><tr><th>优化</th><th>说明</th><th>位置</th></tr></thead><tbody><tr><td><strong>S1 合并解析</strong></td><td>同一文件 AST 在进程内缓存复用(<code>parseCachedAst</code>),消除重复解析</td><td><code>src/Translator.php</code> <code>parseCachedAst()</code></td></tr><tr><td><strong>S2 增量对象缓存</strong></td><td>生成单元按「编译命令 + PHP ABI + 生成头内容指纹」缓存 <code>.o</code>,改动一处不必全量重编</td><td><a href="pages/incremental-cache.html">增量编译缓存</a></td></tr><tr><td><strong>PCH 预编译头</strong></td><td>预编译 <code>phpx.h</code> 等全局头,加速每个编译单元</td><td><code>src/Build/PrecompiledHeaderManager.php</code></td></tr><tr><td><strong>并行编译</strong></td><td><code>-j</code><code>pcntl</code> 多进程编译</td><td><code>src/Translator.php</code> <code>compileWithPcntl()</code></td></tr><tr><td><strong>misc 缓存</strong></td><td>phpx 运行时源按 ABI 指纹缓存</td><td><code>hasMiscObjectFileCache()</code></td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1116-L1160">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1449-L1487">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1584-L1637">src/Translator.php</a></p>
<h2>调试与剖析</h2>
<ul><li><code>--dry</code>:只生成 C++,跳过编译/链接,用于检查翻译与定位;</li><li><code>--profile</code>:启用 gperftools 剖析(仅 Linux),并在 <code>./tpc app.prof</code> 模式调 <code>pprof --web</code></li><li><code>--no-progress</code> / <code>-j</code>:控制进度展示与并行度。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\compiler.php#L48-L73">src/compiler.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L30-L36">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L251-L255">src/Translator.php</a></p>
<h2>相关阅读</h2>
<ul><li>缓存机制细节 → <a href="pages/incremental-cache.html">增量编译缓存</a></li><li>优化器如何提升产物性能 → <a href="pages/optimizer.html">优化器</a></li><li>优先级规划 → <code>docs/aot-optimization-priority.md</code></li></ul></div></body></html>

@ -0,0 +1,46 @@
<blog>
# 构建速度与性能
TypePHP 的「编译速度」包含两个维度:**编译期(PHP→C++→二进制)自身耗时** 与 **生成产物的运行性能**。本章聚焦前者——哪些环节最慢,以及已落地的优化。
Sources: [docs/AOT_BUILD_SPEED_RESEARCH.md](docs/AOT_BUILD_SPEED_RESEARCH.md) · [docs/aot-optimization-priority.md](docs/aot-optimization-priority.md)
## 时间都花在哪
编译期主要成本:
1. **PHP 解析**:每个文件被多次解析(预处理一次、转换一次)——这是主要冗余;
2. **C++ 翻译**:`doConvert()` 遍历 AST 并发射 C++;
3. **C++ 编译**:大量 `.cc` 单元编译,受 PCH 与并行度影响;
4. **链接**:最终链接往往是大项目瓶颈之一。
Sources: [docs/AOT_BUILD_SPEED_RESEARCH.md](docs/AOT_BUILD_SPEED_RESEARCH.md) · [src/Translator.php](src/Translator.php#L2455-L2459)
## 已落地的优化
| 优化 | 说明 | 位置 |
|---|---|---|
| **S1 合并解析** | 同一文件 AST 在进程内缓存复用(`parseCachedAst`),消除重复解析 | `src/Translator.php` `parseCachedAst()` |
| **S2 增量对象缓存** | 生成单元按「编译命令 + PHP ABI + 生成头内容指纹」缓存 `.o`,改动一处不必全量重编 | [增量编译缓存](incremental-cache) |
| **PCH 预编译头** | 预编译 `phpx.h` 等全局头,加速每个编译单元 | `src/Build/PrecompiledHeaderManager.php` |
| **并行编译** | `-j``pcntl` 多进程编译 | `src/Translator.php` `compileWithPcntl()` |
| **misc 缓存** | phpx 运行时源按 ABI 指纹缓存 | `hasMiscObjectFileCache()` |
Sources: [src/Translator.php](src/Translator.php#L1116-L1160) · [src/Translator.php](src/Translator.php#L1449-L1487) · [src/Translator.php](src/Translator.php#L1584-L1637)
## 调试与剖析
- `--dry`:只生成 C++,跳过编译/链接,用于检查翻译与定位;
- `--profile`:启用 gperftools 剖析(仅 Linux),并在 `./tpc app.prof` 模式调 `pprof --web`
- `--no-progress` / `-j`:控制进度展示与并行度。
Sources: [src/compiler.php](src/compiler.php#L48-L73) · [src/Translator.php](src/Translator.php#L30-L36) · [src/Translator.php](src/Translator.php#L251-L255)
## 相关阅读
- 缓存机制细节 → [增量编译缓存](incremental-cache)
- 优化器如何提升产物性能 → [优化器](optimizer)
- 优先级规划 → `docs/aot-optimization-priority.md`
</blog>

@ -0,0 +1,46 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>编译模式</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>编译模式</h1>
<p>TypePHP 通过 <code>-m / --mode</code> 支持三种产物形态,由 <code>Translator::setBuildMode()</code> 归一化。三种模式共享同一套 PHP→C++ 翻译,区别只在于<strong>入口约定与链接目标</strong></p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L574-L589">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\docs\COMPILATION_MODES.md">docs/COMPILATION_MODES.md</a></p>
<h2>三种模式对比</h2>
<table><thead><tr><th>模式</th><th>参数</th><th>产物</th><th>入口要求</th><th>典型用途</th></tr></thead><tbody><tr><td>二进制</td><td><code>bin</code>(默认)</td><td>独立可执行文件</td><td>必须定义 <code>main()</code></td><td>CLI 工具、独立服务</td></tr><tr><td>共享库</td><td><code>lib</code></td><td><code>.so</code> / <code>.dll</code> / <code>.dylib</code></td><td>不需要 <code>main()</code></td><td>供其它程序调用的逻辑库</td></tr><tr><td>扩展</td><td><code>ext</code></td><td>PHP 扩展 <code>.so</code></td><td>不需要 <code>main()</code></td><td>嵌入现有 PHP 应用(php-fpm 等)</td></tr></tbody></table>
<p><code>bin</code><code>binary/cli</code> 的别名;<code>lib</code> 涵盖 <code>library/shared/dll/dylib/so</code><code>ext</code> 等价于 <code>extension</code>。未知值会报错退出。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L574-L589">src/Translator.php</a></p>
<h2>二进制模式(embed)</h2>
<p><code>bin</code> 模式在 <code>genExtension()</code> 中嵌入 <code>cli_set_process_title</code> / <code>cli_get_process_title</code> 内置函数,并在 <code>RINIT</code> 阶段用 <code>php::eval</code> 直接执行 <code>main()</code>(带 <code>$argc/$argv</code> 或空参数两种签名)。</p>
<pre class="mermaid">flowchart LR
A[&quot;main()&quot;] --&gt; B[&quot;RINIT: php_app_init()&quot;]
B --&gt; C[&quot;php::eval(&#x27;main($argc,$argv)&#x27;)&quot;]
C --&gt; D[&quot;RSHUTDOWN: php_app_clean()&quot;]</pre>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1043-L1066">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1055-L1059">src/Translator.php</a></p>
<h2>共享库模式</h2>
<p>库模式下,编译器会:</p>
<ul><li><code>exported</code> 函数加上 <code>TYPEPHP_&lt;NAME&gt;_API</code> 导出宏;</li><li>生成 <code>&lt;target&gt;.stub.php</code> 导入桩,供其它 TypePHP 工程按库调用;</li><li>在 Linux 上自动给目标名加 <code>lib</code> 前缀。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L652-L666">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L633-L650">src/Translator.php#L1736-L1738) · [src/Translator.php</a></p>
<h2>扩展模式</h2>
<p><code>ext</code> 模式会跳过 <code>main()</code>,使用 <code>ZEND_GET_MODULE()</code> 注册 Zend 模块,并在 <code>RSHUTDOWN</code> 清空函数/类/属性表以支持重复加载。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1033-L1037">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1090-L1091">src/Translator.php</a></p>
<h2>相关阅读</h2>
<ul><li>命令行全量参数见 <a href="pages/compile-time-attributes.html">编译期属性</a><code>docs/COMPILER_CLI.md</code></li><li>产物如何被链接 → <a href="pages/build-pipeline.html">构建流水线</a></li></ul></div></body></html>

@ -0,0 +1,54 @@
<blog>
# 编译模式
TypePHP 通过 `-m / --mode` 支持三种产物形态,由 `Translator::setBuildMode()` 归一化。三种模式共享同一套 PHP→C++ 翻译,区别只在于**入口约定与链接目标**。
Sources: [src/Translator.php](src/Translator.php#L574-L589) · [docs/COMPILATION_MODES.md](docs/COMPILATION_MODES.md)
## 三种模式对比
| 模式 | 参数 | 产物 | 入口要求 | 典型用途 |
|---|---|---|---|---|
| 二进制 | `bin`(默认) | 独立可执行文件 | 必须定义 `main()` | CLI 工具、独立服务 |
| 共享库 | `lib` | `.so` / `.dll` / `.dylib` | 不需要 `main()` | 供其它程序调用的逻辑库 |
| 扩展 | `ext` | PHP 扩展 `.so` | 不需要 `main()` | 嵌入现有 PHP 应用(php-fpm 等) |
`bin``binary/cli` 的别名;`lib` 涵盖 `library/shared/dll/dylib/so`;`ext` 等价于 `extension`。未知值会报错退出。
Sources: [src/Translator.php](src/Translator.php#L574-L589)
## 二进制模式(embed)
`bin` 模式在 `genExtension()` 中嵌入 `cli_set_process_title` / `cli_get_process_title` 内置函数,并在 `RINIT` 阶段用 `php::eval` 直接执行 `main()`(带 `$argc/$argv` 或空参数两种签名)。
```mermaid
flowchart LR
A["main()"] --> B["RINIT: php_app_init()"]
B --> C["php::eval('main($argc,$argv)')"]
C --> D["RSHUTDOWN: php_app_clean()"]
```
Sources: [src/Translator.php](src/Translator.php#L1043-L1066) · [src/Translator.php](src/Translator.php#L1055-L1059)
## 共享库模式
库模式下,编译器会:
- 对 `exported` 函数加上 `TYPEPHP_<NAME>_API` 导出宏;
- 生成 `<target>.stub.php` 导入桩,供其它 TypePHP 工程按库调用;
- 在 Linux 上自动给目标名加 `lib` 前缀。
Sources: [src/Translator.php](src/Translator.php#L652-L666) · [src/Translator.php#L1736-L1738) · [src/Translator.php](src/Translator.php#L633-L650)
## 扩展模式
`ext` 模式会跳过 `main()`,使用 `ZEND_GET_MODULE()` 注册 Zend 模块,并在 `RSHUTDOWN` 清空函数/类/属性表以支持重复加载。
Sources: [src/Translator.php](src/Translator.php#L1033-L1037) · [src/Translator.php](src/Translator.php#L1090-L1091)
## 相关阅读
- 命令行全量参数见 [编译期属性](compile-time-attributes) 与 `docs/COMPILER_CLI.md`
- 产物如何被链接 → [构建流水线](build-pipeline)。
</blog>

@ -0,0 +1,37 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>编译期属性</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>编译期属性</h1>
<p>除了 PHP 8 的运行时 attribute,TypePHP 引入一组<strong>编译期函数与属性</strong>,用于在翻译阶段向编译器传递类型/语义提示。它们被 <code>Transform</code> 层的 Lowering 算子在遍历 AST 时识别与校验。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Transform\CompileTimeAttribute.php">src/Transform/CompileTimeAttribute.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Transform\CompileTimeAttributeRegistry.php">src/Transform/CompileTimeAttributeRegistry.php</a> · <a href="file:///D:\git\php\aot-compiler\docs\COMPILE_TIME_FUNCTIONS.md">docs/COMPILE_TIME_FUNCTIONS.md</a></p>
<h2>核心编译期函数</h2>
<table><thead><tr><th>函数</th><th>用途</th></tr></thead><tbody><tr><td><code>any($v)</code></td><td>显式标记变量为「任意 PHP 值」(通用 <code>php::Var</code>),绕过原生类型推断</td></tr><tr><td><code>refval($v, Type)</code></td><td>以指定类型解释变量(类型恢复/断言)</td></tr><tr><td><code>objval($v, &#x27;ClassName&#x27;)</code></td><td>从数组/返回值恢复对象的类信息(见 <a href="pages/native-types.html">native-types</a></td></tr><tr><td><code>expected()</code> / <code>unexpected()</code></td><td>编译期断言/分支提示</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\COMPILE_TIME_FUNCTIONS.md">docs/COMPILE_TIME_FUNCTIONS.md</a></p>
<h2>编译期属性</h2>
<p><code>CompileTimeAttribute::validateNode()</code><code>Visitor::enterNode</code> 中对每个节点校验编译期属性;<code>CompileTimeAttributeRegistry</code> 维护已知属性注册表。典型如 <code>MustUse</code>(要求函数/方法有返回值且调用方使用)、<code>Any</code>(类型放宽)等——<code>Preprocessor</code><code>parseFunctionDecl()</code> 中也会对 <code>MustUse</code> 强行校验返回类型。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Transform\Visitor.php#L30">src/Transform/Visitor.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L537">src/Preprocessor.php</a></p>
<h2>与 Lowering 的配合</h2>
<p><code>FunctionAttributeLowering</code> / <code>ConstructorLowering</code> / <code>GetterLowering</code> / <code>PropertyMethodLowering</code> 把带编译期属性的节点降级为可翻译结构;<code>RuntimeAttributeFactoryLowering</code> 作为独立访客挂在遍历链上,处理运行时工厂属性。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Transform\Visitor.php#L30-L35">src/Transform/Visitor.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2467">src/Translator.php</a></p>
<h2>相关阅读</h2>
<ul><li>遍历中如何调用这些算子 → <a href="pages/transform.html">AST 转换</a></li><li>类型如何因此被放宽/恢复 → <a href="pages/type-check.html">类型检查与兼容</a></li><li>SSA 优化如何消费类型信息 → <a href="pages/optimizer.html">优化器</a></li></ul></div></body></html>

@ -0,0 +1,38 @@
<blog>
# 编译期属性
除了 PHP 8 的运行时 attribute,TypePHP 引入一组**编译期函数与属性**,用于在翻译阶段向编译器传递类型/语义提示。它们被 `Transform` 层的 Lowering 算子在遍历 AST 时识别与校验。
Sources: [src/Transform/CompileTimeAttribute.php](src/Transform/CompileTimeAttribute.php) · [src/Transform/CompileTimeAttributeRegistry.php](src/Transform/CompileTimeAttributeRegistry.php) · [docs/COMPILE_TIME_FUNCTIONS.md](docs/COMPILE_TIME_FUNCTIONS.md)
## 核心编译期函数
| 函数 | 用途 |
|---|---|
| `any($v)` | 显式标记变量为「任意 PHP 值」(通用 `php::Var`),绕过原生类型推断 |
| `refval($v, Type)` | 以指定类型解释变量(类型恢复/断言) |
| `objval($v, 'ClassName')` | 从数组/返回值恢复对象的类信息(见 [native-types](native-types)) |
| `expected()` / `unexpected()` | 编译期断言/分支提示 |
Sources: [docs/COMPILE_TIME_FUNCTIONS.md](docs/COMPILE_TIME_FUNCTIONS.md)
## 编译期属性
`CompileTimeAttribute::validateNode()``Visitor::enterNode` 中对每个节点校验编译期属性;`CompileTimeAttributeRegistry` 维护已知属性注册表。典型如 `MustUse`(要求函数/方法有返回值且调用方使用)、`Any`(类型放宽)等——`Preprocessor` 在 `parseFunctionDecl()` 中也会对 `MustUse` 强行校验返回类型。
Sources: [src/Transform/Visitor.php](src/Transform/Visitor.php#L30) · [src/Preprocessor.php](src/Preprocessor.php#L537)
## 与 Lowering 的配合
`FunctionAttributeLowering` / `ConstructorLowering` / `GetterLowering` / `PropertyMethodLowering` 把带编译期属性的节点降级为可翻译结构;`RuntimeAttributeFactoryLowering` 作为独立访客挂在遍历链上,处理运行时工厂属性。
Sources: [src/Transform/Visitor.php](src/Transform/Visitor.php#L30-L35) · [src/Translator.php](src/Translator.php#L2467)
## 相关阅读
- 遍历中如何调用这些算子 → [AST 转换](transform)
- 类型如何因此被放宽/恢复 → [类型检查与兼容](type-check)
- SSA 优化如何消费类型信息 → [优化器](optimizer)
</blog>

@ -0,0 +1,44 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>扩展模块生成</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>扩展模块生成</h1>
<p><code>genExtension()</code> 是流水线里最大的一块生成逻辑,产出 <code>extension-&lt;target&gt;.cc</code>——一个标准 Zend 扩展模块。它把前面收集的所有类、函数、常量、全局变量缝合进 Zend 的生命周期钩子(MINIT / MINIT / RINIT / RSHUTDOWN)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L739-L1105">src/Translator.php</a></p>
<h2>生成结构</h2>
<pre class="mermaid">flowchart TD
A[&quot;extension-&lt;target&gt;.cc&quot;] --&gt; B[&quot;全局变量 / 类表 / 函数表 / 属性表 声明&quot;]
B --&gt; C[&quot;php_get_class / php_get_func / php_get_method / php_get_prop&quot;]
C --&gt; D[&quot;ext_functions[] (ZEND_FE 列表)&quot;]
D --&gt; E[&quot;PHP_MINIT: 注册类/反射处理器/符号&quot;]
E --&gt; F[&quot;PHP_RINIT: php_app_init + eval(main)&quot;]
F --&gt; G[&quot;PHP_RSHUTDOWN: php_app_clean + request_shutdown&quot;]
G --&gt; H[&quot;zend_module_entry&quot;]</pre>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L769-L1087">src/Translator.php</a></p>
<h2>关键片段</h2>
<ul><li><strong>符号解析函数</strong><code>php_get_class</code> / <code>php_get_func</code> / <code>php_get_method</code> / <code>php_get_prop</code> 用缓存的 <code>class_map</code> / <code>func_map</code> / <code>prop_map</code> 把字符串名映射到运行时 <code>zend_class_entry<em></code> / <code>zend_function</em></code>,带 <code>UNEXPECTED</code> 快路径。</li><li><strong>MINIT</strong>:安装 Fiber/Generator 类、反射属性处理器,逐个注册类并设置属性处理器;最后调用各 <code>registerSymbols</code> 函数。</li><li><strong>RINIT</strong><code>php_app_init()</code> 注册常量、全局变量、静态属性、类数组常量;<code>bin</code> 模式下 <code>php::eval</code> 执行 <code>main()</code>(按 <code>$argc/$argv</code> 或空参两种签名)。</li><li><strong>RSHUTDOWN</strong><code>php_app_clean()</code> 清理全局变量、VAR 常量、类数组常量(含继承来的数组常量),<code>ext</code> 模式额外清空三张映射表以支持模块重载。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L791-L837">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L884-L1066">src/Translator.php</a></p>
<h2>模式差异</h2>
<ul><li><code>bin</code>:嵌入 <code>cli_set_process_title</code><code>RINIT</code> 直接跑 <code>main()</code></li><li><code>lib</code>:Linux 下导出 <code>save_ps_args</code> 占位符;<code>getModuleName()</code> 仍生成模块入口;</li><li><code>ext</code>:用 <code>ZEND_GET_MODULE()</code>,跳过 <code>main()</code>,RSHUTDOWN 清空映射表。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L754-L767">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1090-L1096">src/Translator.php</a></p>
<h2>相关阅读</h2>
<ul><li>函数/类声明如何生成 → <a href="pages/gen-functions-classes.html">函数与类生成</a></li><li>生成的 <code>.cc</code> 如何被编译/缓存 → <a href="pages/incremental-cache.html">增量编译缓存</a></li><li>三种产物形态 → <a href="pages/compilation-modes.html">编译模式</a></li></ul></div></body></html>

@ -0,0 +1,47 @@
<blog>
# 扩展模块生成
`genExtension()` 是流水线里最大的一块生成逻辑,产出 `extension-<target>.cc`——一个标准 Zend 扩展模块。它把前面收集的所有类、函数、常量、全局变量缝合进 Zend 的生命周期钩子(MINIT / MINIT / RINIT / RSHUTDOWN)。
Sources: [src/Translator.php](src/Translator.php#L739-L1105)
## 生成结构
```mermaid
flowchart TD
A["extension-<target>.cc"] --> B["全局变量 / 类表 / 函数表 / 属性表 声明"]
B --> C["php_get_class / php_get_func / php_get_method / php_get_prop"]
C --> D["ext_functions[] (ZEND_FE 列表)"]
D --> E["PHP_MINIT: 注册类/反射处理器/符号"]
E --> F["PHP_RINIT: php_app_init + eval(main)"]
F --> G["PHP_RSHUTDOWN: php_app_clean + request_shutdown"]
G --> H["zend_module_entry"]
```
Sources: [src/Translator.php](src/Translator.php#L769-L1087)
## 关键片段
- **符号解析函数**:`php_get_class` / `php_get_func` / `php_get_method` / `php_get_prop` 用缓存的 `class_map` / `func_map` / `prop_map` 把字符串名映射到运行时 `zend_class_entry*` / `zend_function*`,带 `UNEXPECTED` 快路径。
- **MINIT**:安装 Fiber/Generator 类、反射属性处理器,逐个注册类并设置属性处理器;最后调用各 `registerSymbols` 函数。
- **RINIT**:`php_app_init()` 注册常量、全局变量、静态属性、类数组常量;`bin` 模式下 `php::eval` 执行 `main()`(按 `$argc/$argv` 或空参两种签名)。
- **RSHUTDOWN**:`php_app_clean()` 清理全局变量、VAR 常量、类数组常量(含继承来的数组常量),`ext` 模式额外清空三张映射表以支持模块重载。
Sources: [src/Translator.php](src/Translator.php#L791-L837) · [src/Translator.php](src/Translator.php#L884-L1066)
## 模式差异
- `bin`:嵌入 `cli_set_process_title`,`RINIT` 直接跑 `main()`
- `lib`:Linux 下导出 `save_ps_args` 占位符;`getModuleName()` 仍生成模块入口;
- `ext`:用 `ZEND_GET_MODULE()`,跳过 `main()`,RSHUTDOWN 清空映射表。
Sources: [src/Translator.php](src/Translator.php#L754-L767) · [src/Translator.php](src/Translator.php#L1090-L1096)
## 相关阅读
- 函数/类声明如何生成 → [函数与类生成](gen-functions-classes)
- 生成的 `.cc` 如何被编译/缓存 → [增量编译缓存](incremental-cache)
- 三种产物形态 → [编译模式](compilation-modes)
</blog>

@ -0,0 +1,41 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>函数与类生成</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>函数与类生成</h1>
<p>本章聚焦两个最频繁的发射动作:<code>genFunctionWrapper()</code>(函数/方法 → C++ 函数)与 <code>genClassWrapper()</code>(类/接口/枚举 → C++ 类与 zend_class_entry 注册)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2515-L2528">src/Translator.php</a></p>
<h2>函数生成</h2>
<p><code>genFunctionDeclarations()</code> 为每个 <code>FunctionDef</code> 生成前向声明,规则包括:</p>
<ul><li><strong>方法</strong>:首参加 <code>php::Object &amp;this_</code>;trait 父类场景追加 <code>zend_class_entry *trait_parent_ce</code></li><li><strong>变参</strong>:声明为 <code>php::Array</code> 并附默认参数表达式;</li><li><strong>多返回</strong>:在 <code>typephp_multi_return</code> 命名空间再生成一份返回元组版本(<code>hasMultiReturn()</code>);</li><li><strong>热/冷路径</strong><code>hot</code>/<code>cold</code><code>TYPEPHP_HOT_ATTRIBUTE</code> / <code>TYPEPHP_COLD_ATTRIBUTE</code></li><li><strong>导入/导出</strong>:跨库函数用 <code>TYPEPHP_&lt;LIB&gt;_IMPORT</code> 宏,库导出函数用 <code>TYPEPHP_&lt;NAME&gt;_API</code></li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1740-L1830">src/Translator.php</a></p>
<h2>类生成</h2>
<p><code>genClassWrapper()</code> 为每个 <code>ClassDef</code> / <code>InterfaceDef</code> 生成:</p>
<ol><li>C++ 类结构与属性布局;</li><li>静态属性默认值(<code>php::setStaticProperty</code>);</li><li><code>create_object</code> 钩子与属性处理器安装(<code>genClassPropertyInit()</code>);</li><li>数组常量(<code>genClassArrayConstants()</code>)。</li></ol>
<p>属性初始化分两种情况(<a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1909-L1963">src/Translator.php</a>):</p>
<ul><li><strong>标量/常量默认值</strong>:包成 <code>php::Var</code> 写入属性表;</li><li><strong>数组默认值</strong>:用 <code>ArrayInitPlan</code> 生成构造表达式,避免重复求值。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1894-L1966">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L974-L1002">src/Preprocessor.php</a></p>
<h2>类注册排序</h2>
<p><code>genClassCeList()</code><code>marcj/topsort</code> 按继承依赖拓扑排序生成 <code>zend_class_entry</code> 注册顺序,确保父类先于子类注册;内置类/接口(不在符号表中)直接加入排序而不参与依赖解析。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2539-L2568">src/Translator.php</a></p>
<h2>相关阅读</h2>
<ul><li>所有声明头如何汇总 → <a href="pages/generator.html">生成器总览</a></li><li>模块入口如何引用这些符号 → <a href="pages/gen-extension.html">扩展模块生成</a></li><li>属性类型约束 → <a href="pages/native-types.html">原生类型映射</a></li></ul></div></body></html>

@ -0,0 +1,48 @@
<blog>
# 函数与类生成
本章聚焦两个最频繁的发射动作:`genFunctionWrapper()`(函数/方法 → C++ 函数)与 `genClassWrapper()`(类/接口/枚举 → C++ 类与 zend_class_entry 注册)。
Sources: [src/Translator.php](src/Translator.php#L2515-L2528)
## 函数生成
`genFunctionDeclarations()` 为每个 `FunctionDef` 生成前向声明,规则包括:
- **方法**:首参加 `php::Object &this_`;trait 父类场景追加 `zend_class_entry *trait_parent_ce`
- **变参**:声明为 `php::Array` 并附默认参数表达式;
- **多返回**:在 `typephp_multi_return` 命名空间再生成一份返回元组版本(`hasMultiReturn()`);
- **热/冷路径**:`hot`/`cold` 加 `TYPEPHP_HOT_ATTRIBUTE` / `TYPEPHP_COLD_ATTRIBUTE`
- **导入/导出**:跨库函数用 `TYPEPHP_<LIB>_IMPORT` 宏,库导出函数用 `TYPEPHP_<NAME>_API`
Sources: [src/Translator.php](src/Translator.php#L1740-L1830)
## 类生成
`genClassWrapper()` 为每个 `ClassDef` / `InterfaceDef` 生成:
1. C++ 类结构与属性布局;
2. 静态属性默认值(`php::setStaticProperty`);
3. `create_object` 钩子与属性处理器安装(`genClassPropertyInit()`);
4. 数组常量(`genClassArrayConstants()`)。
属性初始化分两种情况([src/Translator.php](src/Translator.php#L1909-L1963)):
- **标量/常量默认值**:包成 `php::Var` 写入属性表;
- **数组默认值**:用 `ArrayInitPlan` 生成构造表达式,避免重复求值。
Sources: [src/Translator.php](src/Translator.php#L1894-L1966) · [src/Preprocessor.php](src/Preprocessor.php#L974-L1002)
## 类注册排序
`genClassCeList()``marcj/topsort` 按继承依赖拓扑排序生成 `zend_class_entry` 注册顺序,确保父类先于子类注册;内置类/接口(不在符号表中)直接加入排序而不参与依赖解析。
Sources: [src/Translator.php](src/Translator.php#L2539-L2568)
## 相关阅读
- 所有声明头如何汇总 → [生成器总览](generator)
- 模块入口如何引用这些符号 → [扩展模块生成](gen-extension)
- 属性类型约束 → [原生类型映射](native-types)
</blog>

@ -0,0 +1,37 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>生成器总览</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>生成器总览</h1>
<p><code>doConvert()</code> 收集完一个文件的语句后,调用各 <code>genXxx</code> 方法把 PHP 构造发射为 C++。所有发射逻辑分两类:<strong>声明头文件生成</strong><code><em>.h</code>)与 <strong>实现单元生成</strong><code></em>.cc</code>)。<code>src/Generator/</code> 下是一组辅助生成器,负责闭包、匿名类、参数、资源文件等专项结构。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2477-L2536">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Generator">src/Generator/</a></p>
<h2>主发射方法(在 Translator / CompilerBase)</h2>
<table><thead><tr><th>方法</th><th>产物</th></tr></thead><tbody><tr><td><code>genFunctionDeclarations()</code></td><td><code>php_&lt;target&gt;_func_decl.h</code>:所有函数前向声明</td></tr><tr><td><code>genDataDeclarations()</code></td><td><code>php_&lt;target&gt;_data_decl.h</code>:全局变量 / 常量 / 类映射表声明</td></tr><tr><td><code>genExtension()</code></td><td><code>extension-&lt;target&gt;.cc</code>:模块入口(MINIT/RINIT/RSHUTDOWN)</td></tr><tr><td><code>genClassWrapper()</code></td><td>单个类/接口的 C++ 实现</td></tr><tr><td><code>genFunctionWrapper()</code></td><td>单个函数的 C++ 实现</td></tr><tr><td><code>genClassPropertyInit()</code></td><td>属性默认值与 create_object 初始化块</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1728-L1786">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L692-L737">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L739-L1105">src/Translator.php</a></p>
<h2>专项生成器(src/Generator/)</h2>
<table><thead><tr><th>生成器</th><th>负责</th></tr></thead><tbody><tr><td><code>ClosureGenerator</code></td><td>匿名函数 / 闭包 → C++ lambda 包装</td></tr><tr><td><code>AnonClassGenerator</code></td><td>匿名类</td></tr><tr><td><code>CallArgumentGenerator</code></td><td>调用实参的默认值/展开处理</td></tr><tr><td><code>DefaultArgumentGenerator</code></td><td>默认参数 helper(<code>genDefaultArgumentHelper*</code></td></tr><tr><td><code>FiberGenerator</code></td><td>协程 / Fiber 生成</td></tr><tr><td><code>PropertyPromotion</code></td><td>构造函数属性提升</td></tr><tr><td><code>ResourceFileGenerator</code></td><td>Windows <code>.rc</code> 资源文件</td></tr><tr><td><code>LibraryImportStubGenerator</code></td><td>跨库导入桩 <code>&lt;target&gt;.stub.php</code></td></tr><tr><td><code>TypeCheckGenerator</code></td><td>运行时类型检查代码</td></tr><tr><td><code>Symbol</code></td><td>符号名转义工具</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Generator">src/Generator/</a></p>
<h2>头文件依赖与增量缓存</h2>
<p>每个生成的 <code>.cc</code><code>#include</code> 项目级 <code>func_decl.h</code> / <code>data_decl.h</code> 与各自的 <code>_arginfo.h</code>。这些共享头文件的内容指纹正是 <a href="pages/incremental-cache.html">增量编译缓存</a> 判定是否重编译的依据——任一头变化都会让依赖它的 <code>.cc</code> 失效。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1253-L1270">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1880-L1892">src/Translator.php</a></p>
<h2>相关阅读</h2>
<ul><li>函数与类如何逐个生成 → <a href="pages/gen-functions-classes.html">函数与类生成</a></li><li>模块入口如何拼装 → <a href="pages/gen-extension.html">扩展模块生成</a></li><li>翻译前的 AST 改写 → <a href="pages/transform.html">AST 转换</a></li></ul></div></body></html>

@ -0,0 +1,51 @@
<blog>
# 生成器总览
`doConvert()` 收集完一个文件的语句后,调用各 `genXxx` 方法把 PHP 构造发射为 C++。所有发射逻辑分两类:**声明头文件生成**(`*.h`)与 **实现单元生成**(`*.cc`)。`src/Generator/` 下是一组辅助生成器,负责闭包、匿名类、参数、资源文件等专项结构。
Sources: [src/Translator.php](src/Translator.php#L2477-L2536) · [src/Generator/](src/Generator/)
## 主发射方法(在 Translator / CompilerBase)
| 方法 | 产物 |
|---|---|
| `genFunctionDeclarations()` | `php_<target>_func_decl.h`:所有函数前向声明 |
| `genDataDeclarations()` | `php_<target>_data_decl.h`:全局变量 / 常量 / 类映射表声明 |
| `genExtension()` | `extension-<target>.cc`:模块入口(MINIT/RINIT/RSHUTDOWN) |
| `genClassWrapper()` | 单个类/接口的 C++ 实现 |
| `genFunctionWrapper()` | 单个函数的 C++ 实现 |
| `genClassPropertyInit()` | 属性默认值与 create_object 初始化块 |
Sources: [src/Translator.php](src/Translator.php#L1728-L1786) · [src/Translator.php](src/Translator.php#L692-L737) · [src/Translator.php](src/Translator.php#L739-L1105)
## 专项生成器(src/Generator/)
| 生成器 | 负责 |
|---|---|
| `ClosureGenerator` | 匿名函数 / 闭包 → C++ lambda 包装 |
| `AnonClassGenerator` | 匿名类 |
| `CallArgumentGenerator` | 调用实参的默认值/展开处理 |
| `DefaultArgumentGenerator` | 默认参数 helper(`genDefaultArgumentHelper*`) |
| `FiberGenerator` | 协程 / Fiber 生成 |
| `PropertyPromotion` | 构造函数属性提升 |
| `ResourceFileGenerator` | Windows `.rc` 资源文件 |
| `LibraryImportStubGenerator` | 跨库导入桩 `<target>.stub.php` |
| `TypeCheckGenerator` | 运行时类型检查代码 |
| `Symbol` | 符号名转义工具 |
Sources: [src/Generator/](src/Generator/)
## 头文件依赖与增量缓存
每个生成的 `.cc``#include` 项目级 `func_decl.h` / `data_decl.h` 与各自的 `_arginfo.h`。这些共享头文件的内容指纹正是 [增量编译缓存](incremental-cache) 判定是否重编译的依据——任一头变化都会让依赖它的 `.cc` 失效。
Sources: [src/Translator.php](src/Translator.php#L1253-L1270) · [src/Translator.php](src/Translator.php#L1880-L1892)
## 相关阅读
- 函数与类如何逐个生成 → [函数与类生成](gen-functions-classes)
- 模块入口如何拼装 → [扩展模块生成](gen-extension)
- 翻译前的 AST 改写 → [AST 转换](transform)
</blog>

@ -0,0 +1,47 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>高精度数值类型</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>高精度数值类型</h1>
<p>PHP 原生 <code>int</code>(64 位)与 <code>float</code>(IEEE754 double)在金融、科学、密码学场景精度不足。TypePHP 提供三种高精度类型,底层直接调用成熟 C/C++ 数学库,<strong>零方法查找/解释器分派开销</strong>(运算本身仍有数学库计算与装箱成本)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\HIGH_PRECISION_TYPES.md#L1-L44">docs/HIGH_PRECISION_TYPES.md</a></p>
<h2>三种类型概览</h2>
<table><thead><tr><th>类型</th><th>底层库</th><th>特点</th></tr></thead><tbody><tr><td><code>bigInt</code> (<code>php::BigInt</code>)</td><td>GMP (<code>libgmp</code>)</td><td>任意精度整数,永不溢出</td></tr><tr><td><code>decimal</code> (<code>php::Decimal</code>)</td><td>libmpdec</td><td>十进制小数,约 50 位有效数字,无二进制浮点误差</td></tr><tr><td><code>bigFloat</code> (<code>php::BigFloat</code>)</td><td>MPFR (<code>libmpfr</code>)</td><td>默认 256 bit,字符串输出 64 位有效数字</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\HIGH_PRECISION_TYPES.md#L39-L43">docs/HIGH_PRECISION_TYPES.md</a> · <a href="file:///D:\git\php\aot-compiler\src\Type.php#L15-L17">src/Type.php</a></p>
<h2>使用前置</h2>
<pre><code>&lt;?php
declare(strict_types=1);
use native_types;
function main(): void {
$x = bigInt(123456789012345678901234567890); // 30 位整数不丢精度
}</code></pre>
<p>系统需安装:<code>libgmp-dev</code><code>libmpdec-dev</code><code>libmpfr-dev</code>(见 README 依赖章节)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\HIGH_PRECISION_TYPES.md#L51-L59">docs/HIGH_PRECISION_TYPES.md</a> · <a href="file:///D:\git\php\aot-compiler\README.md#L29-L40">README.md</a></p>
<h2>能力</h2>
<ul><li><strong>算术/比较/复合赋值</strong><code>+ - * / %</code><code>&lt; &gt; ==</code> 以及 <code>+=</code> 等均可用于高精度类型;</li><li><strong>通用方法调用</strong>:通过 <a href="file:///D:\git\php\aot-compiler\universal-methods">通用方法</a>(见 <code>docs/UNIVERSAL_METHODS.md</code>)统一接口;</li><li><strong>类型转换</strong>:高精度类型之间及与原生类型可互转;</li><li><strong>超长字面量自动识别</strong>:超出原生范围的整数字面量自动识别为 <code>bigInt</code></li><li><strong>混合运算类型提升</strong>:不同精度参与运算时按规则提升。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\HIGH_PRECISION_TYPES.md#L7-L17">docs/HIGH_PRECISION_TYPES.md</a></p>
<h2>设计取舍与优化</h2>
<p>相关设计文档(属于调研/规划,可能与当前实现有出入):</p>
<ul><li><code>docs/BIG_NUMBER_INPLACE_OPTIMIZATION_PLAN.md</code> —— 高精度类型原地运算优化方案;</li><li><code>docs/GMP_GAP.md</code> —— GMP 能力差异分析。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\BIG_NUMBER_INPLACE_OPTIMIZATION_PLAN.md">docs/BIG_NUMBER_INPLACE_OPTIMIZATION_PLAN.md</a> · <a href="file:///D:\git\php\aot-compiler\docs\GMP_GAP.md">docs/GMP_GAP.md</a></p>
<h2>相关阅读</h2>
<ul><li>类型如何映射到 C++ → <a href="pages/native-types.html">原生类型映射</a></li><li>类型判定逻辑 → <a href="pages/type-check.html">类型检查与兼容</a></li><li>数组/容器配合 → <code>docs/STD_CONTAINERS.md</code></li></ul></div></body></html>

@ -0,0 +1,58 @@
<blog>
# 高精度数值类型
PHP 原生 `int`(64 位)与 `float`(IEEE754 double)在金融、科学、密码学场景精度不足。TypePHP 提供三种高精度类型,底层直接调用成熟 C/C++ 数学库,**零方法查找/解释器分派开销**(运算本身仍有数学库计算与装箱成本)。
Sources: [docs/HIGH_PRECISION_TYPES.md](docs/HIGH_PRECISION_TYPES.md#L1-L44)
## 三种类型概览
| 类型 | 底层库 | 特点 |
|---|---|---|
| `bigInt` (`php::BigInt`) | GMP (`libgmp`) | 任意精度整数,永不溢出 |
| `decimal` (`php::Decimal`) | libmpdec | 十进制小数,约 50 位有效数字,无二进制浮点误差 |
| `bigFloat` (`php::BigFloat`) | MPFR (`libmpfr`) | 默认 256 bit,字符串输出 64 位有效数字 |
Sources: [docs/HIGH_PRECISION_TYPES.md](docs/HIGH_PRECISION_TYPES.md#L39-L43) · [src/Type.php](src/Type.php#L15-L17)
## 使用前置
```php
<?php
declare(strict_types=1);
use native_types;
function main(): void {
$x = bigInt(123456789012345678901234567890); // 30 位整数不丢精度
}
```
系统需安装:`libgmp-dev`、`libmpdec-dev`、`libmpfr-dev`(见 README 依赖章节)。
Sources: [docs/HIGH_PRECISION_TYPES.md](docs/HIGH_PRECISION_TYPES.md#L51-L59) · [README.md](README.md#L29-L40)
## 能力
- **算术/比较/复合赋值**:`+ - * / %`、`< > ==` 以及 `+=` 等均可用于高精度类型;
- **通用方法调用**:通过 [通用方法](universal-methods)(见 `docs/UNIVERSAL_METHODS.md`)统一接口;
- **类型转换**:高精度类型之间及与原生类型可互转;
- **超长字面量自动识别**:超出原生范围的整数字面量自动识别为 `bigInt`
- **混合运算类型提升**:不同精度参与运算时按规则提升。
Sources: [docs/HIGH_PRECISION_TYPES.md](docs/HIGH_PRECISION_TYPES.md#L7-L17)
## 设计取舍与优化
相关设计文档(属于调研/规划,可能与当前实现有出入):
- `docs/BIG_NUMBER_INPLACE_OPTIMIZATION_PLAN.md` —— 高精度类型原地运算优化方案;
- `docs/GMP_GAP.md` —— GMP 能力差异分析。
Sources: [docs/BIG_NUMBER_INPLACE_OPTIMIZATION_PLAN.md](docs/BIG_NUMBER_INPLACE_OPTIMIZATION_PLAN.md) · [docs/GMP_GAP.md](docs/GMP_GAP.md)
## 相关阅读
- 类型如何映射到 C++ → [原生类型映射](native-types)
- 类型判定逻辑 → [类型检查与兼容](type-check)
- 数组/容器配合 → `docs/STD_CONTAINERS.md`
</blog>

@ -0,0 +1,38 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>不支持的 PHP 特性</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>不支持的 PHP 特性</h1>
<p>AOT 编译对 PHP 运行时做了静态化假设,因此<strong>并非所有 PHP 特性都可编译</strong>。遇到编译报错(如 <code>Not implemented</code>)时,应据此清单判断是设计限制、有意规则,还是尚未实现。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\INCOMPATIBLE_PHP_FEATURES.md">docs/INCOMPATIBLE_PHP_FEATURES.md</a> · <a href="file:///D:\git\php\aot-compiler\docs\PHP_INCOMPATIBILITY_CLASSIFICATION.md">docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md</a></p>
<h2>不兼容分类</h2>
<p><code>PHP_INCOMPATIBILITY_CLASSIFICATION.md</code> 把限制分成四类,避免维护多份清单:</p>
<table><thead><tr><th>类别</th><th>含义</th></tr></thead><tbody><tr><td><strong>Hard Limit</strong></td><td>架构上无法支持(如依赖运行时动态加载的特性)</td></tr><tr><td><strong>Intentional Rule</strong></td><td>有意为之的设计规则(如可执行代码必须放进 <code>main()</code></td></tr><tr><td><strong>Pending</strong></td><td>计划支持但尚未实现</td></tr><tr><td><strong>Partial</strong></td><td>部分支持,有约束条件</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\PHP_INCOMPATIBILITY_CLASSIFICATION.md">docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md</a></p>
<h2>常见设计规则</h2>
<ul><li><strong>游离可执行代码</strong>:文件顶层的可执行语句(循环、函数调用)不被允许,必须放在 <code>main()</code> 或函数体内;</li><li><strong>固定值类型属性不可 <code>unset</code>/赋 <code>null</code></strong>:见 <a href="pages/native-types.html">原生类型映射</a></li><li><strong>显式返回类型</strong>:函数/方法必须声明返回类型(<code>parseFunctionDecl()</code><code>fatalError</code>)。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\QUICKSTART.md#L191-L203">docs/QUICKSTART.md</a> · <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L497-L536">src/Preprocessor.php</a></p>
<h2>维护约定</h2>
<ol><li>兼容性变化须<strong>同时更新</strong> <code>INCOMPATIBLE_PHP_FEATURES.md</code> 与分类文档;</li><li>所有语法/语义限制统一链接到当前兼容性清单,避免重复;</li><li>功能是否支持以 <strong>PHPT / PHPUnit 回归测试</strong> 为准;</li><li>历史研究文档(hhvm/kphp/peachpie 对比等)保留原始结论,不悄然改写为当前状态。</li></ol>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\README.md#L30-L35">docs/README.md</a></p>
<h2>相关阅读</h2>
<ul><li>运行期能力边界 → <a href="pages/runtime.html">PHPX 运行时集成</a></li><li>类型约束 → <a href="pages/native-types.html">原生类型映射</a></li><li>属性/类约束 → <a href="pages/gen-functions-classes.html">函数与类生成</a></li></ul></div></body></html>

@ -0,0 +1,45 @@
<blog>
# 不支持的 PHP 特性
AOT 编译对 PHP 运行时做了静态化假设,因此**并非所有 PHP 特性都可编译**。遇到编译报错(如 `Not implemented`)时,应据此清单判断是设计限制、有意规则,还是尚未实现。
Sources: [docs/INCOMPATIBLE_PHP_FEATURES.md](docs/INCOMPATIBLE_PHP_FEATURES.md) · [docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md](docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md)
## 不兼容分类
`PHP_INCOMPATIBILITY_CLASSIFICATION.md` 把限制分成四类,避免维护多份清单:
| 类别 | 含义 |
|---|---|
| **Hard Limit** | 架构上无法支持(如依赖运行时动态加载的特性) |
| **Intentional Rule** | 有意为之的设计规则(如可执行代码必须放进 `main()`) |
| **Pending** | 计划支持但尚未实现 |
| **Partial** | 部分支持,有约束条件 |
Sources: [docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md](docs/PHP_INCOMPATIBILITY_CLASSIFICATION.md)
## 常见设计规则
- **游离可执行代码**:文件顶层的可执行语句(循环、函数调用)不被允许,必须放在 `main()` 或函数体内;
- **固定值类型属性不可 `unset`/赋 `null`**:见 [原生类型映射](native-types);
- **显式返回类型**:函数/方法必须声明返回类型(`parseFunctionDecl()` 会 `fatalError`)。
Sources: [docs/QUICKSTART.md](docs/QUICKSTART.md#L191-L203) · [src/Preprocessor.php](src/Preprocessor.php#L497-L536)
## 维护约定
1. 兼容性变化须**同时更新** `INCOMPATIBLE_PHP_FEATURES.md` 与分类文档;
2. 所有语法/语义限制统一链接到当前兼容性清单,避免重复;
3. 功能是否支持以 **PHPT / PHPUnit 回归测试** 为准;
4. 历史研究文档(hhvm/kphp/peachpie 对比等)保留原始结论,不悄然改写为当前状态。
Sources: [docs/README.md](docs/README.md#L30-L35)
## 相关阅读
- 运行期能力边界 → [PHPX 运行时集成](runtime)
- 类型约束 → [原生类型映射](native-types)
- 属性/类约束 → [函数与类生成](gen-functions-classes)
</blog>

@ -0,0 +1,42 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>增量编译缓存</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>增量编译缓存</h1>
<p>TypePHP 的二次编译速度,很大程度上取决于<strong>对象文件缓存是否命中</strong>。编译器对两类源文件做内容指纹缓存:phpx 的 <code>misc</code> 源,以及编译器自己生成的 <code>.cc</code> 单元。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1116-L1292">src/Translator.php</a></p>
<h2>两类缓存</h2>
<table><thead><tr><th>方法</th><th>适用对象</th><th>缓存键组成</th></tr></thead><tbody><tr><td><code>hasMiscObjectFileCache()</code></td><td><code>phpx/src/misc/*.cc</code></td><td>编译命令 + PHP ABI(<code>PHP_VERSION_ID</code> / <code>ZEND_MODULE_API_NO</code> / <code>PHP_ZTS</code> / <code>PHP_INT_SIZE</code> 等)+ phpx 头文件 mtime</td></tr><tr><td><code>hasGeneratedObjectFileCache()</code></td><td>构建目录下的生成 <code>.cc</code></td><td>编译命令 + PHP ABI + <strong>所有生成头文件内容指纹</strong><code>func_decl.h</code> / <code>data_decl.h</code> / 各 <code>_arginfo.h</code></td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1116-L1160">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1207-L1240">src/Translator.php</a></p>
<h2>缓存键构造</h2>
<pre><code>hash(&#x27;sha256&#x27;,
buildCompileFileCommand(source, object) // 可复现的编译命令行
. &quot;\0&quot; . serialize($abi) // PHP ABI 快照
[. &quot;\0&quot; . header . &quot;\0&quot; . hash_file(header)] // 仅生成单元:逐头文件内容
)</code></pre>
<p>键写入 <code>&lt;object&gt;.typephp-cache</code> 元数据文件(<code>getMiscObjectCacheMetadataFile()</code>)。每次 <code>compileFile()</code> 先比较元数据文件中的键,再比较 <code>.o</code> 与源/头的 mtime——<strong>任一不满足即失效并重编译</strong></p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1162-L1187">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1272-L1292">src/Translator.php</a></p>
<h2>设计要点</h2>
<ul><li><strong><code>extension-&lt;target&gt;.cc</code> 故意不进共享头依赖</strong>:它的内容随任意类变化而改变,若纳入指纹会让每个 <code>.cc</code> 都失效,破坏增量构建。它自身作为生成单元拥有独立缓存项。</li><li><strong><code>--force</code> / <code>--profile</code> 强制失效</strong>:这两类参数会跳过全部缓存,确保 misc 文件也被重编(profiler 需 <code>PPROF_ON</code> 宏生效)。</li><li><strong>头文件依赖集排序后参与哈希</strong>,避免路径顺序差异导致误失效。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1242-L1271">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1218-L1226">src/Translator.php</a></p>
<h2>相关阅读</h2>
<ul><li>缓存何时被查询 → <a href="pages/build-pipeline.html">构建流水线</a></li><li>编译命令从何而来 → <a href="pages/backend.html">编译器后端</a></li><li>整体性能分析 → <a href="pages/build-speed.html">构建速度与性能</a></li></ul></div></body></html>

@ -0,0 +1,46 @@
<blog>
# 增量编译缓存
TypePHP 的二次编译速度,很大程度上取决于**对象文件缓存是否命中**。编译器对两类源文件做内容指纹缓存:phpx 的 `misc` 源,以及编译器自己生成的 `.cc` 单元。
Sources: [src/Translator.php](src/Translator.php#L1116-L1292)
## 两类缓存
| 方法 | 适用对象 | 缓存键组成 |
|---|---|---|
| `hasMiscObjectFileCache()` | `phpx/src/misc/*.cc` | 编译命令 + PHP ABI(`PHP_VERSION_ID` / `ZEND_MODULE_API_NO` / `PHP_ZTS` / `PHP_INT_SIZE` 等)+ phpx 头文件 mtime |
| `hasGeneratedObjectFileCache()` | 构建目录下的生成 `.cc` | 编译命令 + PHP ABI + **所有生成头文件内容指纹**(`func_decl.h` / `data_decl.h` / 各 `_arginfo.h`) |
Sources: [src/Translator.php](src/Translator.php#L1116-L1160) · [src/Translator.php](src/Translator.php#L1207-L1240)
## 缓存键构造
```php
hash('sha256',
buildCompileFileCommand(source, object) // 可复现的编译命令行
. "\0" . serialize($abi) // PHP ABI 快照
[. "\0" . header . "\0" . hash_file(header)] // 仅生成单元:逐头文件内容
)
```
键写入 `<object>.typephp-cache` 元数据文件(`getMiscObjectCacheMetadataFile()`)。每次 `compileFile()` 先比较元数据文件中的键,再比较 `.o` 与源/头的 mtime——**任一不满足即失效并重编译**。
Sources: [src/Translator.php](src/Translator.php#L1162-L1187) · [src/Translator.php](src/Translator.php#L1272-L1292)
## 设计要点
- **`extension-<target>.cc` 故意不进共享头依赖**:它的内容随任意类变化而改变,若纳入指纹会让每个 `.cc` 都失效,破坏增量构建。它自身作为生成单元拥有独立缓存项。
- **`--force` / `--profile` 强制失效**:这两类参数会跳过全部缓存,确保 misc 文件也被重编(profiler 需 `PPROF_ON` 宏生效)。
- **头文件依赖集排序后参与哈希**,避免路径顺序差异导致误失效。
Sources: [src/Translator.php](src/Translator.php#L1242-L1271) · [src/Translator.php](src/Translator.php#L1218-L1226)
## 相关阅读
- 缓存何时被查询 → [构建流水线](build-pipeline)
- 编译命令从何而来 → [编译器后端](backend)
- 整体性能分析 → [构建速度与性能](build-speed)
</blog>

@ -0,0 +1,43 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>原生类型映射</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>原生类型映射</h1>
<p>TypePHP 的核心性能来源之一:<strong>PHP 标量直接映射 C++ 原生类型</strong>,而不是一律装箱成 <code>zval</code>。所有可发射的 C++ 类型集中在 <code>Type</code> 类(<code>src/Type.php</code>),以字符串常量形式供生成器引用。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Type.php#L5-L27">src/Type.php</a> · <a href="file:///D:\git\php\aot-compiler\docs\NATIVE_TYPES.md">docs/NATIVE_TYPES.md</a></p>
<h2>类型常量表</h2>
<table><thead><tr><th>TypePHP 类型</th><th>C++ 映射</th><th>说明</th></tr></thead><tbody><tr><td><code>int</code></td><td><code>php::Int</code></td><td>zend_long,8 字节</td></tr><tr><td><code>float</code></td><td><code>php::Float</code></td><td>double,8 字节</td></tr><tr><td><code>bool</code></td><td><code>php::Bool</code></td><td>原生 bool</td></tr><tr><td><code>string</code></td><td><code>php::Str</code></td><td>字符串</td></tr><tr><td><code>array</code></td><td><code>php::Array</code></td><td>PHP 数组</td></tr><tr><td><code>object</code></td><td><code>php::Object</code></td><td>对象</td></tr><tr><td><code>mixed</code> / 动态</td><td><code>php::Var</code></td><td>通用 zval 盒子</td></tr><tr><td><code>resource</code> / <code>stream</code></td><td><code>php::Resource</code> / <code>php::Stream</code></td><td>资源</td></tr><tr><td><code>bigInt</code> / <code>decimal</code> / <code>bigFloat</code></td><td><code>php::BigInt</code> / <code>Decimal</code> / <code>php::BigFloat</code></td><td>高精度(见 <a href="pages/high-precision-types.html">high-precision-types</a></td></tr><tr><td><code>std</code> 容器</td><td><code>php::StdArray/StdVector/StdMap/StdOrderedMap</code></td><td>Std 容器</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Type.php#L7-L24">src/Type.php</a></p>
<h2>固定值类型属性约束</h2>
<p>AOT 要求<strong>对象属性在生命周期内保持声明类型</strong>。对 <code>int/float/bool/string/array</code> 这类固定值类型属性:</p>
<ul><li><strong>不允许</strong> <code>unset()</code> 或赋值 <code>null</code>(等价于把类型改成 null);</li><li>需要可空时显式声明 <code>?int $id = null;</code></li></ul>
<p>对象属性遵循静态类型规则:非空赋值需满足 <code>is-a</code> 继承关系,可用子类对象赋基类属性,反之不行。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\NATIVE_TYPES.md#L19-L51">docs/NATIVE_TYPES.md</a></p>
<h2>使用前置</h2>
<p>高精度/原生类型需在文件头声明 <code>use native_types</code>(并建议 <code>declare(strict_types=1)</code>),例如:</p>
<pre><code>&lt;?php
declare(strict_types=1);
use native_types;
function main(): void { /* ... */ }</code></pre>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\HIGH_PRECISION_TYPES.md#L51-L59">docs/HIGH_PRECISION_TYPES.md</a></p>
<h2>相关阅读</h2>
<ul><li>类型如何被检查与兼容判定 → <a href="pages/type-check.html">类型检查与兼容</a></li><li>高精度类型的运算语义 → <a href="pages/high-precision-types.html">高精度数值类型</a></li><li>类型如何进入 C++ 生成 → <a href="pages/generator.html">生成器总览</a></li></ul></div></body></html>

@ -0,0 +1,55 @@
<blog>
# 原生类型映射
TypePHP 的核心性能来源之一:**PHP 标量直接映射 C++ 原生类型**,而不是一律装箱成 `zval`。所有可发射的 C++ 类型集中在 `Type` 类(`src/Type.php`),以字符串常量形式供生成器引用。
Sources: [src/Type.php](src/Type.php#L5-L27) · [docs/NATIVE_TYPES.md](docs/NATIVE_TYPES.md)
## 类型常量表
| TypePHP 类型 | C++ 映射 | 说明 |
|---|---|---|
| `int` | `php::Int` | zend_long,8 字节 |
| `float` | `php::Float` | double,8 字节 |
| `bool` | `php::Bool` | 原生 bool |
| `string` | `php::Str` | 字符串 |
| `array` | `php::Array` | PHP 数组 |
| `object` | `php::Object` | 对象 |
| `mixed` / 动态 | `php::Var` | 通用 zval 盒子 |
| `resource` / `stream` | `php::Resource` / `php::Stream` | 资源 |
| `bigInt` / `decimal` / `bigFloat` | `php::BigInt` / `Decimal` / `php::BigFloat` | 高精度(见 [high-precision-types](high-precision-types)) |
| `std` 容器 | `php::StdArray/StdVector/StdMap/StdOrderedMap` | Std 容器 |
Sources: [src/Type.php](src/Type.php#L7-L24)
## 固定值类型属性约束
AOT 要求**对象属性在生命周期内保持声明类型**。对 `int/float/bool/string/array` 这类固定值类型属性:
- **不允许** `unset()` 或赋值 `null`(等价于把类型改成 null);
- 需要可空时显式声明 `?int $id = null;`
对象属性遵循静态类型规则:非空赋值需满足 `is-a` 继承关系,可用子类对象赋基类属性,反之不行。
Sources: [docs/NATIVE_TYPES.md](docs/NATIVE_TYPES.md#L19-L51)
## 使用前置
高精度/原生类型需在文件头声明 `use native_types`(并建议 `declare(strict_types=1)`),例如:
```php
<?php
declare(strict_types=1);
use native_types;
function main(): void { /* ... */ }
```
Sources: [docs/HIGH_PRECISION_TYPES.md](docs/HIGH_PRECISION_TYPES.md#L51-L59)
## 相关阅读
- 类型如何被检查与兼容判定 → [类型检查与兼容](type-check)
- 高精度类型的运算语义 → [高精度数值类型](high-precision-types)
- 类型如何进入 C++ 生成 → [生成器总览](generator)
</blog>

@ -0,0 +1,36 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>优化器</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>优化器</h1>
<p>TypePHP 在语义分析阶段运行一组 <strong>SSA(静态单赋值)驱动</strong>的优化器,目标是减少运行时开销与生成的 C++ 体积。优化入口与 SSA 构建分别在 <code>src/Optimizer/</code><code>src/Analysis/SsaBuilder.php</code></p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Optimizer">src/Optimizer/</a> · <a href="file:///D:\git\php\aot-compiler\src\Analysis\SsaBuilder.php">src/Analysis/SsaBuilder.php</a></p>
<h2>优化器清单</h2>
<table><thead><tr><th></th><th>作用</th></tr></thead><tbody><tr><td><code>SsaBuilder</code></td><td>构建 SSA 形式,为下游优化提供定义-使用链</td></tr><tr><td><code>SsaTypeOptimizer</code></td><td>基于 SSA 的类型推导,传播确定类型以去除运行时 <code>php::Var</code> 装箱</td></tr><tr><td><code>SsaPropOptimizer</code></td><td>属性类型的 SSA 推导与去虚化</td></tr><tr><td><code>FuncCallOptimizer</code></td><td>函数调用优化(内联候选、已知函数特化等)</td></tr><tr><td><code>LoopVarOptimizer</code></td><td>循环变量类型/作用域优化</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Optimizer\SsaTypeOptimizer.php">src/Optimizer/SsaTypeOptimizer.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Optimizer\SsaPropOptimizer.php">src/Optimizer/SsaPropOptimizer.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Optimizer\FuncCallOptimizer.php">src/Optimizer/FuncCallOptimizer.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Optimizer\LoopVarOptimizer.php">src/Optimizer/LoopVarOptimizer.php</a></p>
<h2>与优化级别的关系</h2>
<p>命令行 <code>-O &lt;level&gt;</code>(0–3)控制优化强度:级别越高,优化器介入越深、生成代码越紧凑,但编译期耗时也越大;<code>-d/--debug</code> 会关闭优化并加调试符号。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L234-L236">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L272-L274">src/Translator.php</a></p>
<h2>与类型系统的协同</h2>
<p>SSA 类型优化直接消费 <a href="pages/type-check.html">类型检查与兼容</a> 的判定结果——一旦推导确认某变量是固定原生类型,生成器即可发射 <code>php::Int</code> 而非通用 <code>php::Var</code>,省去 zval 装箱。这是「原生类型优先」原则在运行期的落地。</p>
<h2>相关阅读</h2>
<ul><li>类型如何判定 → <a href="pages/type-check.html">类型检查与兼容</a></li><li>编译期属性如何辅助优化 → <a href="pages/compile-time-attributes.html">编译期属性</a></li><li>整体速度瓶颈与改进 → <a href="pages/build-speed.html">构建速度与性能</a></li></ul></div></body></html>

@ -0,0 +1,37 @@
<blog>
# 优化器
TypePHP 在语义分析阶段运行一组 **SSA(静态单赋值)驱动**的优化器,目标是减少运行时开销与生成的 C++ 体积。优化入口与 SSA 构建分别在 `src/Optimizer/``src/Analysis/SsaBuilder.php`
Sources: [src/Optimizer/](src/Optimizer/) · [src/Analysis/SsaBuilder.php](src/Analysis/SsaBuilder.php)
## 优化器清单
| 类 | 作用 |
|---|---|
| `SsaBuilder` | 构建 SSA 形式,为下游优化提供定义-使用链 |
| `SsaTypeOptimizer` | 基于 SSA 的类型推导,传播确定类型以去除运行时 `php::Var` 装箱 |
| `SsaPropOptimizer` | 属性类型的 SSA 推导与去虚化 |
| `FuncCallOptimizer` | 函数调用优化(内联候选、已知函数特化等) |
| `LoopVarOptimizer` | 循环变量类型/作用域优化 |
Sources: [src/Optimizer/SsaTypeOptimizer.php](src/Optimizer/SsaTypeOptimizer.php) · [src/Optimizer/SsaPropOptimizer.php](src/Optimizer/SsaPropOptimizer.php) · [src/Optimizer/FuncCallOptimizer.php](src/Optimizer/FuncCallOptimizer.php) · [src/Optimizer/LoopVarOptimizer.php](src/Optimizer/LoopVarOptimizer.php)
## 与优化级别的关系
命令行 `-O <level>`(0–3)控制优化强度:级别越高,优化器介入越深、生成代码越紧凑,但编译期耗时也越大;`-d/--debug` 会关闭优化并加调试符号。
Sources: [src/Translator.php](src/Translator.php#L234-L236) · [src/Translator.php](src/Translator.php#L272-L274)
## 与类型系统的协同
SSA 类型优化直接消费 [类型检查与兼容](type-check) 的判定结果——一旦推导确认某变量是固定原生类型,生成器即可发射 `php::Int` 而非通用 `php::Var`,省去 zval 装箱。这是「原生类型优先」原则在运行期的落地。
## 相关阅读
- 类型如何判定 → [类型检查与兼容](type-check)
- 编译期属性如何辅助优化 → [编译期属性](compile-time-attributes)
- 整体速度瓶颈与改进 → [构建速度与性能](build-speed)
</blog>

@ -0,0 +1,35 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>项目简介</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>项目简介</h1>
<p><strong>TypePHP</strong> 是 Swoole 团队开发的 <strong>PHP 原生 AOT 编译器</strong>(仓库名 <code>aot-compiler</code>,Composer 包名 <code>swoole/typephp</code>)。它把 PHP 源码在编译期直接翻译成 <strong>C++</strong>,再借助 PHPX 运行时与 Zend 引擎嵌入(<code>embed</code>)能力,链接为 <strong>Windows / Linux / macOS</strong> 平台原生的可执行文件、共享库或 PHP 扩展。</p>
<p>当前版本 <code>v0.4.3</code>(见 <code>src/Translator.php</code> 常量 <code>VERSION</code>)。编译器自身需要 <strong>PHP 8.4+</strong>;生成的产物可面向 <strong>PHP 8.2 ~ 8.5</strong> 的运行时语义。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\composer.json#L2-L4">composer.json</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L61-L62">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\README.md#L1-L6">README.md</a></p>
<h2>为什么不是解释执行</h2>
<p>传统 PHP 每次运行都要经过「词法 → 语法 → 字节码 → Zend 解释」四步。TypePHP 把前两步(以及大量语义分析)在编译期一次性完成,生成的 C++ 再被优化编译器(GCC/Clang/MSVC)进一步内联、逃逸分析与向量化,最终产出的二进制<strong>不再依赖 <code>php</code> 解释器即可运行</strong>——只依赖编译进来的 Zend 引擎(<code>libphp.so</code> / <code>php8ts.lib</code>)。</p>
<blockquote>注意:预览版目前<strong>仅支持 Linux</strong>(建议 Ubuntu 22.04),Windows 编译需在具备 Windows SDK 与 PHP embed 库的环境下进行。</blockquote>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\README.md#L42-L43">README.md</a> · <a href="file:///D:\git\php\aot-compiler\composer.json#L8-L16">composer.json</a></p>
<h2>设计原则</h2>
<ul><li><strong>原生类型优先</strong>:标量(int/float/bool/string)直接映射 C++ 原生类型,避免 zval 装箱开销。</li><li><strong>PHP 语义对齐</strong>:类、接口、trait、枚举、反射、属性(attribute)、生成器(Generator)等尽量保持运行时行为。</li><li><strong>可增量构建</strong>:phpx misc 文件与编译器生成单元都带内容指纹缓存,改动一处不必全量重编译。</li></ul>
<h2>阅读路线</h2>
<ul><li>想了解「PHP 如何一步步变成二进制」→ <a href="pages/pipeline.html">编译流水线</a></li><li>想区分三种产物形态 → <a href="pages/compilation-modes.html">编译模式</a></li><li>想立刻跑通一个例子 → <a href="pages/quickstart.html">快速开始</a></li><li>想深入每个子系统 → 依次阅读 <a href="pages/parser.html">解析器与 AST</a><a href="pages/preprocessor.html">预处理与符号表</a><a href="pages/transform.html">AST 转换</a><a href="pages/generator.html">代码生成</a></li></ul></div></body></html>

@ -0,0 +1,32 @@
<blog>
# 项目简介
**TypePHP** 是 Swoole 团队开发的 **PHP 原生 AOT 编译器**(仓库名 `aot-compiler`,Composer 包名 `swoole/typephp`)。它把 PHP 源码在编译期直接翻译成 **C++**,再借助 PHPX 运行时与 Zend 引擎嵌入(`embed`)能力,链接为 **Windows / Linux / macOS** 平台原生的可执行文件、共享库或 PHP 扩展。
当前版本 `v0.4.3`(见 `src/Translator.php` 常量 `VERSION`)。编译器自身需要 **PHP 8.4+**;生成的产物可面向 **PHP 8.2 ~ 8.5** 的运行时语义。
Sources: [composer.json](composer.json#L2-L4) · [src/Translator.php](src/Translator.php#L61-L62) · [README.md](README.md#L1-L6)
## 为什么不是解释执行
传统 PHP 每次运行都要经过「词法 → 语法 → 字节码 → Zend 解释」四步。TypePHP 把前两步(以及大量语义分析)在编译期一次性完成,生成的 C++ 再被优化编译器(GCC/Clang/MSVC)进一步内联、逃逸分析与向量化,最终产出的二进制**不再依赖 `php` 解释器即可运行**——只依赖编译进来的 Zend 引擎(`libphp.so` / `php8ts.lib`)。
> 注意:预览版目前**仅支持 Linux**(建议 Ubuntu 22.04),Windows 编译需在具备 Windows SDK 与 PHP embed 库的环境下进行。
Sources: [README.md](README.md#L42-L43) · [composer.json](composer.json#L8-L16)
## 设计原则
- **原生类型优先**:标量(int/float/bool/string)直接映射 C++ 原生类型,避免 zval 装箱开销。
- **PHP 语义对齐**:类、接口、trait、枚举、反射、属性(attribute)、生成器(Generator)等尽量保持运行时行为。
- **可增量构建**:phpx misc 文件与编译器生成单元都带内容指纹缓存,改动一处不必全量重编译。
## 阅读路线
- 想了解「PHP 如何一步步变成二进制」→ [编译流水线](pipeline)
- 想区分三种产物形态 → [编译模式](compilation-modes)
- 想立刻跑通一个例子 → [快速开始](quickstart)
- 想深入每个子系统 → 依次阅读 [解析器与 AST](parser) → [预处理与符号表](preprocessor) → [AST 转换](transform) → [代码生成](generator)
</blog>

@ -0,0 +1,45 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>解析器与 AST</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>解析器与 AST</h1>
<p>TypePHP 复用 <strong>nikic/php-parser</strong> 作为前端词法/语法分析器(依赖固定版本 <code>5.6.1</code>)。解析得到的 PHP AST 是后续所有翻译与符号收集的输入。本章说明 AST 从何而来、以何种形态进入编译器。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\composer.json#L10">composer.json</a> · <a href="file:///D:\git\php\aot-compiler\project.yml#L34-L35">project.yml</a></p>
<h2>AST 来源</h2>
<p>解析发生在两个时机:</p>
<ol><li><strong>预处理阶段</strong><code>Preprocessor::prepareFile()</code> 解析每个 PHP 文件,收集类/函数/常量/属性等符号,构建全局符号表(详见 <a href="pages/preprocessor.html">预处理与符号表</a>)。</li><li><strong>转换阶段</strong><code>doConvert()</code> 通过 <code>parseCachedAst()</code> 解析同一文件,得到供代码生成的 AST。</li></ol>
<blockquote><code>parseCachedAst()</code> 做了两层缓存——同一文件的 AST 在进程内复用,避免重复解析;这也是编译性能优化(S1)的关键点之一。</blockquote>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L116">src/Preprocessor.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2459">src/Translator.php</a></p>
<h2>表达式翻译:Parser Trait 集合</h2>
<p>编译器主体 <code>CompilerBase</code> 并未把「PHP 表达式 → C++」的逻辑塞进一个巨型方法,而是拆成 <code>src/Parser/</code> 下的 <strong>21 个 trait</strong>,每个 trait 负责一类语法结构:</p>
<table><thead><tr><th>Trait</th><th>负责的 PHP 结构</th></tr></thead><tbody><tr><td><code>BinaryOpTrait</code></td><td><code>+ - * /</code> 等二元运算</td></tr><tr><td><code>AssignOpTrait</code></td><td><code>+=</code><code>??=</code> 等复合赋值</td></tr><tr><td><code>UnaryExpressionTrait</code></td><td><code>!</code><code>-</code><code>++</code> 等一元运算</td></tr><tr><td><code>FunctionCallTrait</code></td><td>普通函数调用</td></tr><tr><td><code>MethodCallTrait</code> / <code>UniversalMethodCall</code></td><td>对象/静态方法调用</td></tr><tr><td><code>PropertyAccessTrait</code></td><td>属性读写</td></tr><tr><td><code>ArrayExpressionTrait</code></td><td>数组字面量</td></tr><tr><td><code>ForeachTrait</code> / <code>LoopControlTrait</code></td><td><code>foreach</code> / <code>for</code> / <code>while</code></td></tr><tr><td><code>SelectionExpressionTrait</code> / <code>SwitchTrait</code></td><td><code>if</code> / <code>switch</code></td></tr><tr><td><code>ConditionalControlTrait</code></td><td><code>?:</code> / <code>match</code></td></tr><tr><td><code>NullsafeAccessTrait</code></td><td><code>?-&gt;</code></td></tr><tr><td><code>ClassConstantFetchTrait</code></td><td>类常量读取</td></tr><tr><td><code>TypeConversionTrait</code> / <code>TypeDetectionTrait</code></td><td>类型转换与 <code>instanceof</code></td></tr><tr><td><code>ConstantExpressionTrait</code></td><td>编译期常量折叠</td></tr><tr><td><code>ExceptionControlFlowTrait</code></td><td><code>try/catch/throw</code></td></tr><tr><td><code>StdContainerTrait</code></td><td><code>std</code> 容器操作</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\CompilerBase.php#L43-L63">src/CompilerBase.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Parser">src/Parser/</a></p>
<h2>访客链</h2>
<p><code>doConvert()</code> 在遍历 AST 前先挂载四个 <code>NodeVisitor</code></p>
<pre><code>$traverser-&gt;addVisitor(new NameResolver(...)); // 全限定名解析
$traverser-&gt;addVisitor(new Visitor(...)); // Transform lowering(见 transform)
$traverser-&gt;addVisitor(new ConstantExpressionValidationVisitor(...));
$traverser-&gt;addVisitor(new RuntimeAttributeFactoryLowering(...));</code></pre>
<p><code>NameResolver</code> 把短名解析为全限定名(但不替换节点),让后续生成能拿到准确的命名空间路径。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2460-L2467">src/Translator.php</a></p>
<h2>相关阅读</h2>
<ul><li>符号如何被收集 → <a href="pages/preprocessor.html">预处理与符号表</a></li><li>遍历中做了哪些改写 → <a href="pages/transform.html">AST 转换</a></li><li>解析结果的消费方 → <a href="pages/generator.html">代码生成</a></li></ul></div></body></html>

@ -0,0 +1,66 @@
<blog>
# 解析器与 AST
TypePHP 复用 **nikic/php-parser** 作为前端词法/语法分析器(依赖固定版本 `5.6.1`)。解析得到的 PHP AST 是后续所有翻译与符号收集的输入。本章说明 AST 从何而来、以何种形态进入编译器。
Sources: [composer.json](composer.json#L10) · [project.yml](project.yml#L34-L35)
## AST 来源
解析发生在两个时机:
1. **预处理阶段**:`Preprocessor::prepareFile()` 解析每个 PHP 文件,收集类/函数/常量/属性等符号,构建全局符号表(详见 [预处理与符号表](preprocessor))。
2. **转换阶段**:`doConvert()` 通过 `parseCachedAst()` 解析同一文件,得到供代码生成的 AST。
> `parseCachedAst()` 做了两层缓存——同一文件的 AST 在进程内复用,避免重复解析;这也是编译性能优化(S1)的关键点之一。
Sources: [src/Preprocessor.php](src/Preprocessor.php#L116) · [src/Translator.php](src/Translator.php#L2459)
## 表达式翻译:Parser Trait 集合
编译器主体 `CompilerBase` 并未把「PHP 表达式 → C++」的逻辑塞进一个巨型方法,而是拆成 `src/Parser/` 下的 **21 个 trait**,每个 trait 负责一类语法结构:
| Trait | 负责的 PHP 结构 |
|---|---|
| `BinaryOpTrait` | `+ - * /` 等二元运算 |
| `AssignOpTrait` | `+=`、`??=` 等复合赋值 |
| `UnaryExpressionTrait` | `!`、`-`、`++` 等一元运算 |
| `FunctionCallTrait` | 普通函数调用 |
| `MethodCallTrait` / `UniversalMethodCall` | 对象/静态方法调用 |
| `PropertyAccessTrait` | 属性读写 |
| `ArrayExpressionTrait` | 数组字面量 |
| `ForeachTrait` / `LoopControlTrait` | `foreach` / `for` / `while` |
| `SelectionExpressionTrait` / `SwitchTrait` | `if` / `switch` |
| `ConditionalControlTrait` | `?:` / `match` |
| `NullsafeAccessTrait` | `?->` |
| `ClassConstantFetchTrait` | 类常量读取 |
| `TypeConversionTrait` / `TypeDetectionTrait` | 类型转换与 `instanceof` |
| `ConstantExpressionTrait` | 编译期常量折叠 |
| `ExceptionControlFlowTrait` | `try/catch/throw` |
| `StdContainerTrait` | `std` 容器操作 |
Sources: [src/CompilerBase.php](src/CompilerBase.php#L43-L63) · [src/Parser/](src/Parser/)
## 访客链
`doConvert()` 在遍历 AST 前先挂载四个 `NodeVisitor`
```php
$traverser->addVisitor(new NameResolver(...)); // 全限定名解析
$traverser->addVisitor(new Visitor(...)); // Transform lowering(见 transform)
$traverser->addVisitor(new ConstantExpressionValidationVisitor(...));
$traverser->addVisitor(new RuntimeAttributeFactoryLowering(...));
```
`NameResolver` 把短名解析为全限定名(但不替换节点),让后续生成能拿到准确的命名空间路径。
Sources: [src/Translator.php](src/Translator.php#L2460-L2467)
## 相关阅读
- 符号如何被收集 → [预处理与符号表](preprocessor)
- 遍历中做了哪些改写 → [AST 转换](transform)
- 解析结果的消费方 → [代码生成](generator)
</blog>

@ -0,0 +1,48 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>编译流水线</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>编译流水线</h1>
<p>TypePHP 的核心入口是 <code>bin/tpc.php</code>,它通过 <code>main()</code> 串起整条流水线:<code>prepare → convert → compile → build → run</code>。本章用一张图和一个阶段表把全链路讲清楚。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\bin\tpc.php#L1-L9">bin/tpc.php</a> · <a href="file:///D:\git\php\aot-compiler\src\compiler.php#L4-L46">src/compiler.php</a></p>
<h2>总体流程</h2>
<pre class="mermaid">flowchart TD
A[&quot;main(argc, argv)&quot;] --&gt; B[&quot;prepare(argv)\n扫描 PHP 文件 + 预处理&quot;]
B --&gt; C[&quot;convert(files)\nPHP AST → C++ 源文件&quot;]
C --&gt; D{&quot;--dry?&quot;}
D -- &quot;&quot; --&gt; Z[&quot;仅输出 C++,结束&quot;]
D -- &quot;&quot; --&gt; E[&quot;compile(sourceFiles)\nC++ → .o / .obj&quot;]
E --&gt; F[&quot;build(objectFiles)\n链接为二进制 / 库 / 扩展&quot;]
F --&gt; G{&quot;--run?&quot;}
G -- &quot;&quot; --&gt; H[&quot;run(binary)\n立即执行&quot;]
G -- &quot;&quot; --&gt; Z</pre>
<p><code>.prof</code> 后缀会被切换为 <code>profileAnalyze</code> 模式,调用 <code>pprof --web</code> 做性能剖析。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\compiler.php#L14-L18">src/compiler.php</a> · <a href="file:///D:\git\php\aot-compiler\src\compiler.php#L26-L45">src/compiler.php</a></p>
<h2>各阶段职责</h2>
<table><thead><tr><th>阶段</th><th>方法</th><th>关键工作</th></tr></thead><tbody><tr><td>准备</td><td><code>Translator::prepare()</code></td><td>扫描所有 PHP 文件、解析命令行、构建符号表(类/函数/常量/属性)</td></tr><tr><td>转换</td><td><code>Translator::convert()</code><code>convertFile()</code><code>doConvert()</code></td><td>对每个 PHP 文件解析 AST,跑 Transform 访客,生成 <code>.cc</code><code>_arginfo.h</code></td></tr><tr><td>编译</td><td><code>Translator::compile()</code></td><td>追加 phpx misc 源文件、准备 PCH、单进程或 <code>pcntl</code> 并行编译</td></tr><tr><td>构建</td><td><code>Translator::build()</code></td><td>调用原生链接器把目标文件 + 资源文件链接成最终产物</td></tr><tr><td>运行</td><td><code>Translator::run()</code></td><td>仅在 <code>bin</code> 模式且带 <code>--run</code> 时执行产物</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L504-L527">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1419-L1447">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1649-L1680">src/Translator.php</a></p>
<h2>转换阶段的两趟扫描</h2>
<p><code>doConvert()</code> 内部做了两件事(<a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2455-L2537">src/Translator.php</a>):</p>
<ol><li><strong>AST 遍历</strong>:用 <code>NodeTraverser</code> 串联 <code>NameResolver</code><code>Transform\Visitor</code><code>ConstantExpressionValidationVisitor</code><code>RuntimeAttributeFactoryLowering</code> 四个访客,做名称解析、编译期属性 lowering、常量表达式校验。</li><li><strong>语句分发</strong>:遍历顶层语句,按 <code>Stmt_Class</code> / <code>Stmt_Function</code> / <code>Stmt_Const</code> 等类型调用对应的 <code>parseXxx()</code>,最后为每个类、接口、函数生成 C++ 包装代码(<code>genClassWrapper</code> / <code>genFunctionWrapper</code>)。</li></ol>
<blockquote>前端的解析/符号表逻辑在 <a href="pages/preprocessor.html">preprocessor</a><a href="pages/transform.html">transform</a> 章节展开;C++ 生成细节在 <a href="pages/generator.html">generator</a></blockquote>
<h2>关键文件</h2>
<ul><li><code>src/compiler.php</code> — 顶层 <code>main()</code><code>.prof</code> 剖析模式。</li><li><code>src/Translator.php</code> — 流水线编排 + 构建/链接/缓存。</li><li><code>src/Preprocessor.php</code> / <code>src/CompilerBase.php</code> — 解析、符号收集、C++ 代码发射(占 7000+ 行,是编译器主体)。</li></ul></div></body></html>

@ -0,0 +1,55 @@
<blog>
# 编译流水线
TypePHP 的核心入口是 `bin/tpc.php`,它通过 `main()` 串起整条流水线:`prepare → convert → compile → build → run`。本章用一张图和一个阶段表把全链路讲清楚。
Sources: [bin/tpc.php](bin/tpc.php#L1-L9) · [src/compiler.php](src/compiler.php#L4-L46)
## 总体流程
```mermaid
flowchart TD
A["main(argc, argv)"] --> B["prepare(argv)\n扫描 PHP 文件 + 预处理"]
B --> C["convert(files)\nPHP AST → C++ 源文件"]
C --> D{"--dry?"}
D -- "是" --> Z["仅输出 C++,结束"]
D -- "否" --> E["compile(sourceFiles)\nC++ → .o / .obj"]
E --> F["build(objectFiles)\n链接为二进制 / 库 / 扩展"]
F --> G{"--run?"}
G -- "是" --> H["run(binary)\n立即执行"]
G -- "否" --> Z
```
`.prof` 后缀会被切换为 `profileAnalyze` 模式,调用 `pprof --web` 做性能剖析。
Sources: [src/compiler.php](src/compiler.php#L14-L18) · [src/compiler.php](src/compiler.php#L26-L45)
## 各阶段职责
| 阶段 | 方法 | 关键工作 |
|---|---|---|
| 准备 | `Translator::prepare()` | 扫描所有 PHP 文件、解析命令行、构建符号表(类/函数/常量/属性) |
| 转换 | `Translator::convert()``convertFile()``doConvert()` | 对每个 PHP 文件解析 AST,跑 Transform 访客,生成 `.cc``_arginfo.h` |
| 编译 | `Translator::compile()` | 追加 phpx misc 源文件、准备 PCH、单进程或 `pcntl` 并行编译 |
| 构建 | `Translator::build()` | 调用原生链接器把目标文件 + 资源文件链接成最终产物 |
| 运行 | `Translator::run()` | 仅在 `bin` 模式且带 `--run` 时执行产物 |
Sources: [src/Translator.php](src/Translator.php#L504-L527) · [src/Translator.php](src/Translator.php#L1419-L1447) · [src/Translator.php](src/Translator.php#L1649-L1680)
## 转换阶段的两趟扫描
`doConvert()` 内部做了两件事([src/Translator.php](src/Translator.php#L2455-L2537)):
1. **AST 遍历**:用 `NodeTraverser` 串联 `NameResolver`、`Transform\Visitor`、`ConstantExpressionValidationVisitor`、`RuntimeAttributeFactoryLowering` 四个访客,做名称解析、编译期属性 lowering、常量表达式校验。
2. **语句分发**:遍历顶层语句,按 `Stmt_Class` / `Stmt_Function` / `Stmt_Const` 等类型调用对应的 `parseXxx()`,最后为每个类、接口、函数生成 C++ 包装代码(`genClassWrapper` / `genFunctionWrapper`)。
> 前端的解析/符号表逻辑在 [preprocessor](preprocessor) 与 [transform](transform) 章节展开;C++ 生成细节在 [generator](generator)。
## 关键文件
- `src/compiler.php` — 顶层 `main()``.prof` 剖析模式。
- `src/Translator.php` — 流水线编排 + 构建/链接/缓存。
- `src/Preprocessor.php` / `src/CompilerBase.php` — 解析、符号收集、C++ 代码发射(占 7000+ 行,是编译器主体)。
</blog>

@ -0,0 +1,41 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>跨平台支持</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>跨平台支持</h1>
<p>TypePHP 目标 <strong>Windows / Linux / macOS</strong> 三大桌面平台,通过 <code>Platform</code> 抽象隔离系统差异(编译器名、目标扩展名、PHP embed 库查找等)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L164-L190">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Platform">src/Platform/</a></p>
<h2>平台抽象</h2>
<p><code>PlatformFactory::create()</code> 按 OS 返回具体 <code>Platform</code> 对象(Windows / Linux / macOS)。<code>detectPlatform()</code><code>Translator</code> 构造时调用,得到平台名 + 后端名(如 <code>Windows + MSVC</code>)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L164-L190">src/Translator.php</a></p>
<h2>各平台要点</h2>
<table><thead><tr><th>平台</th><th>默认后端</th><th>链接产物</th><th>特殊点</th></tr></thead><tbody><tr><td>Linux</td><td>GCC/Clang</td><td>ELF 可执行 / <code>.so</code></td><td>推荐 Ubuntu 22.04,需 <code>libphp.so</code></td></tr><tr><td>macOS</td><td>Clang</td><td>Mach-O / <code>.dylib</code></td><td><code>strip</code> 可选</td></tr><tr><td>Windows</td><td>MSVC</td><td>PE <code>.exe</code> / <code>.dll</code></td><td>需 Windows SDK + <code>php8ts.lib</code>;资源文件编译</td></tr></tbody></table>
<p>Windows 分支会额外 <code>detectPhpLibs()</code> 探测 embed 与 core 的 <code>.lib</code>,并把 <code>isZts</code>、SDK 路径注入 <code>Windows</code> 对象。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L170-L181">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\README.md#L42-L43">README.md</a></p>
<h2>Windows 资源文件</h2>
<p><code>bin</code> 模式在 Windows 下编译 <code>.rc</code>(图标、版本信息、UAC 等,由 <code>project.yml</code><code>resource</code> 配置),生成的 <code>.res</code><code>build()</code> 链接时并入。资源文件生成见 <code>Generator/ResourceFileGenerator</code><code>Build/ResourceCompilationTrait</code></p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\project.yml#L10-L24">project.yml</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1653-L1659">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Build\ResourceCompilationTrait.php">src/Build/ResourceCompilationTrait.php</a></p>
<h2>libphp.so 自动安装</h2>
<p>Linux 缺 <code>libphp.so</code> 时,<code>tpc.php</code> 可交互式下载 PHP 源码并自动构建,详见 <code>docs/LIBPHP_INSTALLER.md</code>;安装器逻辑在 <code>src/Installer/</code></p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\README.md#L27">README.md</a> · <a href="file:///D:\git\php\aot-compiler\src\Installer">src/Installer/</a></p>
<h2>相关阅读</h2>
<ul><li>后端命令如何生成 → <a href="pages/backend.html">编译器后端</a></li><li>链接与资源并入 → <a href="pages/build-pipeline.html">构建流水线</a></li><li>二进制打包(UPX/strip) → README 的 Release packaging 段</li></ul></div></body></html>

@ -0,0 +1,45 @@
<blog>
# 跨平台支持
TypePHP 目标 **Windows / Linux / macOS** 三大桌面平台,通过 `Platform` 抽象隔离系统差异(编译器名、目标扩展名、PHP embed 库查找等)。
Sources: [src/Translator.php](src/Translator.php#L164-L190) · [src/Platform/](src/Platform/)
## 平台抽象
`PlatformFactory::create()` 按 OS 返回具体 `Platform` 对象(Windows / Linux / macOS)。`detectPlatform()` 在 `Translator` 构造时调用,得到平台名 + 后端名(如 `Windows + MSVC`)。
Sources: [src/Translator.php](src/Translator.php#L164-L190)
## 各平台要点
| 平台 | 默认后端 | 链接产物 | 特殊点 |
|---|---|---|---|
| Linux | GCC/Clang | ELF 可执行 / `.so` | 推荐 Ubuntu 22.04,需 `libphp.so` |
| macOS | Clang | Mach-O / `.dylib` | `strip` 可选 |
| Windows | MSVC | PE `.exe` / `.dll` | 需 Windows SDK + `php8ts.lib`;资源文件编译 |
Windows 分支会额外 `detectPhpLibs()` 探测 embed 与 core 的 `.lib`,并把 `isZts`、SDK 路径注入 `Windows` 对象。
Sources: [src/Translator.php](src/Translator.php#L170-L181) · [README.md](README.md#L42-L43)
## Windows 资源文件
`bin` 模式在 Windows 下编译 `.rc`(图标、版本信息、UAC 等,由 `project.yml``resource` 配置),生成的 `.res``build()` 链接时并入。资源文件生成见 `Generator/ResourceFileGenerator``Build/ResourceCompilationTrait`
Sources: [project.yml](project.yml#L10-L24) · [src/Translator.php](src/Translator.php#L1653-L1659) · [src/Build/ResourceCompilationTrait.php](src/Build/ResourceCompilationTrait.php)
## libphp.so 自动安装
Linux 缺 `libphp.so` 时,`tpc.php` 可交互式下载 PHP 源码并自动构建,详见 `docs/LIBPHP_INSTALLER.md`;安装器逻辑在 `src/Installer/`
Sources: [README.md](README.md#L27) · [src/Installer/](src/Installer/)
## 相关阅读
- 后端命令如何生成 → [编译器后端](backend)
- 链接与资源并入 → [构建流水线](build-pipeline)
- 二进制打包(UPX/strip) → README 的 Release packaging 段
</blog>

@ -0,0 +1,45 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>预处理与符号表</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>预处理与符号表</h1>
<p>在生成任何 C++ 之前,编译器必须先<strong>通读所有 PHP 文件、收集符号、解决依赖顺序</strong>。这一步由 <code>Preprocessor</code>(父类)与 <code>CompilerBase</code>(主体)承担,产物是贯穿全程的全局符号表。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php">src/Preprocessor.php</a> · <a href="file:///D:\git\php\aot-compiler\src\CompilerBase.php#L97">src/CompilerBase.php</a></p>
<h2>文件排序</h2>
<p><code>prepare()</code> 扫描出 PHP 文件后,用 <code>getSortedFiles()</code> 做拓扑排序,保证被依赖的文件先被处理。<code>marcj/topsort</code> 负责依赖排序(Composer 依赖之一)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L43-L75">src/Preprocessor.php</a> · <a href="file:///D:\git\php\aot-compiler\composer.json#L13">composer.json</a></p>
<h2>符号收集</h2>
<p>每个文件经 <code>prepareFile()</code> 解析后,编译器为每个语言构造建立 <code>Entity</code> 定义对象:</p>
<table><thead><tr><th>实体</th><th>定义类</th><th>收集方法</th></tr></thead><tbody><tr><td>函数</td><td><code>FunctionDef</code></td><td><code>prepareFunction()</code> / <code>parseFunctionDecl()</code></td></tr><tr><td>类 / trait / 枚举</td><td><code>ClassDef</code></td><td><code>prepareClass()</code></td></tr><tr><td>接口</td><td><code>InterfaceDef</code></td><td><code>prepareClass()</code></td></tr><tr><td>属性</td><td><code>PropertyDef</code></td><td><code>addClassProperty()</code></td></tr><tr><td>常量</td><td><code>ConstantDef</code></td><td><code>parseClassLikeConstant()</code> / <code>parseConstDef()</code></td></tr><tr><td>方法</td><td><code>MethodDef</code></td><td><code>parseFunctionDecl()</code></td></tr></tbody></table>
<p><code>main()</code> 有专门校验:参数必须形如 <code>(int $argc, array $argv)</code> 且返回 <code>void</code>(仅 <code>bin</code> 模式强制要求)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L489-L589">src/Preprocessor.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L685">src/Preprocessor.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L1124">src/Preprocessor.php</a></p>
<h2>符号仓库</h2>
<p>收集到的符号统一存放在 <code>Symbol\SymbolRepository</code><code>$this-&gt;symbols</code>),按 functions / classes / interfaces 分别索引。<code>genExtension()</code>、各类 <code>genXxx</code> 方法都从仓库读取,保证「声明一次、多处生成」。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Symbol\SymbolRepository.php">src/Symbol/SymbolRepository.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L863-L880">src/Translator.php</a></p>
<h2>属性默认值计划</h2>
<p>标量/数组属性默认值会被预处理成 <code>ArrayInitPlan</code>,避免 C++ 初始化时重复求值;数组常量也会被标记 special 处理(见 <a href="pages/generator.html">generator</a> 的属性初始化)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L974-L1002">src/Preprocessor.php</a></p>
<h2>数值与常量</h2>
<p><code>loadInternalConstants()</code> 把 PHP 内置常量(排除用户态、<code>TypePHP</code> 扩展组)载入编译期常量表,供常量折叠使用。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L139-L159">src/Translator.php</a></p>
<h2>相关阅读</h2>
<ul><li>解析器如何产出 AST → <a href="pages/parser.html">解析器与 AST</a></li><li>遍历中如何改写节点 → <a href="pages/transform.html">AST 转换</a></li><li>符号如何变成 C++ → <a href="pages/generator.html">代码生成</a></li></ul></div></body></html>

@ -0,0 +1,56 @@
<blog>
# 预处理与符号表
在生成任何 C++ 之前,编译器必须先**通读所有 PHP 文件、收集符号、解决依赖顺序**。这一步由 `Preprocessor`(父类)与 `CompilerBase`(主体)承担,产物是贯穿全程的全局符号表。
Sources: [src/Preprocessor.php](src/Preprocessor.php) · [src/CompilerBase.php](src/CompilerBase.php#L97)
## 文件排序
`prepare()` 扫描出 PHP 文件后,用 `getSortedFiles()` 做拓扑排序,保证被依赖的文件先被处理。`marcj/topsort` 负责依赖排序(Composer 依赖之一)。
Sources: [src/Preprocessor.php](src/Preprocessor.php#L43-L75) · [composer.json](composer.json#L13)
## 符号收集
每个文件经 `prepareFile()` 解析后,编译器为每个语言构造建立 `Entity` 定义对象:
| 实体 | 定义类 | 收集方法 |
|---|---|---|
| 函数 | `FunctionDef` | `prepareFunction()` / `parseFunctionDecl()` |
| 类 / trait / 枚举 | `ClassDef` | `prepareClass()` |
| 接口 | `InterfaceDef` | `prepareClass()` |
| 属性 | `PropertyDef` | `addClassProperty()` |
| 常量 | `ConstantDef` | `parseClassLikeConstant()` / `parseConstDef()` |
| 方法 | `MethodDef` | `parseFunctionDecl()` |
`main()` 有专门校验:参数必须形如 `(int $argc, array $argv)` 且返回 `void`(仅 `bin` 模式强制要求)。
Sources: [src/Preprocessor.php](src/Preprocessor.php#L489-L589) · [src/Preprocessor.php](src/Preprocessor.php#L685) · [src/Preprocessor.php](src/Preprocessor.php#L1124)
## 符号仓库
收集到的符号统一存放在 `Symbol\SymbolRepository`(`$this->symbols`),按 functions / classes / interfaces 分别索引。`genExtension()`、各类 `genXxx` 方法都从仓库读取,保证「声明一次、多处生成」。
Sources: [src/Symbol/SymbolRepository.php](src/Symbol/SymbolRepository.php) · [src/Translator.php](src/Translator.php#L863-L880)
## 属性默认值计划
标量/数组属性默认值会被预处理成 `ArrayInitPlan`,避免 C++ 初始化时重复求值;数组常量也会被标记 special 处理(见 [generator](generator) 的属性初始化)。
Sources: [src/Preprocessor.php](src/Preprocessor.php#L974-L1002)
## 数值与常量
`loadInternalConstants()` 把 PHP 内置常量(排除用户态、`TypePHP` 扩展组)载入编译期常量表,供常量折叠使用。
Sources: [src/Translator.php](src/Translator.php#L139-L159)
## 相关阅读
- 解析器如何产出 AST → [解析器与 AST](parser)
- 遍历中如何改写节点 → [AST 转换](transform)
- 符号如何变成 C++ → [代码生成](generator)
</blog>

@ -0,0 +1,53 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>快速开始</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>快速开始</h1>
<p>本章给出一个最小可编译示例,以及常用命令。完整的用户手册在独立的 <code>aot/docs</code> 仓库,这里聚焦「能在 5 分钟内跑起来」。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\QUICKSTART.md">docs/QUICKSTART.md</a> · <a href="file:///D:\git\php\aot-compiler\README.md#L7-L26">README.md</a></p>
<h2>安装依赖</h2>
<pre><code>composer install # 安装 nikic/php-parser, phpx 等
php bin/tpc.php --help # 验证安装</code></pre>
<p>Linux 还需高精度数学库:<code>sudo apt install libgmp-dev libmpfr-dev libmpdec-dev</code></p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\README.md#L29-L40">README.md</a></p>
<h2>写一个最小程序</h2>
<p>可执行代码必须放在 <code>main()</code> 里,类/函数/常量可在全局定义:</p>
<pre><code>&lt;?php
class Calculator {
public function add(int $a, int $b): int { return $a + $b; }
}
function main(): void {
$c = new Calculator();
echo $c-&gt;add(5, 3) . PHP_EOL;
}</code></pre>
<p>编译并运行:</p>
<pre><code>php bin/tpc.php main.php -o myapp
./myapp # 输出 8</code></pre>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\QUICKSTART.md#L166-L189">docs/QUICKSTART.md</a> · <a href="file:///D:\git\php\aot-compiler\docs\QUICKSTART.md#L99-L127">docs/QUICKSTART.md</a></p>
<h2>常用命令</h2>
<table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td><code>php bin/tpc.php hello.php -O2</code></td><td>指定优化级别编译</td></tr><tr><td><code>php bin/tpc.php src/ -o app -j4</code></td><td>目录编译 + 4 路并行</td></tr><tr><td><code>php bin/tpc.php src/ --dry --build-dir /tmp/b</code></td><td>只生成 C++ 不编译(调试用)</td></tr><tr><td><code>php bin/tpc.php app.php -r -O2 -- --flag1 v1</code></td><td>编译后直接运行,并传参</td></tr><tr><td><code>php bin/tpc.php project.yml</code></td><td><code>project.yml</code> 配置编译</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L225-L258">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\docs\QUICKSTART.md#L271-L280">docs/QUICKSTART.md</a></p>
<h2>调试中间产物</h2>
<p><code>--dry</code> 会把生成的 <code>.cc</code> / <code>_arginfo.h</code> 写入 <code>--build-dir</code> 指定的目录,便于检查翻译结果;<code>--no-progress</code> 则逐文件打印进度。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L30-L36">src/Translator.php</a></p>
<h2>下一步</h2>
<ul><li>了解产物形态差异 → <a href="pages/compilation-modes.html">编译模式</a></li><li>理解 <code>project.yml</code> 字段 → 见仓库根 <code>project.yml</code><code>docs/COMPILER_CLI.md</code></li><li>排查「Not implemented」→ <a href="pages/incompatible-features.html">不支持的 PHP 特性</a></li></ul></div></body></html>

@ -0,0 +1,68 @@
<blog>
# 快速开始
本章给出一个最小可编译示例,以及常用命令。完整的用户手册在独立的 `aot/docs` 仓库,这里聚焦「能在 5 分钟内跑起来」。
Sources: [docs/QUICKSTART.md](docs/QUICKSTART.md) · [README.md](README.md#L7-L26)
## 安装依赖
```bash
composer install # 安装 nikic/php-parser, phpx 等
php bin/tpc.php --help # 验证安装
```
Linux 还需高精度数学库:`sudo apt install libgmp-dev libmpfr-dev libmpdec-dev`。
Sources: [README.md](README.md#L29-L40)
## 写一个最小程序
可执行代码必须放在 `main()` 里,类/函数/常量可在全局定义:
```php
<?php
class Calculator {
public function add(int $a, int $b): int { return $a + $b; }
}
function main(): void {
$c = new Calculator();
echo $c->add(5, 3) . PHP_EOL;
}
```
编译并运行:
```bash
php bin/tpc.php main.php -o myapp
./myapp # 输出 8
```
Sources: [docs/QUICKSTART.md](docs/QUICKSTART.md#L166-L189) · [docs/QUICKSTART.md](docs/QUICKSTART.md#L99-L127)
## 常用命令
| 命令 | 作用 |
|---|---|
| `php bin/tpc.php hello.php -O2` | 指定优化级别编译 |
| `php bin/tpc.php src/ -o app -j4` | 目录编译 + 4 路并行 |
| `php bin/tpc.php src/ --dry --build-dir /tmp/b` | 只生成 C++ 不编译(调试用) |
| `php bin/tpc.php app.php -r -O2 -- --flag1 v1` | 编译后直接运行,并传参 |
| `php bin/tpc.php project.yml` | 用 `project.yml` 配置编译 |
Sources: [src/Translator.php](src/Translator.php#L225-L258) · [docs/QUICKSTART.md](docs/QUICKSTART.md#L271-L280)
## 调试中间产物
`--dry` 会把生成的 `.cc` / `_arginfo.h` 写入 `--build-dir` 指定的目录,便于检查翻译结果;`--no-progress` 则逐文件打印进度。
Sources: [src/Translator.php](src/Translator.php#L30-L36)
## 下一步
- 了解产物形态差异 → [编译模式](compilation-modes)
- 理解 `project.yml` 字段 → 见仓库根 `project.yml``docs/COMPILER_CLI.md`
- 排查「Not implemented」→ [不支持的 PHP 特性](incompatible-features)
</blog>

@ -0,0 +1,44 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>PHPX 运行时集成</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>PHPX 运行时集成</h1>
<p>TypePHP 不是从零实现 PHP 语义,而是站在 <strong>PHPX</strong>(Swoole 的 C++ PHP 绑定库)与 <strong>Zend 引擎 embed</strong> 之上。生成的 C++ 大量调用 <code>phpx.h</code> 提供的 <code>php::</code> 命名空间 API,最终把 Zend 引擎链接进产物。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L79-L89">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\README.md#L44-L61">README.md</a></p>
<h2>全局头文件</h2>
<p><code>Translator::$globalHeaders</code> 列出每个编译单元默认包含的头:</p>
<pre><code>[&#x27;cstring&#x27;, &#x27;phpx.h&#x27;, &#x27;phpx_helper.h&#x27;, &#x27;phpx_big_int.h&#x27;,
&#x27;phpx_big_float.h&#x27;, &#x27;phpx_decimal.h&#x27;, &#x27;typephp_helper.h&#x27;,
&#x27;typephp_fiber_generator.h&#x27;, &#x27;phpx_std.h&#x27;]</code></pre>
<p>高精度类型(<code>BigInt</code>/<code>BigFloat</code>/<code>Decimal</code>)的头正是此处引入;<code>typephp_*</code> 是 TypePHP 自带的扩展头(Fiber/Generator、helper)。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L79-L89">src/Translator.php</a></p>
<h2>运行时依赖源</h2>
<p><code>compile()</code> 会追加 phpx 的 <code>src/misc/</code> 源文件(C++ 运行时实现),以及 embed 模式下的 <code>typephp_main.cc</code>、bin 模式下的 cli 标题源。这些 misc 源受 <a href="pages/incremental-cache.html">增量编译缓存</a> 的 ABI 指纹保护。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L1423-L1434">src/Translator.php</a></p>
<h2>关键运行时能力</h2>
<ul><li><strong>Fiber / Generator</strong><code>typephp_fiber_generator.cc/.h</code> 提供协程与生成器支持,详见 <code>docs/YIELD_GENERATOR.md</code></li><li><strong>混合 C++/PHP</strong>:可在工程中直接写 C++ 并与 PHP 互调,见 <code>docs/MIXED_CPP_PHP.md</code></li><li><strong>反射</strong><code>typephp_install_reflection_attribute_handlers()</code> 在 MINIT 安装反射/属性处理器;</li><li><strong>类继承</strong>:PHP 类到 C++ 的映射与 <code>extends</code> 处理见 <code>docs/CLASS_INHERITANCE.md</code></li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L887-L890">src/Translator.php</a> · <a href="file:///D:\git\php\aot-compiler\docs\YIELD_GENERATOR.md">docs/YIELD_GENERATOR.md</a> · <a href="file:///D:\git\php\aot-compiler\docs\MIXED_CPP_PHP.md">docs/MIXED_CPP_PHP.md</a> · <a href="file:///D:\git\php\aot-compiler\docs\CLASS_INHERITANCE.md">docs/CLASS_INHERITANCE.md</a></p>
<h2>动态链接库</h2>
<p>运行时要求 <code>libphp.so</code>(Linux)/ <code>php8ts.lib</code>(Windows)与 <code>libphpx.so</code> 就位;缺库时可用 <code>docs/LIBPHP_INSTALLER.md</code> 自动构建。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\README.md#L57-L70">README.md</a> · <a href="file:///D:\git\php\aot-compiler\docs\LIBPHP_INSTALLER.md">docs/LIBPHP_INSTALLER.md</a></p>
<h2>相关阅读</h2>
<ul><li>产物如何被链接 → <a href="pages/build-pipeline.html">构建流水线</a></li><li>跨平台库差异 → <a href="pages/platform.html">跨平台支持</a></li><li>哪些 PHP 特性尚不支持 → <a href="pages/incompatible-features.html">不支持的 PHP 特性</a></li></ul></div></body></html>

@ -0,0 +1,50 @@
<blog>
# PHPX 运行时集成
TypePHP 不是从零实现 PHP 语义,而是站在 **PHPX**(Swoole 的 C++ PHP 绑定库)与 **Zend 引擎 embed** 之上。生成的 C++ 大量调用 `phpx.h` 提供的 `php::` 命名空间 API,最终把 Zend 引擎链接进产物。
Sources: [src/Translator.php](src/Translator.php#L79-L89) · [README.md](README.md#L44-L61)
## 全局头文件
`Translator::$globalHeaders` 列出每个编译单元默认包含的头:
```php
['cstring', 'phpx.h', 'phpx_helper.h', 'phpx_big_int.h',
'phpx_big_float.h', 'phpx_decimal.h', 'typephp_helper.h',
'typephp_fiber_generator.h', 'phpx_std.h']
```
高精度类型(`BigInt`/`BigFloat`/`Decimal`)的头正是此处引入;`typephp_*` 是 TypePHP 自带的扩展头(Fiber/Generator、helper)。
Sources: [src/Translator.php](src/Translator.php#L79-L89)
## 运行时依赖源
`compile()` 会追加 phpx 的 `src/misc/` 源文件(C++ 运行时实现),以及 embed 模式下的 `typephp_main.cc`、bin 模式下的 cli 标题源。这些 misc 源受 [增量编译缓存](incremental-cache) 的 ABI 指纹保护。
Sources: [src/Translator.php](src/Translator.php#L1423-L1434)
## 关键运行时能力
- **Fiber / Generator**:`typephp_fiber_generator.cc/.h` 提供协程与生成器支持,详见 `docs/YIELD_GENERATOR.md`
- **混合 C++/PHP**:可在工程中直接写 C++ 并与 PHP 互调,见 `docs/MIXED_CPP_PHP.md`
- **反射**:`typephp_install_reflection_attribute_handlers()` 在 MINIT 安装反射/属性处理器;
- **类继承**:PHP 类到 C++ 的映射与 `extends` 处理见 `docs/CLASS_INHERITANCE.md`
Sources: [src/Translator.php](src/Translator.php#L887-L890) · [docs/YIELD_GENERATOR.md](docs/YIELD_GENERATOR.md) · [docs/MIXED_CPP_PHP.md](docs/MIXED_CPP_PHP.md) · [docs/CLASS_INHERITANCE.md](docs/CLASS_INHERITANCE.md)
## 动态链接库
运行时要求 `libphp.so`(Linux)/ `php8ts.lib`(Windows)与 `libphpx.so` 就位;缺库时可用 `docs/LIBPHP_INSTALLER.md` 自动构建。
Sources: [README.md](README.md#L57-L70) · [docs/LIBPHP_INSTALLER.md](docs/LIBPHP_INSTALLER.md)
## 相关阅读
- 产物如何被链接 → [构建流水线](build-pipeline)
- 跨平台库差异 → [跨平台支持](platform)
- 哪些 PHP 特性尚不支持 → [不支持的 PHP 特性](incompatible-features)
</blog>

@ -0,0 +1,37 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>AST 转换(Lowering)</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>AST 转换(Lowering)</h1>
<p><code>doConvert()</code> 遍历 AST 时,<code>src/Transform/Visitor.php</code> 作为 <code>NodeVisitor</code> 挂载在遍历链上,对节点做<strong>编译期改写(lowering)</strong>——把 PHP 8.x 的高层语法(属性钩子、构造函数提升、getter、编译期属性等)降级为编译器能直接翻译的普通结构。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Transform\Visitor.php#L19-L36">src/Transform/Visitor.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2462">src/Translator.php</a></p>
<h2>两个遍历钩子</h2>
<p><code>Visitor</code> 实现 <code>enterNode()</code><code>leaveNode()</code> 两阶段处理:</p>
<ul><li><strong>enterNode</strong>(进入节点前):校验 + 即时 lowering。依次调用 <code>CompileTimeAttribute::validateNode</code><code>FunctionAttributeLowering::lower</code><code>GetterLowering::validateTarget</code><code>PropertyMethodLowering::validateTarget</code><code>ConstructorLowering::validateTarget</code></li><li><strong>leaveNode</strong>(离开节点后):对函数/闭包做 <code>ParameterValidationLowering</code>;对类/属性做 <code>PropertyHookLowering</code><code>GetterLowering</code><code>PropertyMethodLowering</code>,把钩子属性、getter、提升属性改写成方法或标记。</li></ul>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Transform\Visitor.php#L28-L80">src/Transform/Visitor.php</a></p>
<h2>Lowering 算子清单</h2>
<table><thead><tr><th></th><th>作用</th></tr></thead><tbody><tr><td><code>CompileTimeAttribute</code> / <code>CompileTimeAttributeRegistry</code></td><td>识别并校验编译期属性(如 <code>Any</code><code>MustUse</code></td></tr><tr><td><code>FunctionAttributeLowering</code></td><td>处理函数上的编译期属性</td></tr><tr><td><code>ConstructorLowering</code></td><td>处理构造函数提升参数</td></tr><tr><td><code>GetterLowering</code></td><td><code>get</code> 钩子属性 → getter 方法</td></tr><tr><td><code>PropertyHookLowering</code></td><td><code>get</code>/<code>set</code> 钩子属性 → 访问器方法</td></tr><tr><td><code>PropertyMethodLowering</code></td><td>属性 ↔ 方法互转</td></tr><tr><td><code>ParameterValidationLowering</code></td><td>参数类型校验,拒绝箭头函数误用</td></tr><tr><td><code>PrinterLowering</code> / <code>ValidateLowering</code></td><td>打印/校验相关降级</td></tr><tr><td><code>ArrayableLowering</code> / <code>NotEmptyLowering</code> / <code>NotNullLowering</code></td><td>特定表达式降级</td></tr><tr><td><code>RuntimeAttributeFactoryLowering</code></td><td>运行时工厂属性(遍历链上独立挂载)</td></tr></tbody></table>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Transform">src/Transform/</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2467">src/Translator.php</a></p>
<h2>顺序意义</h2>
<p>Lowering 必须早于 C++ 生成:例如属性钩子若不先降级成方法,后续 <code>genClassWrapper</code> 就无法生成对应的 C++ 访问逻辑。它处在 <a href="pages/parser.html">解析器与 AST</a> 之后、<a href="pages/generator.html">代码生成</a> 之前。</p>
<h2>相关阅读</h2>
<ul><li>编译期属性语义 → <a href="pages/compile-time-attributes.html">编译期属性</a></li><li>降级后如何发射 C++ → <a href="pages/generator.html">生成器总览</a></li><li>常量表达式校验 → <a href="pages/type-check.html">类型检查与兼容</a></li></ul></div></body></html>

@ -0,0 +1,45 @@
<blog>
# AST 转换(Lowering)
`doConvert()` 遍历 AST 时,`src/Transform/Visitor.php` 作为 `NodeVisitor` 挂载在遍历链上,对节点做**编译期改写(lowering)**——把 PHP 8.x 的高层语法(属性钩子、构造函数提升、getter、编译期属性等)降级为编译器能直接翻译的普通结构。
Sources: [src/Transform/Visitor.php](src/Transform/Visitor.php#L19-L36) · [src/Translator.php](src/Translator.php#L2462)
## 两个遍历钩子
`Visitor` 实现 `enterNode()``leaveNode()` 两阶段处理:
- **enterNode**(进入节点前):校验 + 即时 lowering。依次调用 `CompileTimeAttribute::validateNode`、`FunctionAttributeLowering::lower`、`GetterLowering::validateTarget`、`PropertyMethodLowering::validateTarget`、`ConstructorLowering::validateTarget`。
- **leaveNode**(离开节点后):对函数/闭包做 `ParameterValidationLowering`;对类/属性做 `PropertyHookLowering`、`GetterLowering`、`PropertyMethodLowering`,把钩子属性、getter、提升属性改写成方法或标记。
Sources: [src/Transform/Visitor.php](src/Transform/Visitor.php#L28-L80)
## Lowering 算子清单
| 类 | 作用 |
|---|---|
| `CompileTimeAttribute` / `CompileTimeAttributeRegistry` | 识别并校验编译期属性(如 `Any`、`MustUse`) |
| `FunctionAttributeLowering` | 处理函数上的编译期属性 |
| `ConstructorLowering` | 处理构造函数提升参数 |
| `GetterLowering` | `get` 钩子属性 → getter 方法 |
| `PropertyHookLowering` | `get`/`set` 钩子属性 → 访问器方法 |
| `PropertyMethodLowering` | 属性 ↔ 方法互转 |
| `ParameterValidationLowering` | 参数类型校验,拒绝箭头函数误用 |
| `PrinterLowering` / `ValidateLowering` | 打印/校验相关降级 |
| `ArrayableLowering` / `NotEmptyLowering` / `NotNullLowering` | 特定表达式降级 |
| `RuntimeAttributeFactoryLowering` | 运行时工厂属性(遍历链上独立挂载) |
Sources: [src/Transform/](src/Transform/) · [src/Translator.php](src/Translator.php#L2467)
## 顺序意义
Lowering 必须早于 C++ 生成:例如属性钩子若不先降级成方法,后续 `genClassWrapper` 就无法生成对应的 C++ 访问逻辑。它处在 [解析器与 AST](parser) 之后、[代码生成](generator) 之前。
## 相关阅读
- 编译期属性语义 → [编译期属性](compile-time-attributes)
- 降级后如何发射 C++ → [生成器总览](generator)
- 常量表达式校验 → [类型检查与兼容](type-check)
</blog>

@ -0,0 +1,38 @@
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>类型检查与兼容</title>
<script src="../script/mermaid.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){if(window.mermaid)mermaid.initialize({startOnLoad:true});});</script>
<style>
:root{--bg:#0f1117;--fg:#e6e6e6;--muted:#9aa0aa;--accent:#6ea8fe;--card:#171a21;--border:#262b36;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.65}
.wrap{max-width:900px;margin:0 auto;padding:32px 24px 80px}
h1,h2,h3{color:#fff;line-height:1.3;margin-top:1.6em}
h1{border-bottom:1px solid var(--border);padding-bottom:.3em}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{background:#0b0d12;padding:.15em .4em;border-radius:4px;font-size:.9em}
pre{background:#0b0d12;border:1px solid var(--border);border-radius:8px;padding:14px;overflow:auto}
pre code{background:none;padding:0}
table{border-collapse:collapse;width:100%;margin:1em 0}
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left}
th{background:var(--card)}
blockquote{border-left:3px solid var(--accent);margin:1em 0;padding:.2em 1em;color:var(--muted);background:var(--card)}
hr{border:none;border-top:1px solid var(--border);margin:2em 0}
</style></head>
<body><div class="wrap"><h1>类型检查与兼容</h1>
<p>类型检查负责回答两个问题:<strong>「这个值在此处是什么类型?」</strong><strong>「把 A 赋给期望 B 的槽位是否合法?」</strong>。TypePHP 的类型逻辑集中在 <code>src/TypeSystem/</code> 两个 trait 中,并在遍历/生成阶段被大量调用。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\TypeSystem">src/TypeSystem/</a></p>
<h2>两个核心 Trait</h2>
<table><thead><tr><th>Trait</th><th>职责</th></tr></thead><tbody><tr><td><code>NativeTypeCompatibilityTrait</code></td><td>判断原生类型之间的赋值/参数兼容(含子类→基类、可空、协变等规则)</td></tr><tr><td><code>CompositeTypeCheckerTrait</code></td><td>处理联合类型、<code>mixed</code>、交集等复合类型的成员判定</td></tr></tbody></table>
<p>它们被 <code>CompilerBase</code> 通过 <code>use</code> 引入(与 Parser 各 trait 并列),为 <code>parseParams</code><code>genArgumentDeclaration</code> 等生成逻辑提供判定依据。</p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\CompilerBase.php#L79-L80">src/CompilerBase.php</a></p>
<h2>检查发生的时机</h2>
<ol><li><strong>参数解析</strong><code>parseParameterType()</code> 决定形参的 C++ 类型;不兼容的默认/传入会在此报错。</li><li><strong>返回值</strong>:函数/方法返回类型在 <code>parseFunctionDecl()</code> 校验,未声明返回类型会 <code>fatalError</code>(要求显式声明)。</li><li><strong>属性赋值</strong>:固定值类型属性赋值受 <a href="pages/native-types.html">native-types</a> 的静态类型规则约束。</li><li><strong>常量表达式</strong><code>ConstantExpressionValidationVisitor</code> 在遍历链上校验编译期常量表达式的合法性。</li></ol>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L328-L368">src/Preprocessor.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Preprocessor.php#L497-L536">src/Preprocessor.php</a> · <a href="file:///D:\git\php\aot-compiler\src\Translator.php#L2463-L2466">src/Translator.php</a></p>
<h2>类型丢失与恢复</h2>
<p>从数组/函数返回值取出对象时会丢失类型上下文,需用编译期函数 <code>objval($var, &#x27;ClassName&#x27;)</code> 显式恢复类信息;<code>refval()</code> / <code>any()</code> 等见 <a href="pages/compile-time-attributes.html">编译期属性</a></p>
<p>Sources: <a href="file:///D:\git\php\aot-compiler\docs\NATIVE_TYPES.md#L55-L70">docs/NATIVE_TYPES.md</a></p>
<h2>相关阅读</h2>
<ul><li>类型如何映射到 C++ → <a href="pages/native-types.html">原生类型映射</a></li><li>编译期属性如何参与类型推断 → <a href="pages/compile-time-attributes.html">编译期属性</a></li><li>生成时如何消费类型 → <a href="pages/generator.html">生成器总览</a></li></ul></div></body></html>

@ -0,0 +1,41 @@
<blog>
# 类型检查与兼容
类型检查负责回答两个问题:**「这个值在此处是什么类型?」** 与 **「把 A 赋给期望 B 的槽位是否合法?」**。TypePHP 的类型逻辑集中在 `src/TypeSystem/` 两个 trait 中,并在遍历/生成阶段被大量调用。
Sources: [src/TypeSystem/](src/TypeSystem/)
## 两个核心 Trait
| Trait | 职责 |
|---|---|
| `NativeTypeCompatibilityTrait` | 判断原生类型之间的赋值/参数兼容(含子类→基类、可空、协变等规则) |
| `CompositeTypeCheckerTrait` | 处理联合类型、`mixed`、交集等复合类型的成员判定 |
它们被 `CompilerBase` 通过 `use` 引入(与 Parser 各 trait 并列),为 `parseParams`、`genArgumentDeclaration` 等生成逻辑提供判定依据。
Sources: [src/CompilerBase.php](src/CompilerBase.php#L79-L80)
## 检查发生的时机
1. **参数解析**:`parseParameterType()` 决定形参的 C++ 类型;不兼容的默认/传入会在此报错。
2. **返回值**:函数/方法返回类型在 `parseFunctionDecl()` 校验,未声明返回类型会 `fatalError`(要求显式声明)。
3. **属性赋值**:固定值类型属性赋值受 [native-types](native-types) 的静态类型规则约束。
4. **常量表达式**:`ConstantExpressionValidationVisitor` 在遍历链上校验编译期常量表达式的合法性。
Sources: [src/Preprocessor.php](src/Preprocessor.php#L328-L368) · [src/Preprocessor.php](src/Preprocessor.php#L497-L536) · [src/Translator.php](src/Translator.php#L2463-L2466)
## 类型丢失与恢复
从数组/函数返回值取出对象时会丢失类型上下文,需用编译期函数 `objval($var, 'ClassName')` 显式恢复类信息;`refval()` / `any()` 等见 [编译期属性](compile-time-attributes)。
Sources: [docs/NATIVE_TYPES.md](docs/NATIVE_TYPES.md#L55-L70)
## 相关阅读
- 类型如何映射到 C++ → [原生类型映射](native-types)
- 编译期属性如何参与类型推断 → [编译期属性](compile-time-attributes)
- 生成时如何消费类型 → [生成器总览](generator)
</blog>

File diff suppressed because one or more lines are too long

1
.gitignore vendored

@ -29,3 +29,4 @@ tests/**/*.log
tests/**/*.out
tests/**/*.php
tests/**/*.sh
/.git-backup-20260803/

@ -0,0 +1,76 @@
# 2026-08-03 工作日志
## TypePHP 编译性能分析(docs/COMPILATION_PERFORMANCE_ANALYSIS.md)
**任务**:分析编译实现、提升编译速度、评估 mago 替代 AST 解析。
**编译管线**(4 阶段):
1. prepare:扫描+parse(#1)+符号表+拓扑排序(SourcePipelineTrait::prepare → Preprocessor::prepareFile)
2. convert:parse(#2,重复)+NodeTraverser+生成 C++(Translator::doConvert)
3. compile:PCH + pcntl 并行(Windows 串行)
4. build:链接
**实测瓶颈**(src/ 139 文件,php-parser 5.6.1):
- 解析吞吐仅 3.18 MB/s,单遍 parse+resolve ≈ 599ms
- **每个 PHP 文件解析两次**(prepare + convert)→ 纯 PHP 解析 ~1.2s 起步
- 生成 .cc 无增量缓存(只有 phpx misc 有),writeFile 无内容比对 → mtime 必变触发全量重编
- prepare/convert 串行;Windows 无 pcntl 串行编译
- AST serialize 体积膨胀 28.8 倍(47.6MB/1.65MB),unserialize 260ms vs parse+resolve 599ms(快 2.3 倍)
**提速方案**(按 ROI):
- S1(P0) prepare/convert 合并单次解析(AST 内存/磁盘缓存)
- S2(P0) 生成 .cc 增量对象缓存 + writeFile 内容比对(key 须含公共声明头)
- S3(P0) PCH/misc 指纹轻量化(mtime 先行,内容 hash 兜底)
- S4(P1) 解析阶段 pcntl 并行
- S5(P1) Windows 并行(Msvc /MP 或 proc_open)
- S7(P2) 合并多次 NodeFinder 全树扫描
**mago 评估结论:不建议替换**。mago 是 Rust 的 PHP 工具链(lint/format/analyze),AST 是 Rust 结构 + CST 模型,与 TypePHP 深度耦合的 php-parser API(296 处引用/75 文件,依赖 NodeTraverser/NameResolver/ConstExprEvaluator/PrettyPrinter/attribute 机制)不兼容。JSON 桥接或 FFI 的成本会吃掉解析速度优势。合理定位:前置语法预检或未来原生前端路线参考。
**产物**:docs/COMPILATION_PERFORMANCE_ANALYSIS.md(完整报告)、.workbuddy/bench_ast.php(可复现基准脚本)
## 实施 S1+S2(下午追加)
**S1 合并两次解析**:
- `CompilerBase` 新增 `parseCachedAst()`(按 `$this->file` 缓存原始 AST)+ `cloneAst()`(CloningVisitor 深拷贝)
- `prepareFile()``doConvert()` 都改用它 → 每个文件只 parse 一次
- 关键约束:`RuntimeAttributeFactoryLowering`/`Visitor` 是有状态 visitor(会向 AST 追加节点),**同一棵 AST 不能二次遍历**,必须返回深拷贝
**S2 增量缓存**:
- `writeFile()` 内容比对:相同不覆盖,返回 bool(mtime 稳定,防头文件抖动)
- 新增 `hasGeneratedObjectFileCache()`:key = sha256(编译命令 + PHP ABI + 生成的 func_decl.h/data_decl.h/_arginfo.h 内容 hash)
- `isGeneratedSourceFile()`:buildDir 前缀 + .cc 扩展判断
- `compileFile()` 对生成的 .cc 走缓存;`save()`/`genExtension()` 仅实际写入时 format
- 注意:extension-<target>.cc **不应**列入 header 依赖(内容随任何类变化,会让所有 .cc 全量失效)
**实测**(302 文件合成项目,dry,3 次均值):
- 全量:基线 6011ms → 改动后 5629ms(-6.4%)
- 增量(不清理 build,第 3 次):5023ms(-16.4% 相对基线冷构建)
- dry 不含 C++ 编译,S2 完整收益需在 build 场景验证
**环境/验证要点**:
- 本机完整编译需要 `PHP_HOME=D:/git/php/tpc_v1095_windows_x86_64`(含 php8embed.lib、SDK/lib/php8ts.lib、phpx/)+ 复制 `tpc_v1095/phpx.dll``phpx/build/phpx.dll`
- `tpc_v1095/tpc.exe` 是编译自 `D:\workspace\compiler` 的二进制,本机无法运行(内嵌路径不符)
- typephp-think(554 文件)补丁后 prepare 通过,convert 阶段 `Cannot re-assign` 是仓库源码类型系统比 v1095 正式版严格(`$str` array→str),非本次改动问题
- **git 事故**:git stash 失败损坏 refs(HEAD→speed_build 指向 a92fc0df 对象缺失)。已恢复:分支指向 18decac(可用 commit),onepiece-doudizhu 示例内容在暂存区完好。备份在 `.git-backup-20260803/`,确认后删除
## git 恢复与提交(下午追加 2)
- **git stash 事故根因确认**:stash 失败不仅丢 refs,还导致 onepiece-doudizhu-win32 全部 19 个文件的 blob 对象缺失(index 引用了不存在的对象)。工作区文件完好。
- **恢复方法**:`git reset -- <dir>` 取消暂存 → `git add <dir>`(从工作区重建 blob)→ commit。已恢复为 `449afc1 add onepiece-ddz game example`(4226 行,与原提交同名)。
- **S1/S2 已提交**:`ac7813d perf: cache AST across prepare/convert and add generated object cache`(3 文件 +206/-14)。
- **最终 git fsck = 0 missing,仓库健康**
- 遗留:`.git-backup-20260803/`(18MB,git 事故备份,确认后删)、`.workbuddy/onepiece-backup/`(文件级备份,可删)。
## code-wiki 文档生成(2026-08-04)
- 为 aot-compiler 生成了中文 AI Wiki:22 页,7 个章节(概览 / 前端解析 / 类型系统 / 代码生成 / 后端构建 / 优化性能 / 运行时边界)。
- **重要修正**:code-wiki skill 的文档写 `<workspace>/.agents/wiki/`,但 `scripts/render.py` 实际读取 `<workspace>/.workbuddy/wiki/`(`wiki_dir = os.path.join(workspace, ".workbuddy", "wiki")`)。写 catalog.xml / pages 必须放 `.workbuddy/wiki/`,否则 render 报 "catalog.xml not found"。
- 产物:`D:\git\php\aot-compiler\.workbuddy\wiki/index.html`(左侧目录导航 + 右侧 iframe,离线 Mermaid)。render 命令:`python <skill>/scripts/render.py render <workspace> --lang zh`。
## 环境结论(重要)
- **本机 MSVC 工具链不完整**:cl.exe + C++ 标准库头存在(`C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/VC/Tools/MSVC/14.44.35207/include/cstring`),但 **Windows SDK(Windows Kits 目录)完全缺失** → 无法完成真实 C++ 编译链接,只能验证到 dry 模式(prepare+convert+生成 .cc)。
- `tpc_v1095_windows_x86_64` 是完整 PHP 构建环境:php8embed.lib、SDK/lib/php8ts.lib、phpx/(含 lib/phpx.lib);phpx.dll 需复制到 `phpx/build/phpx.dll` 才能通过校验。
- 完整编译需在装有 Windows SDK 的机器执行(用户原编译机)。

@ -0,0 +1,154 @@
# 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 找到核心库——这是用户环境的常规布局操作。

@ -0,0 +1,224 @@
# TypePHP 编译实现分析、提速方案与 mago 替代评估
> 生成日期:2026-08-03
> 适用范围:`swoole/typephp` v0.4.3(本仓库 `D:\git\php\aot-compiler`
> 说明:所有耗时数字均来自本仓库实际基准测试(`src/` 下 139 个 PHP 文件、约 1.65 MB 源码,PHP 8.4.16 CLI;端到端对比用 302 文件合成项目,Windows + MSVC)
---
## 0. 实施状态(2026-08-03 更新)
**S1(合并两次解析)与 S2(writeFile 内容比对 + 生成 .cc 增量缓存)已实施并验证。**
### 已落地的改动
| 文件 | 改动 |
|---|---|
| `src/CompilerBase.php` | 新增 `parseCachedAst()` / `cloneAst()`(AST 内存缓存 + CloningVisitor 深拷贝);`writeFile()` 增加内容比对(内容相同不覆盖,返回 bool 保持 mtime 稳定) |
| `src/Preprocessor.php` | `prepareFile()` 改用 `parseCachedAst()`,消除与 convert 的重复 parse |
| `src/Translator.php` | `doConvert()` 改用 `parseCachedAst()`;新增 `hasGeneratedObjectFileCache()` / `getGeneratedObjectCacheKey()` / `getGeneratedHeaderDependencies()` / `isGeneratedSourceFile()`;`compileFile()` 对生成的 .cc 走对象缓存;`save()`/`genExtension()` 仅在实际写入时 format |
### 实测收益(302 文件合成项目,dry 模式,3 次取均值)
| 场景 | 基线 | 改动后 | 提升 |
|---|---|---|---|
| 全量构建(冷) | 6011 ms | 5629 ms | **-6.4%** |
| 增量构建(第 3 次,不清理 build) | — | 5023 ms | **-16.4%**(相对基线冷构建) |
说明:dry 模式不包含 C++ 编译,S2 对象缓存的更大收益(跳过 g++/cl 编译)需在完整 build 场景体现;dry 耗时中固定开销(CLI 启动、YAML、符号表)占比高,稀释了 S1 的解析节省。
### 验证记录
- `parseCachedAst`:同一文件 prepare+convert 各调用一次,第二次命中缓存且返回深拷贝(不共享节点、无 resolvedName 残留污染)✓
- `writeFile` 内容比对:相同内容跳过写入,mtime 保持不变 ✓
- `isGeneratedSourceFile`:buildDir 前缀 + .cc 扩展名判断,与 `getCppFile` 路径一致 ✓
- PHPUnit:Entity 目录 85 测试、FileScanner 等 110 测试通过(1 个 Windows 路径分隔符既有失败与本次无关)
- `typephp-think`(554 文件):补丁后 prepare 通过,convert 阶段因「仓库源码类型系统比 v1095 正式版严格」(`Cannot re-assign`)受阻,属源码版本差异,非本次改动问题
---
## 1. 编译是如何实现的(管线剖析)
### 1.1 总体流程
入口为 `bin/tpc.php``src/compiler.php``main()``src/Translator.php`(继承 `Preprocessor``CompilerBase`)。编译是一条四阶段流水线:
```
┌────────────────────────────────────────────────────────────────────────┐
│ ① prepare() 扫描+解析+建符号表+依赖拓扑排序 │
│ src/Build/SourcePipelineTrait.php::prepare() │
│ → 每文件: Preprocessor::prepareFile() │
│ parser->parse() ←── AST 解析 #1
│ NodeTraverser(NameResolver + Visitor + 常量校验 + AttributeLower) │
│ 收集 symbolDeclInFile / symbolCallInFile / classMap / funcMap │
│ → getSortedFiles(): marcj/topsort 按文件间符号依赖拓扑排序 │
├────────────────────────────────────────────────────────────────────────┤
│ ② convert() 每文件: AST → C++ 源码 │
│ src/Build/SourcePipelineTrait.php::convert() │
│ → Translator::convertFile() → doConvert() │
│ loadFile() 读盘(第二次) │
│ parser->parse() ←── AST 解析 #2(同一文件解析两次!) │
│ NodeTraverser 再次遍历(与 prepare 相同的 4 个 visitor) │
│ 逐节点翻译生成 C++(CompilerBase 4000 行核心逻辑) │
│ → 生成 func_decl.h / data_decl.h / extension-<target>.cc │
├────────────────────────────────────────────────────────────────────────┤
│ ③ compile() C++ → .o(外部编译器) │
│ Translator::compile() │
│ → phpx misc 文件 + 生成的 .cc │
│ → PCH 预编译头(PrecompiledHeaderManager,内容指纹缓存) │
│ → Linux/macOS: pcntl fork 并行(默认 4 jobs,大文件优先调度) │
│ → Windows: 串行(supportsPcntlParallelCompile() = false) │
│ → phpx misc 有 .o 缓存(hasMiscObjectFileCache,命令+ABI 指纹) │
├────────────────────────────────────────────────────────────────────────┤
│ ④ build() 链接为可执行文件 / 扩展 / 库 │
│ Translator::build() → NativeBuilder::link() │
└────────────────────────────────────────────────────────────────────────┘
```
### 1.2 关键实现位置
| 职责 | 文件 |
|---|---|
| 四阶段编排 | `src/Build/SourcePipelineTrait.php`(prepare/convert)、`src/Translator.php`(compile/build) |
| PHP→C++ 逐节点翻译 | `src/CompilerBase.php`(4133 行,核心) |
| 扫描/符号表/依赖排序 | `src/Preprocessor.php` |
| 编译器抽象 | `src/Backend/`(CompilerBackend → GccLikeBackend → Gcc/Clang、Msvc) |
| 平台抽象 | `src/Platform/`(Linux/Macos/Windows) |
| 并行编译调度 | `src/Build/NativeBuilder.php::dispatchParallel()` + `SourceCompileQueue` |
| PCH 管理 | `src/Build/PrecompiledHeaderManager.php` |
| 生成物落盘 | `CompilerBase::writeFile()`(无内容比对,直接覆盖) |
### 1.3 当前已具备的优化
1. **PCH 预编译头**:`phpx.h` 等 8 个公共头打成 `.gch`,带 24 位 sha256 内容指纹缓存(`PrecompiledHeaderManager`)。
2. **phpx misc 对象缓存**:`phpx/src/misc/*.cc` 的 `.o` 用「编译命令 + PHP ABI」指纹缓存,避免每次全量重编 PHPX 运行时。
3. **编译并行**:Linux/macOS 用 pcntl fork 并行(`-j`,默认 4),且按「大文件优先占满槽位 + 小文件快速通道」调度。
4. **编译期常量展开、字面量字符串表**、`--dry` 只生成 C++ 等。
---
## 2. 性能瓶颈实测
### 2.1 基准方法
`nikic/php-parser 5.6.1`(与仓库 `composer.json` 一致)对 `src/` 全部 139 个 PHP 文件(约 1.65 MB)做多轮测量:
```
parse (nikic/php-parser) ×3 1455.5 ms → 单遍 ≈ 485 ms(3.18 MB/s)
parse + NameResolver ×3 1797.0 ms → 单遍 ≈ 599 ms
parse+resolve+serialize (1x) 701.1 ms
unserialize ×3 779.8 ms → 单遍 ≈ 260 ms
parse+resolve+prettyPrint ×3 2196.0 ms
serialized AST 体积 47.6 MB(源码的 28.8 倍)
```
### 2.2 结论性瓶颈清单(按影响排序)
| # | 瓶颈 | 证据 | 影响 |
|---|---|---|---|
| B1 | **每个 PHP 文件被解析两次** | `prepareFile()`(Preprocessor.php:130)与 `doConvert()`(Translator.php:2325)各自 `parser->parse()` 一次,且各自跑一遍完整 NodeTraverser | PHP 侧最大浪费:139 文件规模下纯解析+resolve ≈ 2×599ms ≈ **1.2s 起步**,大项目线性放大 |
| B2 | **生成的 .cc 无增量缓存** | `compileFile()` 只对 phpx misc 走 `hasMiscObjectFileCache`;用户代码生成的 `.cc` 每次全量重编 | 改 1 个 PHP 文件也要重编全部 .cc(C++ 编译是耗时的绝对大头) |
| B3 | **生成文件无内容比对** | `CompilerBase::writeFile()` 直接 `file_put_contents` 覆盖,mtime 必然变化 | 即使内容相同也触发下游重新依赖判断/重编,破坏增量可能性 |
| B4 | **prepare/convert 阶段完全串行** | 两阶段都是单进程 `foreach` | 多核只用于 compile;PHP 侧解析/翻译占用的核数为 1 |
| B5 | **Windows 无并行编译** | `Windows::supportsPcntlParallelCompile() = false`,直接走 `compileSourceFile()` 串行 | Windows 大项目编译时间 = 串行全量 |
| B6 | **PCH/缓存指纹每次全量计算** | `PrecompiledHeaderManager::buildFingerprint()` 每次构建都递归遍历 phpx 头文件并逐个 hash(实测 131 个头文件 ≈ 42ms,PHP 侧还有 misc 缓存同样的遍历) | 每次构建的固定开销,虽不大但在累积 |
| B7 | 转换阶段反复 `getType()` 字符串比较、NodeFinder 多次全树扫描(如 `findSymbolUsing`、`canOptimizeMultiReturn`) | CompilerBase/Preprocessor 中多次独立 `findInstanceOf` | 每文件多次 O(N) 全树扫描,可合并为一次遍历 |
### 2.3 一个反直觉的事实
**AST 序列化缓存是划算的**:unserialize 单遍 260ms vs parse+resolve 599ms(快 2.3 倍),即使加上 serialize 成本(首轮 ~700ms/139 文件),二次构建纯 PHP 解析阶段也能从 ~1.2s 降到 ~260ms。但注意序列化体积膨胀 28.8 倍(47.6MB/1.65MB),对磁盘 I/O 有压力——更优做法是**合并两阶段解析**(见方案 S1)。
---
## 3. 提速方案(按性价比排序)
### S1(P0)prepare/convert 合并为单次解析 —— 省一半 PHP 解析时间
- **问题**:同一文件 `prepare``convert` 各 parse+traverse 一次,逻辑完全重复(visitor 列表一致)。
- **做法**:让 `prepareFile()` 把 parse 好的 `$stmts`(NameResolver 等已跑完)挂到文件缓存(内存 `array<string, array<Node>>`,或按 `filemtime+内容hash` 落盘 `build/cache/ast/`),`doConvert()` 优先取缓存;取不到(如 `--force`、跨进程)才重新 parse。
- **收益**:139 文件规模 PHP 解析从 ~1.2s → ~0.6s;大项目直接减半。
- **风险**:低。`convert` 依赖的 `resolvedName` 等 attribute 由 NameResolver 在 traverse 时写入,缓存的是 traverse 之后的 AST,语义不变。需注意 `ConstantExpressionValidationVisitor` 的报错回调在 prepare 是 warning、convert 是 fatal,缓存后错误在 prepare 阶段就能暴露,行为只会更早更一致。
### S2(P0)生成 .cc 的增量对象缓存 —— 编译期最大收益
- **做法**:把 `hasMiscObjectFileCache()` 的机制推广到所有生成的 `.cc`:缓存 key = 编译命令 + PHP ABI + **生成物内容 hash(或源 PHP hash + 编译器版本)**。内容未变 → 跳过 g++/clang。
- **配套**:`writeFile()` 加「内容比对,相同则不落盘」逻辑(解决 B3),保证 mtime 稳定。
- **收益**:改 1 个文件后二次构建,C++ 编译从全量变为仅差异文件;这是肉眼可感知的最大提升。
- **风险**:中。生成 .cc 的内容由 `genFunctionDeclarations`/`genDataDeclarations` 等公共头影响——若公共头变了,所有 .cc 的 `#include` 语义可能变,但 .cc 内容 hash 不会变。**必须把公共声明头也纳入 key**(hash func_decl.h/data_decl.h/extension 文件内容),或采用「.cc 内容未变 且 公共头未变 且 .o 存在 且 命令指纹未变」四条件跳过。
### S3(P0)修复 PCH/缓存指纹的计算时机
- `buildFingerprint()` 每次构建全量扫描+hash 头文件(~42ms + misc 缓存同款遍历),应改为「mtime+size 列表指纹」先行判断,内容 hash 仅在前者变化时执行。
### S4(P1)prepare/convert 并行化
- parse+resolve 是纯函数,可用 pcntl fork 分片解析(子进程只做 parse+serialize,父进程归并反序列化),类似已有 `dispatchParallel` 的模式。转换阶段因共享状态(literalStrings、classMap 等)风险高,可先只并行「解析」,翻译仍串行。
### S5(P1)Windows 并行编译
- 两条路:(a) 给 `Msvc` 后端加 `/MP`(MSVC 原生多进程编译,单命令即可);(b) Windows 上用 `proc_open` 实现多进程调度替代 pcntl。收益对 Windows 用户显著。
### S6(P2)工具链层
- `ccache`/`sccache` 兜底(对 C++ 编译做内容缓存,配合 S2 效果更稳);链接器换 `mold`/`lld`;`-pipe`、`-fno-asynchronous-unwind-tables` 等编译 flag 微调。
### S7(P2)单次遍历替代多次 NodeFinder
- `findSymbolUsing()` 等每文件多次 `findInstanceOf` 全树扫描,可合并为一次 visit 收集函数调用/类引用/常量引用三类符号,减少 O(N) 重复。
---
## 4. AST 解析能否用 mago 替代?—— 结论:不建议(现阶段不可行)
### 4.1 mago 是什么
[mago](https://github.com/carthage-software/mago)(carthage-software/mago)是一个 **Rust 编写的 PHP 工具链**:lint、format、静态分析(对标 Clippy/OXC),v1.45.0(2026-07),MIT/Apache-2.0 双许可,开发活跃。其核心是自研高性能 PHP 解析器(crate:`mago-syntax` 词法/语法/遍历、`mago-names` 名称解析),官方基准:7M 行 WordPress 静态分析 1.46s(PHPStan 55.9s)、lint 0.88s、format 0.43s。**已支持 PHP 8.4 property hooks、asymmetric visibility(private(set) 等)。**
### 4.2 为什么不能直接替代
| 维度 | nikic/php-parser(现状) | mago | 兼容性判断 |
|---|---|---|---|
| 语言/进程 | PHP 类库,进程内对象 | Rust 二进制/crate,AST 是 Rust 结构 | 无法在 PHP 进程中直接消费 |
| AST 模型 | AST(抽象语法树),含 attribute 机制(`resolvedName`/`namespacedName` 等存于节点属性) | CST(具体语法树,含 trivia),`mago ast --json` 是调试输出 | 结构不同,需要完整映射层 |
| 下游依赖 | TypePHP 全库 296 处 `PhpParser\*` 引用、75 个文件;依赖 `NodeTraverser`/`NodeVisitor`/`NameResolver`/`NodeFinder`/`ConstExprEvaluator`/`PrettyPrinter`/`Modifiers` 等 | 无 PHP API | 替换 = 重写编译器前端 |
| 名称解析 | `NameResolver` 写入节点 attribute,后续代码生成大量读取 | `mago-names` 输出到 Rust 侧 | 需要桥接层搬运 |
| 代码生成 | `PrettyPrinter` 用于 stub 生成(`LibraryImportStubGenerator`、`gen_stub.php`) | 无等价物 | 缺失 |
| 错误语义 | `PhpParser\Error` + 行列号,被 `SyntaxError` 包装 | 错误模型不同 | 需要适配 |
**核心矛盾**:TypePHP 不是「解析完就丢」的 linter,而是「AST 必须留在 PHP 进程内、被 4000 行 CompilerBase 逐节点翻译」的编译器。走 mago 只有两条路,都不可行:
1. **mago CLI → JSON AST → PHP 重建对象**:JSON 序列化体积远大于源码(参考第 2 节:php-parser AST 序列化已膨胀 28.8 倍,mago CST 只多不少),反序列化 + 对象重建的 CPU/内存开销会**吃掉**解析省下的时间,且每文件一次进程启动(exec)成本极高;同时丢失 attribute 机制、注释/行列语义需重新映射。
2. **PHP FFI 绑定 mago-syntax crate**:需要为整个 Rust AST 写 FFI 边界 + 转 PHP 对象层,工程量接近重写前端,且 mago 的 CST 与 TypePHP 需要的「带 resolvedName 的 AST」不是一回事,仍要在 PHP 侧自建名称解析与代码生成数据模型。
### 4.3 mago 在本项目中的合理定位(可选)
- **前置快速语法校验**:编译前用 `mago`(或 `mago lint --quick`)做廉价的全项目语法/明显错误预检,给出比 php-parser 更快的反馈(适合 IDE/CI 场景,不进入编译流水线核心)。
- **性能参照物**:mago 证明了「Rust 原生解析」的量级(~百 MB/s vs php-parser 3 MB/s),可作为未来「把前端重写为原生编译器(如 Rust 侧做 parse+name-resolution,产物是自研序列化 AST)」的路线参考——但那是一个全新项目,而非对现有编译器的增量改造。
- **若仍想引入原生解析**:更现实的是保留 php-parser 语义、仅加速解析环节(如 `--php-parser``PhpParser\ParserFactory` 换用 tree-sitter 绑定并在 PHP 侧重建 php-parser 节点),但收益-成本比仍不如 S1/S2。
### 4.4 结论
> **不建议用 mago 替换 nikic/php-parser**。mago 是「解析即弃」的工具链,TypePHP 是「解析后深度消费 AST」的编译器,二者 AST 模型与进程模型不兼容,桥接成本会抹平解析速度优势。
> 真正的提速路径在 **S1(两阶段合并单次解析)+ S2(生成 .cc 增量缓存 + writeFile 内容比对)+ S4/S5(并行化)**,全部基于现有架构增量改造,风险可控、收益立竿见影。
---
## 5. 落地顺序建议
```
第一阶段(P0,改动小收益大,1-2 天)
1. writeFile 内容比对(S2 前置)
2. prepare/convert 共享 AST(S1)
3. 生成 .cc 增量对象缓存(S2,key 含公共头内容 hash)
第二阶段(P1,结构性收益)
4. PCH/misc 指纹轻量化(S3)
5. 解析阶段 pcntl 并行(S4 解析部分)
6. Windows 并行(/MP 或 proc_open)(S5)
第三阶段(P2,长期)
7. 单次遍历合并 NodeFinder(S7)
8. ccache / mold / lld(S6)
```
## 6. 附:基准脚本
分析所用基准脚本:`.workbuddy/bench_ast.php`(可重复运行:`php .workbuddy/bench_ast.php`),复现本节所有数字。

@ -0,0 +1,124 @@
# 海贼王 · 斗地主(Win32 / TypePHP 示例)
`HelgeSverre-libui-sdk/examples/onepiece-doudizhu.php`(libui 版)移植为
**TypePHP(tpc.exe)AOT 编译的原生 Win32 程序**,参考 `landlord-win32`
「纯 PHP 逻辑 + C++ 薄封装 Win32 绑定」架构。
## 特性
- 完整保留原作玩法:三大势力(海军 / 七武海 / 四皇)、9 名角色技能、
叫地主 → 出牌 → 结算全流程,内置 AI 对手与托管。
- 界面由 Win32 GDI 自绘(海洋渐变背景、势力配色卡牌、对手面板、
可拖拽手牌、底部操作按钮)。
- 游戏逻辑 100% 在 PHP 中实现(`php-src/doudizhu/`),与 libui 版共用同一套
领域模型;C++(`cpp-src/win32.cc`)仅封装窗口、消息循环与 GDI 绘制原语。
- 单文件 exe,无外部 PHP 依赖,不依赖 libui / mbstring / miniaudio。
## 目录结构
```
onepiece-doudizhu-win32/
├── main.php 入口:Win32 消息循环驱动 GameController
├── project.yml tpc.exe 构建配置(mode: bin)
├── build.bat 一键编译脚本(需 VS 2022 x64 工具链环境)
├── cpp-src/
│ └── win32.cc Win32 窗口 + GDI 绘制原语(C++ 薄封装)
└── php-src/
├── win32.stub.php win_* 原生函数声明(stub)
└── doudizhu/ 游戏域模型 + 渲染 shim
├── Card / Deck / Combo / MoveGenerator / Game / Ai / Skill /
│ Character / Faction / PlayerState 纯逻辑(与 libui 版同源)
├── GameController.php 对局编排 + Win32 输入/渲染适配
├── Render.php libui DrawContext 兼容 shim → GDI
└── Sound.php 音效管理器(TypePHP 版为空实现,保留 API)
```
## 编译
**x64 Native Tools Command Prompt for VS 2022**(或已加载
`vcvars64.bat` 的终端)中执行:
```bat
build.bat
```
脚本会:
1. 校验 `cl.exe` 可用(不在 VS 环境中会提示)。
2. 从 TypePHP 根目录调用 `tpc.exe project.yml`(tpc 打包后按 CWD 解析
`vendor/autoload.php`,必须从根目录运行)。
3. 将产物 `onepiece_doudizhu.exe` 复制回示例目录。
等价手动命令:
```bat
set PHP_HOME=D:\git\php\tpc_v1095_windows_x86_64
set PHPX_HOME=%PHP_HOME%\phpx
set PATH=%PHP_HOME%;%PATH%
cd /d D:\git\php\tpc_v1095_windows_x86_64
tpc.exe examples\onepiece-doudizhu-win32\project.yml --no-progress
```
## 运行
```bat
onepiece_doudizhu.exe
```
- 拖拽手牌选牌,底部按钮:出牌 / 不出 / 提示 / 技能 / 托管。
- 右上角按钮切换音效(当前为占位实现)。
- 关闭窗口或 `Esc` 退出。
## 移植要点
1. **渲染 shim(Render.php)**:原 `GameController` 的绘制代码直接使用
libui 的 `DrawContext / Brush / Color / FontDescriptor / DrawTextAlign /
TextWeight`,这些符号在 `php-src/doudizhu/Render.php` 中同命名空间重新声明,
`WinDrawContext` 把每个调用翻译为 `win_fill_rect / win_fill_ellipse /
win_fill_rounded_rect / win_stroke_rounded_rect / win_draw_text_ex`。
渐变近似为纯色填充;颜色统一 `0xRRGGBB → COLORREF(0xBBGGRR)` 转换。
2. **输入驱动**:libui 的 `AreaDelegate::mouse/key` 改为 `main.php` 主循环
轮询 `win_peek_message()`,把 `WM_LBUTTONDOWN/UP/MOUSEMOVE/KEYDOWN`
转换为 `GameController::onMouse()/onKey()` 调用。
3. **定时器**:`Loop::delay` 改为 `GameController::tick()`,由主循环每帧
调用,驱动 AI 走子 / 叫分 / 托管(基于 `win_get_tick_count()`)。
4. **TypePHP 兼容性修正**(相对 libui 版源码):
- 顶层游离代码(`define('FONT', …)`)包装进 `ensureDdzFont()`,由
`main()` 调用——TPC 要求所有执行代码位于函数内。
- `readonly` 属性 / `&$ref` 引用遍历等改为普通属性 / 下标赋值(TPC 语法限制)。
- `mb_strlen / mb_substr` 改为 `ddz_utf8_len / ddz_utf8_substr`
(PCRE 实现,TPC 运行时未链接 mbstring)。
- `Brush::linearGradient()` 签名改为与 libui 一致的
`(x0,y0,x1,y1, stops)` 形式,避免 TPC 变参类型推断失败。
5. **双缓冲位图管理(win32.cc)**:修复 `GetStockObject(BITMAP)` 用法错误
(`BITMAP` 是类型不是常量),改为保存/恢复 DC 原 bitmap 后再删除离屏位图。
6. **窗口尺寸(win32.cc)**:`win_create_window` 把传入宽高当作**客户区**尺寸
(`AdjustWindowRect` 自动补上标题栏/边框),保证底部按钮完整可见;窗口
样式为 `WS_OVERLAPPEDWINDOW`,支持拖拽缩放与最大化。新增
`win_get_client_size()` 返回当前客户区尺寸,`render()`/`drawButtons()`
每帧按实际尺寸自适应布局(绘制与点击命中均基于当前尺寸记录的矩形)。
7. **运行时兼容性**:`MoveGenerator::all()` 的 `$cnt` 闭包改为
`count($byRank[$r] ?? [])`——原实现 `count($byRank[$r])` 在顺子/连对
枚举缺失 rank 时对 null 调用 `count()`,TypePHP 运行时(PHP 8 严格类型)
`TypeError: count(): Argument #1 must be of type Countable|array`
8. **窗口标题乱码(win32.cc)**:本机腾讯电脑管家(`tsbx.dll`)会 inline-hook
进程内 user32 的 `DefWindowProcA` / `CreateWindowExW` / `SetWindowTextW`
导致窗口标题被改写为乱码("wm<崑s " 之类修复
- `DdzWndProc` 末尾显式调用 **`DefWindowProcW`**(而非 `DefWindowProc` 宏,
后者在非 UNICODE 编译下展开为被 hook 的 `DefWindowProcA`)。
- `php_win_create_window` 通过 `LoadLibraryW("C:\\Windows\\System32\\user32.dll")`
+ `GetProcAddress` 解析**原始** `CreateWindowExW` / `SetWindowTextW` 调用,
创建后再用原始 `SetWindowTextW` 重设一次标题(双保险)。
- 对照组验证:Explorer / Edge / SmartGit 等其它进程窗口标题读取正常,
仅本进程自定义 WndProc 窗口受影响,证明是进程内 hook 而非系统/代码问题。
9. **托管节奏(GameController.php)**:Win32 移植版里 `defer()``delay()`
都由主循环 `tick()` 驱动、机制相同。原代码在托管/AI 走子/叫分处同时挂
`defer`(立即执行)+ `delay`(兜底),导致动作在下一帧瞬间完成,玩家
来不及点「取消托管」。修复:
- 去掉托管分支的 `defer`,只保留 **1500ms** 延迟(充足取消窗口)。
- `scheduleAi()` 去掉 `defer`,AI 出牌节奏改为 **800ms**
- `scheduleBidStep()` 去掉 `defer`,AI 叫分节奏改为 **700ms**
- `toggleAutoPlay()` 关闭托管时调用 `cancelTimers()` 立即取消挂起的
托管定时器;`autoPlayStep()` 原有 `!$this->autoPlay` 保护兜底。
- `toggleAutoPlay()` 关闭托管且轮到玩家时,重新调用
`setActionsForHumanTurn()` 重算按钮状态——托管时「出牌/不出/提示/技能」
`!autoPlay` 被禁用,恢复后必须重算才能重新可点。

@ -0,0 +1,52 @@
@echo off
rem ============================================================
rem Build script for the One Piece Dou Dizhu Win32 example.
rem
rem Run this from the "x64 Native Tools Command Prompt for VS 2022"
rem (the MSVC environment must be active so that cl.exe, INCLUDE
rem and LIB are all set up). Usage:
rem
rem build.bat
rem
rem NOTE: tpc.exe is a packaged binary and resolves vendor/autoload.php
rem relative to the CURRENT WORKING DIRECTORY, so we launch it from
rem the TypePHP root (TPC_ROOT), not from this examples dir. The
rem finished binary is copied back into this directory.
rem ============================================================
setlocal
rem --- sanity check: are we inside an MSVC environment? ---
where cl.exe >nul 2>&1
if errorlevel 1 (
echo [ERROR] cl.exe not found on PATH.
echo Open the "x64 Native Tools Command Prompt for VS 2022"
echo and run this script from there.
exit /b 1
)
set "TPC_ROOT=D:\git\php\tpc_v1095_windows_x86_64"
set "PHP_HOME=%TPC_ROOT%"
set "PHPX_HOME=%TPC_ROOT%\phpx"
set "PATH=%TPC_ROOT%;%PATH%"
rem --- resolve this script's directory ---
set "SCRIPT_DIR=%~dp0"
pushd "%TPC_ROOT%"
echo [1/2] Running TypePHP compiler (tpc.exe) from %TPC_ROOT%...
"%TPC_ROOT%\tpc.exe" "%SCRIPT_DIR%project.yml" --no-progress
set "RESULT=%errorlevel%"
popd
if not "%RESULT%"=="0" (
echo.
echo BUILD FAILED: tpc.exe exited with errorlevel %RESULT%
exit /b 1
)
rem --- copy the binary back into the example directory ---
copy /Y "%TPC_ROOT%\onepiece_doudizhu.exe" "%SCRIPT_DIR%onepiece_doudizhu.exe" >nul
echo.
echo [2/2] Build finished. Output binary: %SCRIPT_DIR%onepiece_doudizhu.exe
endlocal

@ -0,0 +1,402 @@
/**
* One Piece Dou Dizhu - Win32 API Layer (TypePHP example)
*
* C++ only wraps Win32 windowing + GDI drawing primitives.
* ALL game logic and ALL rendering decisions live in PHP
* (see php-src/doudizhu/GameController.php via the WinDrawContext shim).
*/
#include <phpx.h>
#include <windows.h>
#include <cstdio>
#include <cstring>
#include <cwchar>
#include <map>
using namespace php;
// ============================================================
// Window & Message
// ============================================================
static bool g_quitRequested = false;
// Per memory-DC frame state: the off-screen bitmap plus the DC's previous
// bitmap (so we can restore it before deleting the off-screen bitmap).
struct FrameState {
HBITMAP bitmap;
HBITMAP oldBitmap;
};
static std::map<HDC, FrameState> g_frames;
// ------------------------------------------------------------------
// Anti-hook: some Chinese security suites (e.g. Tencent PC Manager's
// tsbx.dll) inline-hook the user32 exports (CreateWindowExW /
// SetWindowTextW) in every process, rewriting window titles to garbage.
// To get a reliable title we resolve the ORIGINAL exports from a freshly
// loaded copy of user32.dll and call those instead of the hooked IAT
// entries. All other Win32 calls are unaffected and stay normal.
// ------------------------------------------------------------------
typedef HWND (WINAPI *RealCreateWindowExW)(DWORD, LPCWSTR, LPCWSTR, DWORD, int, int, int, int, HWND, HMENU, HINSTANCE, LPVOID);
typedef BOOL (WINAPI *RealSetWindowTextW)(HWND, LPCWSTR);
typedef int (WINAPI *RealGetWindowTextW)(HWND, LPWSTR, int);
static HMODULE g_realUser32 = NULL;
static RealCreateWindowExW g_realCreateWindowExW = NULL;
static RealSetWindowTextW g_realSetWindowTextW = NULL;
static RealGetWindowTextW g_realGetWindowTextW = NULL;
static void resolve_real_user32(void) {
if (g_realCreateWindowExW != NULL) {
return;
}
g_realUser32 = LoadLibraryW(L"C:\\Windows\\System32\\user32.dll");
if (g_realUser32 == NULL) {
g_realUser32 = GetModuleHandleW(L"user32.dll");
}
if (g_realUser32 != NULL) {
g_realCreateWindowExW = (RealCreateWindowExW)GetProcAddress(g_realUser32, "CreateWindowExW");
g_realSetWindowTextW = (RealSetWindowTextW)GetProcAddress(g_realUser32, "SetWindowTextW");
g_realGetWindowTextW = (RealGetWindowTextW)GetProcAddress(g_realUser32, "GetWindowTextW");
}
// Fallbacks to the (possibly hooked) IAT entries if resolution failed.
if (g_realCreateWindowExW == NULL) g_realCreateWindowExW = CreateWindowExW;
if (g_realSetWindowTextW == NULL) g_realSetWindowTextW = SetWindowTextW;
if (g_realGetWindowTextW == NULL) g_realGetWindowTextW = GetWindowTextW;
}
LRESULT CALLBACK DdzWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CLOSE:
case WM_DESTROY:
g_quitRequested = true;
PostQuitMessage(0);
return 0;
case WM_PAINT:
// We render the whole client area every frame via GetDC, so just
// validate the paint region to avoid an endless WM_PAINT loop.
ValidateRect(hWnd, NULL);
return 0;
}
// NOTE: must call DefWindowProcW (not the DefWindowProc macro). On this
// machine Tencent PC Manager (tsbx.dll) inline-hooks DefWindowProcA and
// corrupts UTF-16 window titles (they come back as garbage). The W
// variant is not hooked.
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
Int php_win_create_window(String title, Int width, Int height) {
SetConsoleOutputCP(65001);
resolve_real_user32();
WNDCLASSW wc;
ZeroMemory(&wc, sizeof(wc));
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = DdzWndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = L"DdzWindow";
RegisterClassW(&wc);
// UTF-8 (PHP string) -> UTF-16 window title.
int wtitle_len = MultiByteToWideChar(CP_UTF8, 0, title.data(), -1, NULL, 0);
wchar_t* wtitle = new wchar_t[wtitle_len];
MultiByteToWideChar(CP_UTF8, 0, title.data(), -1, wtitle, wtitle_len);
// The requested width/height are treated as the CLIENT area, so every
// control the PHP layout draws is fully visible. The outer window frame
// (title bar + borders) is added on top via AdjustWindowRect.
RECT rc = {0, 0, (int)width, (int)height};
DWORD style = WS_OVERLAPPEDWINDOW; // resizable + maximizable
AdjustWindowRect(&rc, style, FALSE);
int winW = rc.right - rc.left;
int winH = rc.bottom - rc.top;
// Use the ORIGINAL (un-hooked) CreateWindowExW so security-suite hooks
// cannot corrupt the window title. Fall back to the normal API if the
// real one could not be resolved.
HWND hWnd = g_realCreateWindowExW(
0, L"DdzWindow", wtitle,
style,
CW_USEDEFAULT, CW_USEDEFAULT,
winW, winH,
NULL, NULL, GetModuleHandle(NULL), NULL
);
// Belt & braces: re-assert the title through the original SetWindowTextW
// (some hooks corrupt the title during CreateWindowExW itself).
if (hWnd != NULL && g_realSetWindowTextW != NULL) {
g_realSetWindowTextW(hWnd, wtitle);
}
delete[] wtitle;
return (Int)hWnd;
}
/** Return the current client-area size as [width, height]. */
Array php_win_get_client_size(Int hWnd) {
RECT rc;
GetClientRect((HWND)hWnd, &rc);
Array result;
result.append((Int)rc.right);
result.append((Int)rc.bottom);
return result;
}
void php_win_show_window(Int hWnd, Int cmdShow) {
ShowWindow((HWND)hWnd, (int)cmdShow);
}
Bool php_win_quit_requested() {
return g_quitRequested;
}
void php_win_post_quit(Int exitCode) {
PostQuitMessage((int)exitCode);
}
/**
* Drain one queued message and translate it into a typed array:
* [type, a, b, c]
* type 0 = unhandled (still dispatched, ignored by PHP)
* type 1 = mouse down : a=x, b=y
* type 2 = mouse up : a=x, b=y
* type 3 = mouse move : a=x, b=y, c=leftHeld(0/1)
* type 4 = key down : a=vk
* Returns empty array when no message is pending.
*/
Array php_win_peek_message() {
MSG msg;
ZeroMemory(&msg, sizeof(msg));
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
Array result;
int type = 0;
int a = 0, b = 0, c = 0;
switch ((UINT)msg.message) {
case WM_LBUTTONDOWN:
type = 1;
a = (int)(msg.lParam & 0xFFFF);
b = (int)((msg.lParam >> 16) & 0xFFFF);
break;
case WM_LBUTTONUP:
type = 2;
a = (int)(msg.lParam & 0xFFFF);
b = (int)((msg.lParam >> 16) & 0xFFFF);
break;
case WM_MOUSEMOVE:
type = 3;
a = (int)(msg.lParam & 0xFFFF);
b = (int)((msg.lParam >> 16) & 0xFFFF);
c = (msg.wParam & MK_LBUTTON) ? 1 : 0;
break;
case WM_KEYDOWN:
type = 4;
a = (int)msg.wParam;
break;
default:
type = 0;
a = (int)msg.message;
b = (int)msg.wParam;
c = (int)msg.lParam;
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
result.append((Int)type);
result.append((Int)a);
result.append((Int)b);
result.append((Int)c);
return result;
}
return Array();
}
Int php_win_get_tick_count() {
return (Int)GetTickCount();
}
Int php_win_message_box(Int hWnd, String text, String caption, Int uType) {
int wtext_len = MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, NULL, 0);
wchar_t* wtext = new wchar_t[wtext_len];
MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, wtext, wtext_len);
int wcaption_len = MultiByteToWideChar(CP_UTF8, 0, caption.data(), -1, NULL, 0);
wchar_t* wcaption = new wchar_t[wcaption_len];
MultiByteToWideChar(CP_UTF8, 0, caption.data(), -1, wcaption, wcaption_len);
int result = MessageBoxW((HWND)hWnd, wtext, wcaption, (UINT)uType);
delete[] wtext;
delete[] wcaption;
return result;
}
void php_win_message_beep(Int type) {
MessageBeep((UINT)type);
}
// ============================================================
// Double-buffered frame
// ============================================================
// Begin a frame. Returns the memory-DC handle (an Int) used by all draw calls.
Int php_win_begin_paint(Int hWnd) {
HDC hdc = GetDC((HWND)hWnd);
RECT rc;
GetClientRect((HWND)hWnd, &rc);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP memBitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
FrameState state;
state.bitmap = memBitmap;
state.oldBitmap = oldBitmap;
g_frames[memDC] = state;
ReleaseDC((HWND)hWnd, hdc);
return (Int)memDC;
}
void php_win_end_paint(Int hWnd, Int hdcHandle) {
HDC memDC = (HDC)hdcHandle;
RECT rc;
GetClientRect((HWND)hWnd, &rc);
HDC hdc = GetDC((HWND)hWnd);
BitBlt(hdc, 0, 0, rc.right, rc.bottom, memDC, 0, 0, SRCCOPY);
ReleaseDC((HWND)hWnd, hdc);
auto it = g_frames.find(memDC);
if (it != g_frames.end()) {
SelectObject(memDC, it->second.oldBitmap);
DeleteObject(it->second.bitmap);
g_frames.erase(it);
}
DeleteDC(memDC);
}
// ============================================================
// GDI primitives
// ============================================================
void php_win_fill_rect(Int hdc, Int x, Int y, Int w, Int h, Int rgbColor) {
HBRUSH brush = CreateSolidBrush((COLORREF)rgbColor);
RECT r = {(int)x, (int)y, (int)(x + w), (int)(y + h)};
FillRect((HDC)hdc, &r, brush);
DeleteObject(brush);
}
void php_win_draw_block(Int hdc, Int x, Int y, Int size, Int rgbColor) {
COLORREF color = (COLORREF)rgbColor;
HBRUSH brush = CreateSolidBrush(color);
RECT r = {(int)x + 1, (int)y + 1, (int)(x + size - 1), (int)(y + size - 1)};
FillRect((HDC)hdc, &r, brush);
DeleteObject(brush);
HPEN borderPen = CreatePen(PS_SOLID, 1, RGB(
(BYTE)(GetRValue(color) * 0.6),
(BYTE)(GetGValue(color) * 0.6),
(BYTE)(GetBValue(color) * 0.6)));
HPEN oldPen = (HPEN)SelectObject((HDC)hdc, borderPen);
HBRUSH oldBrush = (HBRUSH)SelectObject((HDC)hdc, GetStockObject(NULL_BRUSH));
Rectangle((HDC)hdc, (int)x, (int)y, (int)(x + size), (int)(y + size));
SelectObject((HDC)hdc, oldBrush);
SelectObject((HDC)hdc, oldPen);
DeleteObject(borderPen);
}
void php_win_draw_line(Int hdc, Int x1, Int y1, Int x2, Int y2, Int rgbColor) {
HPEN pen = CreatePen(PS_SOLID, 1, (COLORREF)rgbColor);
HPEN oldPen = (HPEN)SelectObject((HDC)hdc, pen);
MoveToEx((HDC)hdc, (int)x1, (int)y1, NULL);
LineTo((HDC)hdc, (int)x2, (int)y2);
SelectObject((HDC)hdc, oldPen);
DeleteObject(pen);
}
// Ellipse using CENTER coordinate semantics (matches libui's fillEllipse).
void php_win_fill_ellipse(Int hdc, Int cx, Int cy, Int w, Int h, Int rgbColor) {
HBRUSH brush = CreateSolidBrush((COLORREF)rgbColor);
HBRUSH oldBrush = (HBRUSH)SelectObject((HDC)hdc, brush);
HPEN pen = CreatePen(PS_SOLID, 1, (COLORREF)rgbColor);
HPEN oldPen = (HPEN)SelectObject((HDC)hdc, pen);
Ellipse((HDC)hdc,
(int)(cx - w / 2), (int)(cy - h / 2),
(int)(cx + w / 2), (int)(cy + h / 2));
SelectObject((HDC)hdc, oldPen);
SelectObject((HDC)hdc, oldBrush);
DeleteObject(pen);
DeleteObject(brush);
}
void php_win_fill_rounded_rect(Int hdc, Int x, Int y, Int w, Int h, Int radius, Int rgbColor) {
HBRUSH brush = CreateSolidBrush((COLORREF)rgbColor);
HPEN nullPen = (HPEN)GetStockObject(NULL_PEN);
HBRUSH oldBrush = (HBRUSH)SelectObject((HDC)hdc, brush);
HPEN oldPen = (HPEN)SelectObject((HDC)hdc, nullPen);
RoundRect((HDC)hdc, (int)x, (int)y, (int)(x + w), (int)(y + h),
(int)radius * 2, (int)radius * 2);
SelectObject((HDC)hdc, oldPen);
SelectObject((HDC)hdc, oldBrush);
DeleteObject(brush);
}
void php_win_stroke_rounded_rect(Int hdc, Int x, Int y, Int w, Int h, Int radius, Int rgbColor, Int thickness) {
HPEN pen = CreatePen(PS_SOLID, (int)thickness, (COLORREF)rgbColor);
HBRUSH nullBrush = (HBRUSH)GetStockObject(NULL_BRUSH);
HPEN oldPen = (HPEN)SelectObject((HDC)hdc, pen);
HBRUSH oldBrush = (HBRUSH)SelectObject((HDC)hdc, nullBrush);
RoundRect((HDC)hdc, (int)x, (int)y, (int)(x + w), (int)(y + h),
(int)radius * 2, (int)radius * 2);
SelectObject((HDC)hdc, oldBrush);
SelectObject((HDC)hdc, oldPen);
DeleteObject(pen);
}
// Plain ASCII text (kept for parity / simple labels).
void php_win_draw_text(Int hdc, Int x, Int y, String text, Int fontSize, Int rgbColor, Int bold) {
SetTextColor((HDC)hdc, (COLORREF)rgbColor);
SetBkMode((HDC)hdc, TRANSPARENT);
HFONT hFont = CreateFontA((int)fontSize, 0, 0, 0,
bold ? FW_BOLD : FW_NORMAL, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, "Arial");
HFONT oldFont = (HFONT)SelectObject((HDC)hdc, hFont);
TextOutA((HDC)hdc, (int)x, (int)y, text.data(), (int)strlen(text.data()));
SelectObject((HDC)hdc, oldFont);
DeleteObject(hFont);
}
// UTF-8 text with left/center/right alignment inside an optional width box.
// align: 0 = left, 1 = center, 2 = right
void php_win_draw_text_ex(Int hdc, Int x, Int y, String text, Int fontSize, Int rgbColor, Int bold, Int width, Int align) {
SetTextColor((HDC)hdc, (COLORREF)rgbColor);
SetBkMode((HDC)hdc, TRANSPARENT);
int wtext_len = MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, NULL, 0);
wchar_t* wtext = new wchar_t[wtext_len];
MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, wtext, wtext_len);
HFONT hFont = CreateFontW((int)fontSize, 0, 0, 0,
bold ? FW_BOLD : FW_NORMAL, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Microsoft YaHei");
HFONT oldFont = (HFONT)SelectObject((HDC)hdc, hFont);
int drawX = (int)x;
if (width > 0) {
SIZE sz;
GetTextExtentPoint32W((HDC)hdc, wtext, (int)wcslen(wtext), &sz);
if (align == 1) {
drawX = (int)x + ((int)width - sz.cx) / 2;
} else if (align == 2) {
drawX = (int)x + (int)width - sz.cx;
}
}
TextOutW((HDC)hdc, drawX, (int)y, wtext, (int)wcslen(wtext));
SelectObject((HDC)hdc, oldFont);
DeleteObject(hFont);
delete[] wtext;
}

@ -0,0 +1,87 @@
<?php
/**
* 海贼王 · 斗地主 —— TypePHP / Win32 入口。
*
* 本文件替代原 libui 版 onepiece-doudizhu.php 的入口:不再依赖 libui 事件循环,
* 而是用 Win32 消息循环驱动 GameController(见 php-src/doudizhu/GameController.php)。
* 所有游戏逻辑(发牌 / 叫地主 / 出牌 / AI / 技能 / 渲染)均在 PHP 中实现,
* C++ 仅负责窗口、GDI 绘制原语与输入(cpp-src/win32.cc)。
*
* 编译:在 examples/onepiece-doudizhu-win32 目录执行
* tpc.exe project.yml
* 运行:生成的 .exe(无外部 PHP 依赖,纯 Win32 + GDI)。
*
* 注意:TypePHP 的 bin 模式会自动以 main() 作为程序入口,无需手动调用。
*/
// Win32 常量
const SW_SHOW = 5;
// win_peek_message() 返回的消息类型
const MSG_OTHER = 0;
const MSG_MOUSE_DOWN = 1;
const MSG_MOUSE_UP = 2;
const MSG_MOUSE_MOVE = 3;
const MSG_KEY_DOWN = 4;
use Yangweijie\Ui2\Games\OnePieceDoudizhu\GameController;
use Yangweijie\Ui2\Games\OnePieceDoudizhu\Sound;
function main(): void
{
\date_default_timezone_set('Asia/Shanghai');
\Yangweijie\Ui2\Games\OnePieceDoudizhu\ensureDdzFont();
$hWnd = win_create_window('海贼王 · 斗地主', GameController::WIN_W, GameController::WIN_H);
if ($hWnd == 0) {
echo "窗口创建失败!\n";
return;
}
win_show_window($hWnd, SW_SHOW);
$ctrl = new GameController();
$ctrl->hWnd = $hWnd;
$ctrl->newGame();
echo "海贼王 · 斗地主 已启动(Win32 / TypePHP)\n";
echo "提示:拖拽手牌选牌,底部按钮出牌/不出/提示/技能/托管,右上角切换音效。\n";
while (true) {
// 1) 处理所有待处理消息
while (true) {
$m = win_peek_message();
if (\count($m) === 0) {
break;
}
$type = $m[0] ?? MSG_OTHER;
if ($type === MSG_MOUSE_DOWN) {
$ctrl->onMouse((object) ['x' => $m[1], 'y' => $m[2], 'down' => 1, 'up' => 0, 'held' => 0]);
} elseif ($type === MSG_MOUSE_UP) {
$ctrl->onMouse((object) ['x' => $m[1], 'y' => $m[2], 'down' => 0, 'up' => 1, 'held' => 0]);
} elseif ($type === MSG_MOUSE_MOVE) {
$ctrl->onMouse((object) ['x' => $m[1], 'y' => $m[2], 'down' => 0, 'up' => 0, 'held' => $m[3]]);
} elseif ($type === MSG_KEY_DOWN) {
$ctrl->onKey((int) $m[1]);
}
// MSG_OTHER (例如 WM_PAINT) 已在 WndProc 中 ValidateRect,此处忽略
}
if (win_quit_requested()) {
break;
}
// 2) 触发到期的定时器(AI 走子 / 叫分 / 托管自动出牌)
$ctrl->tick();
// 3) 渲染当前帧(双缓冲,整窗重绘)
$ctrl->render();
// 4) 简单节流到 ~60 FPS
\usleep(16000);
}
Sound::instance()->unload();
echo "游戏结束,bye!\n";
}

@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
/**
* AI 对手:手牌评估 + 跟牌/首出启发式 + 势力感知行为 + 技能效用决策。
*
* 设计为「无状态决策器」:每次轮到 AI 时调用 act() 完成整个回合动作。
* 赤犬反击(counterBomb)属于反应式,由驱动器在对手出炸弹后单独调用 maybeCounter()。
*/
final class Ai
{
/** 叫分:按手牌强度评估 0..3。 */
public static function bid(array $hand): int
{
$score = 0;
$byRank = [];
foreach ($hand as $c) {
$byRank[$c->rank] = ($byRank[$c->rank] ?? 0) + 1;
}
foreach ($byRank as $r => $n) {
if ($n === 4) {
$score += 7; // 炸弹
}
if ($r === Card::JOKER_SMALL) {
$score += 4;
}
if ($r === Card::JOKER_BIG) {
$score += 5;
}
if ($r === 15) {
$score += 2; // 2
}
if ($r === 14 || $r === 13) {
$score += 1; // A/K
}
}
if ($score >= 16) {
return 3;
}
if ($score >= 10) {
return 2;
}
if ($score >= 6) {
return 1;
}
return 0;
}
/** 执行 AI 玩家的整个回合(含技能)。 */
public static function act(Game $g, int $player): void
{
if ($g->phase !== 'playing' || $g->turn !== $player) {
return;
}
self::maybeTurnStartSkill($g, $player);
if ($g->phase !== 'playing' || $g->turn !== $player) {
return;
}
$moves = $g->legalMoves($player);
if ($g->lastPlay === null) {
if ($moves === []) {
return;
}
$mv = self::pickLead($g, $player, $moves);
self::maybeArmOnPlay($g, $player, $mv);
self::maybeArmOnBomb($g, $player, $mv);
$g->play($player, $mv->cards);
return;
}
$beat = self::pickFollow($g, $player, $moves);
if ($beat === null) {
$g->pass($player);
return;
}
self::maybeArmOnPlay($g, $player, $beat);
self::maybeArmOnBomb($g, $player, $beat);
$g->play($player, $beat->cards);
}
/** 赤犬反击:对手刚出炸弹时调用,决定是否反击。 */
public static function maybeCounter(Game $g, int $player): bool
{
if ($g->lastPlay === null || !$g->lastPlay['combo']->isBomb) {
return false;
}
if ($g->players[$player]->characterId !== 'akainu') {
return false;
}
$skill = Character::byId('akainu')->skill();
if (!$g->canUse($player, $skill)) {
return false;
}
$bomber = $g->lastPlay['player'];
// 农民反击地主,或地主反击农民(阻止其快出完)
$threat = $g->handCount($bomber) <= 5;
if ($g->side($player) !== $g->side($bomber) || $threat) {
$g->counterBomb($player);
return true;
}
return false;
}
// ---------------------------------------------------------------- 决策细节
/**
* 首出时挑选要甩出的牌组(公开,供控制器提示与测试复用)。
*
* @param list<Combo> $moves
*/
public static function pickLead(Game $g, int $player, array $moves): Combo
{
$nonBomb = \array_filter($moves, static fn (Combo $c) => !$c->isBomb && !$c->isRocket);
if ($nonBomb !== []) {
// 优先甩出牌数多的组合(顺/飞机/连对),其次最小单
\usort($nonBomb, static function (Combo $a, Combo $b): int {
$ca = \count($a->cards);
$cb = \count($b->cards);
if ($cb !== $ca) {
return $cb <=> $ca;
}
return $a->rank <=> $b->rank;
});
return $nonBomb[0];
}
// 只剩炸弹/火箭
\usort($moves, static fn (Combo $a, Combo $b): int => $a->rank <=> $b->rank);
return $moves[0];
}
/**
* 跟牌时挑选要压过的牌组(公开,供控制器提示与测试复用)。
*
* @param list<Combo> $moves
*/
public static function pickFollow(Game $g, int $player, array $moves): ?Combo
{
if ($moves === []) {
return null;
}
$oppHand = PHP_INT_MAX;
foreach ([0, 1, 2] as $p) {
if ($p !== $player && $g->side($p) !== $g->side($player)) {
$oppHand = \min($oppHand, $g->handCount($p));
}
}
$nonBomb = \array_filter($moves, static fn (Combo $c) => !$c->isBomb && !$c->isRocket);
if ($nonBomb !== []) {
\usort($nonBomb, static fn (Combo $a, Combo $b): int => $a->rank <=> $b->rank);
return $nonBomb[0];
}
// 只有炸弹/火箭:对手快出完才用
if ($oppHand <= 2) {
\usort($moves, static fn (Combo $a, Combo $b): int => $a->rank <=> $b->rank);
return $moves[0];
}
return null; // 留炸弹
}
private static function maybeTurnStartSkill(Game $g, int $player): void
{
$ps = $g->players[$player];
if ($ps->characterId === null) {
return;
}
$skill = Character::byId($ps->characterId)->skill();
if ($skill->trigger !== 'onTurnStart') {
return;
}
if (!$g->canUse($player, $skill)) {
return;
}
// 目标:手牌最少的对手(最具威胁)
$target = null;
$best = PHP_INT_MAX;
foreach ([0, 1, 2] as $p) {
if ($p === $player) {
continue;
}
$c = $g->handCount($p);
if ($c < $best) {
$best = $c;
$target = $p;
}
}
if ($target === null) {
return;
}
// 势力感知:海军/七武海更爱用控制技;四皇(大妈)偷牌也积极
$g->armSkill($player, $target);
}
private static function maybeArmOnPlay(Game $g, int $player, Combo $mv): void
{
$ps = $g->players[$player];
if ($ps->characterId === null || $ps->armed !== null) {
return;
}
$skill = Character::byId($ps->characterId)->skill();
if ($skill->trigger !== 'onPlay') {
return;
}
if (!$g->canUse($player, $skill)) {
return;
}
$faction = $ps->faction;
if ($skill->id === 'garp_shock' || $skill->id === 'mihawk_unblock') {
if ($mv->type === 'single' || $mv->type === 'pair') {
// 海军/七武海:用控制技压制
$g->armSkill($player);
}
} elseif ($skill->id === 'shanks_haki') {
// 四皇:手牌少时花霸气锁 trick;否则偶尔用
if ($g->handCount($player) <= 6 || ($g->lastPlay !== null && \rand(0, 2) === 0)) {
$g->armSkill($player);
}
}
}
private static function maybeArmOnBomb(Game $g, int $player, Combo $mv): void
{
$ps = $g->players[$player];
if ($ps->characterId === null || $ps->armed !== null) {
return;
}
$skill = Character::byId($ps->characterId)->skill();
if ($skill->trigger !== 'onBombPlayed' || !$mv->isBomb) {
return;
}
if (!$g->canUse($player, $skill)) {
return;
}
if ($skill->id === 'whitebeard_quake') {
$g->armSkill($player);
}
}
}

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
/**
* 一张牌:rank 决定牌力,suit 仅用于显示与花色技能(四皇「领土宣言」)。
*
* 牌力序(升序):
* 3..10 -> 3..10
* J=11 Q=12 K=13 A=14 2=15
* 小王=16 大王=17
*/
final class Card
{
public const SUITS = ['♠', '♥', '♦', '♣']; // ♠ ♥ ♦ ♣
public const JOKER_SMALL = 16;
public const JOKER_BIG = 17;
private const RANK_LABELS = [
3 => '3', 4 => '4', 5 => '5', 6 => '6', 7 => '7', 8 => '8', 9 => '9', 10 => '10',
11 => 'J', 12 => 'Q', 13 => 'K', 14 => 'A', 15 => '2',
16 => '小王', 17 => '大王',
];
public function __construct(
public int $rank,
public string $suit = '',
) {
}
public static function smallJoker(): self
{
return new self(self::JOKER_SMALL, 'JOKER');
}
public static function bigJoker(): self
{
return new self(self::JOKER_BIG, 'JOKER');
}
/** 人类可读标签,如「♠A」「大王」。 */
public function label(): string
{
if ($this->rank >= self::JOKER_SMALL) {
return self::RANK_LABELS[$this->rank];
}
return $this->suit . self::RANK_LABELS[$this->rank];
}
/** 稳定 id,用于去重/比较(同点不同花色算不同牌)。 */
public function id(): string
{
return $this->suit . ':' . $this->rank;
}
public function isJoker(): bool
{
return $this->rank >= self::JOKER_SMALL;
}
public function jsonSerialize(): array
{
return ['rank' => $this->rank, 'suit' => $this->suit, 'label' => $this->label()];
}
}

@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
/**
* 9 名代表性角色(每势力 3 名),各带一个独特技能。
* 技能深度结合卡牌对战:onPlay/onBombPlayed 挂出牌修正,onTurnStart 直接施加战场状态。
*/
final class Character
{
public function __construct(
public string $id,
public string $name,
public string $title,
public string $faction,
public string $skillId,
public string $skillName,
public string $skillDesc,
public string $skillTrigger,
public string $skillCost,
) {
}
public function skill(): Skill
{
return new Skill(
$this->skillId,
$this->skillName,
$this->skillDesc,
$this->skillTrigger,
$this->skillCost,
$this->faction,
);
}
/** @return list<self> */
public static function all(): array
{
return [
// 海军本部 — 控制 / 反制
new self('garp', '蒙奇·D·卡普', '拳骨', Faction::NAVY, 'garp_shock', '银河碎拳',
'出单/对时启用:该手牌附带冲击波,下家本 trick 强制 pass。', 'onPlay', 'once'),
new self('akainu', '萨卡斯基', '冥狗', Faction::NAVY, 'akainu_magma', '岩浆灼烧',
'对手出炸弹时反击:使其炸弹无效,并禁用其下个炸弹。', 'onBombPlayed', 'charges:2'),
new self('aokiji', '库赞', '冰河时代', Faction::NAVY, 'aokiji_freeze', '冻结',
'冻结一名对手 1 回合(自动 pass)。', 'onTurnStart', 'once'),
// 王下七武海 — 诡诈 / 交换
new self('mihawk', '鹰眼 米霍克', '世界第一大剑豪', Faction::WARLORD, 'mihawk_unblock', '黑刀·夜',
'出单/对时启用:该手牌不可被非炸拦截。', 'onPlay', 'charges:2'),
new self('boa', '波雅·汉库克', '女帝', Faction::WARLORD, 'boa_petrify', '虏之矢',
'石化一名对手 2 回合(禁用其技能)。', 'onTurnStart', 'once'),
new self('kuma', '巴索罗米·熊', '暴君', Faction::WARLORD, 'kuma_push', '肉球推送',
'将你最小的一张手牌推给指定对手(扰乱牌型)。', 'onTurnStart', 'once'),
// 四皇 — 压制 / 掠夺
new self('shanks', '香克斯', '红发', Faction::EMPEROR, 'shanks_haki', '霸王色霸气',
'出牌时花费 1 霸气:该手牌凌驾一切非炸(除非炸弹/火箭)。', 'onPlay', 'haki:1'),
new self('whitebeard', '爱德华·纽盖特', '白胡子', Faction::EMPEROR, 'whitebeard_quake', '震震果实',
'出炸弹时启用:炸弹威力 +1(视为更大炸弹)。', 'onBombPlayed', 'charges:2'),
new self('bigmom', '夏洛特·玲玲', '大妈', Faction::EMPEROR, 'bigmom_steal', '魂魂召唤',
'随机偷取一名对手 1 张手牌。', 'onTurnStart', 'once'),
];
}
public static function byId(string $id): self
{
static $map = null;
if ($map === null) {
$map = [];
foreach (self::all() as $c) {
$map[$c->id] = $c;
}
}
if (!isset($map[$id])) {
throw new \InvalidArgumentException("未知角色: $id");
}
return $map[$id];
}
/** @return list<self> */
public static function byFaction(string $faction): array
{
return \array_values(\array_filter(self::all(), static fn (self $c) => $c->faction === $faction));
}
}

@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
use RuntimeException;
/**
* 牌型识别与比较。
*
* 支持:单/对/三/三带一/三带二/顺子/连对/飞机(含翼)/炸弹/火箭。
* 比较规则:火箭 > 炸弹(比点) > 普通(同型同长比点)。技能修正(霸气/不可拦截)
* 由 Engine 在对局层处理,不在本类内。
*/
final class Combo
{
public function __construct(
public string $type,
public int $rank, // 主要比较点(炸弹/火箭用 17)
public int $length, // 顺子/连对/飞机的长度
public array $cards, // list<Card>
public bool $isBomb = false,
public bool $isRocket = false,
) {
}
/**
* @param list<Card> $cards
*/
public static function parse(array $cards): ?self
{
$n = \count($cards);
if ($n === 0) {
return null;
}
$byRank = [];
foreach ($cards as $c) {
$byRank[$c->rank][] = $c;
}
$ranks = \array_keys($byRank);
\sort($ranks);
$counts = [];
foreach ($ranks as $r) {
$counts[$r] = \count($byRank[$r]);
}
// 火箭(双王)
if ($n === 2 && isset($byRank[Card::JOKER_SMALL]) && isset($byRank[Card::JOKER_BIG])) {
return new self('rocket', Card::JOKER_BIG, 1, $cards, false, true);
}
// 炸弹(四同)
if ($n === 4 && \count($ranks) === 1) {
return new self('bomb', $ranks[0], 1, $cards, true);
}
// 单
if ($n === 1) {
return new self('single', $ranks[0], 1, $cards);
}
// 对
if ($n === 2 && \count($ranks) === 1) {
return new self('pair', $ranks[0], 1, $cards);
}
// 三
if ($n === 3 && \count($ranks) === 1) {
return new self('triple', $ranks[0], 1, $cards);
}
// 三带一
if ($n === 4 && \count($ranks) === 2) {
$triple = self::rankWithCount($counts, 3);
if ($triple !== null) {
return new self('triple1', $triple, 1, $cards);
}
return null;
}
// 三带二
if ($n === 5 && \count($ranks) === 2) {
$triple = self::rankWithCount($counts, 3);
$pair = self::rankWithCount($counts, 2);
if ($triple !== null && $pair !== null) {
return new self('triple2', $triple, 1, $cards);
}
return null;
}
$maxRank = \max($ranks);
$allOnes = \min($counts) === 1 && \max($counts) === 1;
$allTwos = \min($counts) === 2 && \max($counts) === 2;
$allThrees = \min($counts) === 3 && \max($counts) === 3;
// 顺子(≥5 连续单张,不含 2/王)
if ($allOnes && $n >= 5 && $maxRank <= 14 && self::isConsecutive($ranks)) {
return new self('straight', $maxRank, $n, $cards);
}
// 连对(≥3 连续对子,不含 2/王)
if ($allTwos && $n >= 6 && $maxRank <= 14 && self::isConsecutive($ranks)) {
return new self('straight2', $maxRank, (int) ($n / 2), $cards);
}
// 飞机(≥2 连续三张,可带翼)
if ($allThrees) {
return new self('plane', $maxRank, $n, $cards);
}
$plane = self::planeWithWings($ranks, $counts, $n, $cards);
if ($plane !== null) {
return $plane;
}
return null;
}
/**
* @param array<int,int> $counts
*/
private static function rankWithCount(array $counts, int $want): ?int
{
foreach ($counts as $r => $c) {
if ($c === $want) {
return $r;
}
}
return null;
}
/**
* @param list<int> $ranks
* @param array<int,int> $counts
* @param list<Card> $cards
*/
private static function planeWithWings(array $ranks, array $counts, int $n, array $cards): ?self
{
$tripleRanks = [];
$wingRanks = [];
foreach ($ranks as $r) {
if ($counts[$r] === 3) {
$tripleRanks[] = $r;
} else {
$wingRanks[] = $r;
}
}
if (\count($tripleRanks) < 2 || !self::isConsecutive($tripleRanks) || \max($tripleRanks) > 14) {
return null;
}
$t = \count($tripleRanks);
$expectedWings = $n - 3 * $t;
// 无翼
if ($expectedWings === 0) {
return new self('plane', \max($tripleRanks), $t, $cards);
}
// 单翼:t 张单牌
if ($expectedWings === $t && \count($wingRanks) === $t && self::allCount($wingRanks, $counts, 1)) {
return new self('plane1', \max($tripleRanks), $t, $cards);
}
// 对翼:t 个对子
if ($expectedWings === 2 * $t && \count($wingRanks) === $t && self::allCount($wingRanks, $counts, 2)) {
return new self('plane2', \max($tripleRanks), $t, $cards);
}
return null;
}
/**
* @param list<int> $ranks
* @param array<int,int> $counts
*/
private static function allCount(array $ranks, array $counts, int $want): bool
{
foreach ($ranks as $r) {
if (($counts[$r] ?? 0) !== $want) {
return false;
}
}
return true;
}
/**
* @param list<int> $ranks
*/
private static function isConsecutive(array $ranks): bool
{
$r = \array_values($ranks);
if (\count($r) < 2) {
return false;
}
\sort($r);
for ($i = 1; $i < \count($r); $i++) {
if ($r[$i] !== $r[$i - 1] + 1) {
return false;
}
}
return true;
}
/** a 是否能压过当前桌面 lastPlay(b)。b=null 表示自由出牌。 */
public static function beats(self $a, ?self $b): bool
{
if ($b === null) {
return true;
}
if ($a->isRocket) {
return true;
}
if ($b->isRocket) {
return false;
}
if ($a->isBomb) {
if ($b->isBomb) {
return $a->rank > $b->rank;
}
return true;
}
if ($b->isBomb) {
return false;
}
if ($a->type !== $b->type) {
return false;
}
if ($a->length !== $b->length) {
return false;
}
return $a->rank > $b->rank;
}
public function describe(): string
{
$names = [
'single' => '单张', 'pair' => '对子', 'triple' => '三张',
'triple1' => '三带一', 'triple2' => '三带二', 'straight' => '顺子',
'straight2' => '连对', 'plane' => '飞机', 'plane1' => '飞机带单',
'plane2' => '飞机带对', 'bomb' => '炸弹', 'rocket' => '王炸',
];
$label = $names[$this->type] ?? $this->type;
return \sprintf('%s(%s)', $label, \implode('', \array_map(static fn (Card $c) => $c->label(), $this->cards)));
}
}

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
/**
* 54 张牌组:4 花色 × 3..2 + 双王。洗牌后发 3 手 + 3 张底牌。
*/
final class Deck
{
/**
* @return list<Card>
*/
public static function build(): array
{
$cards = [];
foreach (Card::SUITS as $suit) {
for ($r = 3; $r <= 15; $r++) {
$cards[] = new Card($r, $suit);
}
}
$cards[] = Card::smallJoker();
$cards[] = Card::bigJoker();
return $cards;
}
/**
* 洗牌发牌。
*
* @return array{hands: array<int, list<Card>>, bottom: list<Card>}
*/
public static function deal(): array
{
$cards = self::build();
\shuffle($cards);
$hands = [[], [], []];
for ($i = 0; $i < 51; $i++) {
$hands[$i % 3][] = $cards[$i];
}
$bottom = \array_slice($cards, 51, 3);
foreach ($hands as $i => $hand) {
self::sort($hands[$i]);
}
return ['hands' => $hands, 'bottom' => $bottom];
}
/**
* 按牌力升序排序(同点花色保持稳定)。
*
* @param list<Card> $hand
*/
public static function sort(array &$hand): void
{
\usort($hand, static fn (Card $a, Card $b): int => $a->rank <=> $b->rank);
}
}

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
/**
* 三大势力:海军本部 / 王下七武海 / 四皇。
* 每个势力有「特性」描述(影响 AI 行为与部分被动),成员使用各自主动技能。
*/
final class Faction
{
public const NAVY = 'navy'; // 海军本部 — 绝对正义(控制/反制)
public const WARLORD = 'warlord'; // 王下七武海 — 被招安的海盗(诡诈/交换)
public const EMPEROR = 'emperor'; // 四皇 — 新世界霸主(压制/掠夺)
/** @var array<string, array{name:string, creed:string, trait:string, color:string}> */
public static array $defs = [
self::NAVY => [
'name' => '海军本部',
'creed' => '绝对正义',
'trait' => '正义铁拳:每局可反制一次对手炸弹;情报:叫完地主可查看底牌。',
'color' => '#1e3a8a',
],
self::WARLORD => [
'name' => '王下七武海',
'creed' => '被招安的海盗',
'trait' => '协定漏洞:被迫过牌时可改出最小单张续命(2 次);凭实力借:每局交换 1 张牌。',
'color' => '#7c3aed',
],
self::EMPEROR => [
'name' => '四皇',
'creed' => '新世界霸主',
'trait' => '霸王色霸气:持有霸气 token,出牌时花费使其凌驾一切非炸;召集:每局抽 1 张。',
'color' => '#b91c1c',
],
];
public static function name(string $id): string
{
return self::$defs[$id]['name'] ?? $id;
}
public static function color(string $id): string
{
return self::$defs[$id]['color'] ?? '#444444';
}
}

@ -0,0 +1,501 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
use InvalidArgumentException;
use RuntimeException;
/**
* 对局状态 + 规则引擎 + 技能调度。
*
* 阶段:bidding(叫地主) → playing(出牌) → over。
* 技能采用「先 arm(激活)后行动」模型:
* - onTurnStart 技能:arm 时立即生效(冻结/石化/偷牌/推牌)
* - onPlay / onBombPlayed 技能:arm 后给「下一手牌」附加修正(跳过/不可拦截/霸气/炸弹+1)
* - 赤犬反击:对手出炸弹后调用 counterBomb() 取消
*/
final class Game
{
/** @var list<PlayerState> */
public array $players;
public ?int $landlord = null;
public int $turn = 0;
/** @var array{player:int, combo:Combo, hakiActive:bool, unblockable:bool, bombBonus:int}|null */
public ?array $lastPlay = null;
public int $passes = 0;
public string $phase = 'bidding';
public ?int $winner = null;
public ?string $winnerSide = null;
/** @var list<string> */
public array $log = [];
/** 战场修正(按目标玩家) */
public array $mods = [
'skipNext' => [false, false, false],
'frozen' => [0, 0, 0],
'petrified' => [0, 0, 0],
'bombDisabled' => [0, 0, 0],
];
/** 叫分状态 */
public array $bidding = [
'scores' => [0, 0, 0],
'current' => 0,
'highest' => 0,
'highestPlayer' => null,
'acted' => [false, false, false],
];
/** @var list<Card> 底牌(叫完分给地主) */
public array $bottom = [];
/** 是否已把底牌发给地主(用于「情报」查看) */
public bool $bottomRevealed = false;
/** 事件回调(音效/UI 钩子):fn(event, payload) */
public $onEvent = null;
/**
* @param list<PlayerState> $players
* @param list<Card> $bottom
*/
public function __construct(array $players, array $bottom)
{
$this->players = $players;
$this->bottom = $bottom;
$this->turn = 0;
}
public function side(int $player): string
{
return $player === $this->landlord ? 'landlord' : 'peasant';
}
public function isOver(): bool
{
return $this->phase === 'over';
}
public function handCount(int $player): int
{
return \count($this->players[$player]->hand);
}
// ---------------------------------------------------------------- 叫地主
/**
* @return 'ok'|'redeal'|'started'
*/
public function bid(int $player, int $score): string
{
if ($this->phase !== 'bidding') {
throw new RuntimeException('not in bidding phase');
}
if ($this->bidding['acted'][$player]) {
throw new InvalidArgumentException('player already bid');
}
$score = \max(0, \min(3, $score));
$this->bidding['scores'][$player] = $score;
$this->bidding['acted'][$player] = true;
if ($score > $this->bidding['highest']) {
$this->bidding['highest'] = $score;
$this->bidding['highestPlayer'] = $player;
}
$this->emit('bid', ['player' => $player, 'score' => $score]);
$actedCount = \count(\array_filter($this->bidding['acted']));
if ($actedCount < 3 && $score < 3) {
// 轮到下一位
$this->bidding['current'] = ($player + 1) % 3;
return 'ok';
}
// 结束叫分
if ($this->bidding['highest'] === 0) {
return 'redeal';
}
$this->startPlaying();
return 'started';
}
private function startPlaying(): void
{
$this->landlord = $this->bidding['highestPlayer'];
$this->players[$this->landlord]->isLandlord = true;
// 给地主发底牌
foreach ($this->bottom as $c) {
$this->players[$this->landlord]->hand[] = $c;
}
Deck::sort($this->players[$this->landlord]->hand);
$this->bottomRevealed = true;
$this->turn = $this->landlord;
$this->phase = 'playing';
$this->emit('gameStart', ['landlord' => $this->landlord]);
}
// ---------------------------------------------------------------- 出牌
/**
* @param list<Card> $cards
* @param int|null $skillTarget 部分技能需要指定目标
*/
public function play(int $player, array $cards, ?int $skillTarget = null): void
{
$this->ensureTurn($player);
if ($this->mods['frozen'][$player] > 0) {
throw new RuntimeException('玩家被冻结,本回合无法出牌');
}
$combo = Combo::parse($cards);
if ($combo === null) {
throw new InvalidArgumentException('无效的牌型');
}
$this->assertCardsInHand($player, $cards);
// 比较:考虑上一手的技能修正
if ($this->lastPlay !== null) {
if (!$this->beatsWithMods($combo, $this->lastPlay)) {
throw new InvalidArgumentException('压不过上一手');
}
}
// 白胡子:自身炸弹 +1(arm 标记 bombBonus)
$bombBonus = 0;
$hakiActive = false;
$unblockable = false;
$ps = $this->players[$player];
if ($ps->armed !== null) {
$skill = Character::byId($ps->characterId)->skill();
if ($skill->trigger === 'onPlay' && $this->comboQualifies($combo, $skill)) {
if ($skill->id === 'shanks_haki') {
$hakiActive = true;
}
if ($skill->id === 'mihawk_unblock') {
$unblockable = true;
}
if ($skill->id === 'garp_shock') {
// 下家跳过:在 advanceTurn 时处理
$this->mods['skipNext'][($player + 1) % 3] = true;
}
}
if ($skill->trigger === 'onBombPlayed' && $combo->isBomb) {
if ($skill->id === 'whitebeard_quake') {
$bombBonus = 1;
}
}
// 技能真正生效时才扣费(once/charges/haki),防止无限使用
$applied = ($skill->trigger === 'onPlay' && $this->comboQualifies($combo, $skill))
|| ($skill->trigger === 'onBombPlayed' && $combo->isBomb);
if ($applied) {
$this->consume($player, $skill);
}
$ps->armed = null;
}
// 移除手牌
$this->removeCards($player, $cards);
$this->lastPlay = [
'player' => $player,
'combo' => $combo,
'hakiActive' => $hakiActive,
'unblockable' => $unblockable,
'bombBonus' => $bombBonus,
];
$this->passes = 0;
$this->emit('play', [
'player' => $player,
'combo' => $combo,
'hakiActive' => $hakiActive,
'unblockable' => $unblockable,
'bombBonus' => $bombBonus,
]);
// 赤犬反击窗口:若有人 armed 赤犬,自动在 counterBomb 中处理(AI/UI 调用)
// 此处不自动触发
if ($this->handCount($player) === 0) {
$this->finish($player);
return;
}
$this->advanceTurn();
}
public function pass(int $player): void
{
$this->ensureTurn($player);
if ($this->lastPlay === null) {
throw new InvalidArgumentException('首出不能过');
}
// 七武海「续命」:技能可改出最小单张(由 armSkill 处理,这里直接过)
$this->passes++;
$this->emit('pass', ['player' => $player]);
$this->advanceTurn();
}
/** 赤犬反击:取消对手刚出的炸弹。 */
public function counterBomb(int $player): void
{
if ($this->lastPlay === null || !$this->lastPlay['combo']->isBomb) {
throw new RuntimeException('当前没有可反击的炸弹');
}
$ps = $this->players[$player];
if ($ps->characterId === null) {
throw new RuntimeException('无角色');
}
$skill = Character::byId($ps->characterId)->skill();
if ($skill->id !== 'akainu_magma') {
throw new RuntimeException('该角色无反击炸弹技能');
}
if (!$this->canUse($player, $skill)) {
throw new RuntimeException('技能不可用');
}
$bomber = $this->lastPlay['player'];
// 把炸弹牌还给出牌者
foreach ($this->lastPlay['combo']->cards as $c) {
$this->players[$bomber]->hand[] = $c;
}
Deck::sort($this->players[$bomber]->hand);
// 该 trick 作废,反击者获得 lead 权
$this->lastPlay = null;
$this->passes = 0;
$this->consume($player, $skill);
$this->mods['bombDisabled'][$bomber] = 2; // 其下个炸弹禁用
$this->turn = $player;
$this->emit('counter', ['player' => $player, 'target' => $bomber]);
}
// ---------------------------------------------------------------- 技能
public function canUse(int $player, Skill $skill): bool
{
if ($this->phase !== 'playing') {
return false;
}
if ($this->mods['petrified'][$player] > 0) {
return false; // 被石化
}
$ps = $this->players[$player];
if ($skill->cost === 'once' && $ps->skill['usedOnce']) {
return false;
}
if (\str_starts_with($skill->cost, 'charges:')) {
$n = (int) \substr($skill->cost, 8);
return $ps->skill['chargesLeft'] >= $n;
}
if (\str_starts_with($skill->cost, 'haki:')) {
$n = (int) \substr($skill->cost, 5);
return $ps->skill['haki'] >= $n;
}
return true;
}
/**
* 激活技能。onTurnStart 立即生效;onPlay/onBombPlayed 预置到下一手。
*/
public function armSkill(int $player, ?int $target = null): void
{
if ($this->phase !== 'playing') {
throw new RuntimeException('非出牌阶段');
}
$ps = $this->players[$player];
if ($ps->characterId === null) {
throw new RuntimeException('玩家未分配角色');
}
$skill = Character::byId($ps->characterId)->skill();
if (!$this->canUse($player, $skill)) {
throw new RuntimeException('技能不可用(费用不足或被石化)');
}
if ($skill->trigger === 'onTurnStart') {
Skill::apply($this, $player, $target);
$this->consume($player, $skill);
$this->emit('skill', ['player' => $player, 'skill' => $skill->id, 'target' => $target]);
return;
}
// onPlay / onBombPlayed:预置
if ($ps->armed !== null) {
throw new RuntimeException('已有预置技能');
}
$ps->armed = $skill->id;
$ps->armedTarget = $target;
$this->emit('arm', ['player' => $player, 'skill' => $skill->id]);
}
private function consume(int $player, Skill $skill): void
{
$ps = $this->players[$player];
if ($skill->cost === 'once') {
$ps->skill['usedOnce'] = true;
} elseif (\str_starts_with($skill->cost, 'charges:')) {
$n = (int) \substr($skill->cost, 8);
$ps->skill['chargesLeft'] -= $n;
} elseif (\str_starts_with($skill->cost, 'haki:')) {
$n = (int) \substr($skill->cost, 5);
$ps->skill['haki'] -= $n;
}
}
private function comboQualifies(Combo $combo, Skill $skill): bool
{
if ($skill->id === 'garp_shock' || $skill->id === 'mihawk_unblock') {
return $combo->type === 'single' || $combo->type === 'pair';
}
if ($skill->id === 'shanks_haki') {
return true; // 任意手牌可附霸气
}
return true;
}
// ---------------------------------------------------------------- 流转
private function ensureTurn(int $player): void
{
if ($this->phase !== 'playing') {
throw new RuntimeException('非出牌阶段');
}
if ($player !== $this->turn) {
throw new InvalidArgumentException('还没轮到该玩家');
}
}
private function advanceTurn(): void
{
// trick 结束:连续两人 pass
if ($this->passes >= 2) {
$this->lastPlay = null;
$this->passes = 0;
// lead 权归上一个出牌者(this->turn 已是最后出牌者,因 pass 不改变 lastPlay 持有者)
}
$next = ($this->turn + 1) % 3;
// 跳过被冻结/被跳过的玩家
$guard = 0;
while ($guard < 3) {
if ($this->mods['skipNext'][$next]) {
$this->mods['skipNext'][$next] = false;
$this->emit('skip', ['player' => $next]);
$next = ($next + 1) % 3;
$guard++;
continue;
}
if ($this->mods['frozen'][$next] > 0) {
$this->mods['frozen'][$next]--;
$this->emit('frozenSkip', ['player' => $next]);
$next = ($next + 1) % 3;
$guard++;
continue;
}
break;
}
// 递减石化/炸弹禁用计数(在对应玩家回合开始时)
if ($this->mods['petrified'][$next] > 0) {
$this->mods['petrified'][$next]--;
}
if ($this->mods['bombDisabled'][$next] > 0) {
$this->mods['bombDisabled'][$next]--;
}
$this->turn = $next;
}
private function finish(int $player): void
{
$this->winner = $player;
$this->winnerSide = $this->side($player);
$this->phase = 'over';
$this->emit('gameOver', ['winner' => $player, 'side' => $this->winnerSide]);
}
// ---------------------------------------------------------------- 比较(含技能修正)
/**
* @param array{combo:Combo,hakiActive:bool,unblockable:bool,bombBonus:int} $last
*/
private function beatsWithMods(Combo $a, array $last): bool
{
$b = $last['combo'];
$bombBonus = $last['bombBonus'];
// 复制 b 并叠加炸弹加成用于比较
if ($bombBonus > 0 && $b->isBomb) {
$b = new Combo($b->type, $b->rank + $bombBonus, $b->length, $b->cards, true);
}
// 霸气/不可拦截:非炸不可压
if (($last['hakiActive'] || $last['unblockable']) && !$a->isBomb && !$a->isRocket) {
return false;
}
return Combo::beats($a, $b);
}
// ---------------------------------------------------------------- 手牌校验
/**
* @param list<Card> $cards
*/
private function assertCardsInHand(int $player, array $cards): void
{
$handIds = [];
foreach ($this->players[$player]->hand as $c) {
$handIds[$c->id()] = true;
}
foreach ($cards as $c) {
if (!isset($handIds[$c->id()])) {
throw new InvalidArgumentException('手牌中没有该牌: ' . $c->label());
}
$handIds[$c->id()] = false; // 防止同 id 重复(同点不同花色安全)
}
}
/**
* @param list<Card> $cards
*/
private function removeCards(int $player, array $cards): void
{
$remove = [];
foreach ($cards as $c) {
$remove[$c->id()] = ($remove[$c->id()] ?? 0) + 1;
}
$kept = [];
foreach ($this->players[$player]->hand as $c) {
if (($remove[$c->id()] ?? 0) > 0) {
$remove[$c->id()]--;
} else {
$kept[] = $c;
}
}
$this->players[$player]->hand = $kept;
}
// ---------------------------------------------------------------- 合法着法(AI/提示)
/**
* @return list<Combo>
*/
public function legalMoves(int $player): array
{
$hand = $this->players[$player]->hand;
$all = MoveGenerator::all($hand);
if ($this->lastPlay === null) {
return $all;
}
return \array_values(\array_filter($all, fn (Combo $c) => $this->beatsWithMods($c, $this->lastPlay)));
}
// ---------------------------------------------------------------- 事件
/**
* @param mixed $payload
*/
private function emit(string $event, $payload): void
{
if (\is_callable($this->onEvent)) {
($this->onEvent)($event, $payload);
}
$this->log[] = \sprintf('[%s] %s', $event, \is_array($payload) ? \json_encode($payload) : (string) $payload);
}
}

@ -0,0 +1,151 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
/**
* 从一手牌枚举所有合法牌型(用于 AI 决策与合法性校验)。
* 同 (type,rank,length) 的去重,避免手牌多花色导致组合爆炸。
*/
final class MoveGenerator
{
/**
* @param list<Card> $hand
* @return list<Combo>
*/
public static function all(array $hand): array
{
if ($hand === []) {
return [];
}
$byRank = [];
foreach ($hand as $c) {
$byRank[$c->rank][] = $c;
}
$ranks = \array_keys($byRank);
\sort($ranks);
$has = static fn (int $r) => isset($byRank[$r]);
$cnt = static fn (int $r) => \count($byRank[$r] ?? []);
$out = [];
$seen = [];
$push = static function (array $cards) use (&$out, &$seen): void {
$combo = Combo::parse($cards);
if ($combo === null) {
return;
}
$key = $combo->type . ':' . $combo->rank . ':' . $combo->length;
if (isset($seen[$key])) {
return;
}
$seen[$key] = true;
$out[] = $combo;
};
// 单 / 对 / 三
foreach ($ranks as $r) {
$push([$byRank[$r][0]]);
if ($cnt($r) >= 2) {
$push([$byRank[$r][0], $byRank[$r][1]]);
}
if ($cnt($r) >= 3) {
$push([$byRank[$r][0], $byRank[$r][1], $byRank[$r][2]]);
}
}
// 三带一 / 三带二
foreach ($ranks as $r) {
if ($cnt($r) < 3) {
continue;
}
$triple = [$byRank[$r][0], $byRank[$r][1], $byRank[$r][2]];
foreach ($ranks as $s) {
if ($s === $r) {
continue;
}
if ($cnt($s) >= 1) {
$push(\array_merge($triple, [$byRank[$s][0]]));
}
if ($cnt($s) >= 2) {
$push(\array_merge($triple, [$byRank[$s][0], $byRank[$s][1]]));
}
}
}
// 顺子 (5..12 连)
for ($len = 5; $len <= 12; $len++) {
for ($start = 3; $start + $len - 1 <= 14; $start++) {
$ok = true;
$cards = [];
for ($k = 0; $k < $len; $k++) {
$rr = $start + $k;
if (!$has($rr)) {
$ok = false;
break;
}
$cards[] = $byRank[$rr][0];
}
if ($ok) {
$push($cards);
}
}
}
// 连对 (3..10 连)
for ($len = 3; $len <= 10; $len++) {
for ($start = 3; $start + $len - 1 <= 14; $start++) {
$ok = true;
$cards = [];
for ($k = 0; $k < $len; $k++) {
$rr = $start + $k;
if ($cnt($rr) < 2) {
$ok = false;
break;
}
$cards[] = $byRank[$rr][0];
$cards[] = $byRank[$rr][1];
}
if ($ok) {
$push($cards);
}
}
}
// 飞机(纯三连,2..6 连)
for ($len = 2; $len <= 6; $len++) {
for ($start = 3; $start + $len - 1 <= 14; $start++) {
$ok = true;
$cards = [];
for ($k = 0; $k < $len; $k++) {
$rr = $start + $k;
if ($cnt($rr) < 3) {
$ok = false;
break;
}
$cards[] = $byRank[$rr][0];
$cards[] = $byRank[$rr][1];
$cards[] = $byRank[$rr][2];
}
if ($ok) {
$push($cards);
}
}
}
// 炸弹
foreach ($ranks as $r) {
if ($cnt($r) === 4) {
$push($byRank[$r]);
}
}
// 火箭
if ($has(Card::JOKER_SMALL) && $has(Card::JOKER_BIG)) {
$push([$byRank[Card::JOKER_SMALL][0], $byRank[Card::JOKER_BIG][0]]);
}
return $out;
}
}

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
/**
* 单名玩家运行时状态。
*/
final class PlayerState
{
/** @var list<Card> */
public array $hand;
public ?string $characterId = null;
public ?string $faction = null;
public bool $isLandlord = false;
/** 技能费用状态 */
public array $skill = [
'chargesLeft' => 0,
'usedOnce' => false,
'haki' => 0,
'cooldown' => 0,
];
/** 预置的下一手技能(onPlay/onBombPlayed) */
public ?string $armed = null;
public ?int $armedTarget = null;
/**
* @param list<Card> $hand
*/
public function __construct(array $hand)
{
$this->hand = $hand;
}
public function initSkill(Skill $skill): void
{
if ($skill->cost === 'once') {
$this->skill['usedOnce'] = false;
} elseif (\str_starts_with($skill->cost, 'charges:')) {
$this->skill['chargesLeft'] = (int) \substr($skill->cost, 8);
} elseif (\str_starts_with($skill->cost, 'haki:')) {
$this->skill['haki'] = (int) \substr($skill->cost, 5);
}
}
}

@ -0,0 +1,261 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
/**
* Win32 rendering shim.
*
* The original GameController was written against libui's DrawContext /
* Brush / Color / FontDescriptor API. Those symbols are re-declared here, in
* the SAME namespace, so the controller's drawing code stays byte-for-byte
* identical while the actual pixels are produced by the Win32 GDI primitives
* declared in win32.stub.php (implemented in cpp-src/win32.cc).
*
* Colour convention: PHP code passes 0xRRGGBB ints. GDI needs 0xBBGGRR, so
* ddz_rgb() flips the channels on the way down to C++.
*/
/* ----------------------------- constants ------------------------------ */
/** Text alignment — mirrors libui DrawTextAlign. */
final class DrawTextAlign
{
public const Left = 0;
public const Center = 1;
public const Right = 2;
}
/** Font weight — mirrors libui TextWeight. */
final class TextWeight
{
public const Normal = 400;
public const Bold = 700;
}
/* ----------------------------- color types ---------------------------- */
final class Color
{
public int $hex;
public function __construct(int $hex)
{
$this->hex = $hex & 0xFFFFFF;
}
public static function rgb(int $hex): self
{
return new self($hex);
}
/** libui accepts normalised floats here (r,g,b in 0..1). */
public static function rgba(float $r, float $g, float $b, float $a): self
{
$rr = (int) ($r * 255);
$gg = (int) ($g * 255);
$bb = (int) ($b * 255);
return new self(($rr << 16) | ($gg << 8) | $bb);
}
}
final class Brush
{
public int $hex;
public function __construct(int $hex)
{
$this->hex = $hex & 0xFFFFFF;
}
public static function rgb(int $hex): self
{
return new self($hex);
}
/** Extract the int colour from a Color (or pass-through an int brush). */
public static function color($c): int
{
if ($c instanceof Color) {
return $c->hex;
}
if ($c instanceof Brush) {
return $c->hex;
}
return (int) $c;
}
/**
* Gradients are approximated by a solid fill in this GDI port.
*
* Signature mirrors libui's brushForFill linearGradient: four float
* coordinates plus a list of [offset, r, g, b, a] stops. We take the
* colour of the LAST stop (or the deep-navy fallback) as the flat fill.
*
* @param array<int, array{0: float, 1: float, 2: float, 3: float, 4?: float}> $stops
*/
public static function linearGradient(float $x0, float $y0, float $x1, float $y1, array $stops): self
{
$hex = 0x08152e;
if (\count($stops) > 0) {
$last = $stops[\count($stops) - 1];
$r = (int) ($last[1] * 255);
$g = (int) ($last[2] * 255);
$b = (int) ($last[3] * 255);
$hex = ($r << 16) | ($g << 8) | $b;
}
return new self($hex);
}
}
final class StrokeParams
{
public int $thickness = 1;
public function thickness(int $t): self
{
$this->thickness = $t;
return $this;
}
}
final class FontDescriptor
{
public string $family;
public int $size;
public int $weight;
public function __construct(string $family, int $size, int $weight = TextWeight::Normal)
{
$this->family = $family;
$this->size = $size;
$this->weight = $weight;
}
}
/* ----------------------------- helpers -------------------------------- */
/** 0xRRGGBB -> Win32 COLORREF (0xBBGGRR). */
function ddz_rgb(int $hex): int
{
$r = ($hex >> 16) & 0xFF;
$g = ($hex >> 8) & 0xFF;
$b = $hex & 0xFF;
return ($r) | ($g << 8) | ($b << 16);
}
/* ------------------- UTF-8 helpers (no mbstring) ---------------------- */
/**
* Character (code point) length of a UTF-8 string.
* mbstring is not linked into the TypePHP runtime, so we count code points
* with PCRE instead of mb_strlen().
*/
function ddz_utf8_len(string $s): int
{
if ($s === '') {
return 0;
}
return \preg_match_all('/./us', $s);
}
/**
* Safe UTF-8 substring by code points (no mbstring).
* Negative $start/$len behave like mb_substr() (offsets from the end).
*/
function ddz_utf8_substr(string $s, int $start, int $len = 0): string
{
if ($s === '') {
return '';
}
\preg_match_all('/./us', $s, $m);
$chars = $m[0];
$n = \count($chars);
if ($len === 0) {
$len = $n;
}
if ($start < 0) {
$start = \max(0, $n + $start);
}
if ($len < 0) {
$len = \max(0, $n - $start + $len);
}
$slice = \array_slice($chars, $start, $len);
return \implode('', $slice);
}
/* --------------------------- draw context ----------------------------- */
/**
* Mimics libui's DrawContext but renders through Win32 GDI. The single
* constructor argument is the memory-DC handle produced by win_begin_paint().
*/
final class WinDrawContext
{
private int $hdc;
public function __construct(int $hdc)
{
$this->hdc = $hdc;
}
private function colOf($b): int
{
if ($b instanceof Color || $b instanceof Brush) {
return $b->hex;
}
return (int) $b;
}
public function fillRect(float $x, float $y, float $w, float $h, $brush): void
{
win_fill_rect($this->hdc, (int) $x, (int) $y, (int) $w, (int) $h, ddz_rgb($this->colOf($brush)));
}
/** Ellipse centred at (cx, cy) — matches libui semantics. */
public function fillEllipse(float $cx, float $cy, float $w, float $h, $brush): void
{
win_fill_ellipse($this->hdc, (int) $cx, (int) $cy, (int) $w, (int) $h, ddz_rgb($this->colOf($brush)));
}
public function fillRoundedRect(float $x, float $y, float $w, float $h, float $r, $brush): void
{
win_fill_rounded_rect($this->hdc, (int) $x, (int) $y, (int) $w, (int) $h, (int) $r, ddz_rgb($this->colOf($brush)));
}
public function strokeRoundedRect(float $x, float $y, float $w, float $h, float $r, $brush, StrokeParams $stroke): void
{
win_stroke_rounded_rect(
$this->hdc,
(int) $x, (int) $y, (int) $w, (int) $h, (int) $r,
ddz_rgb($this->colOf($brush)),
(int) $stroke->thickness
);
}
/**
* @param int $w width of the alignment box (0 = no alignment)
* @param int $align DrawTextAlign::Left|Center|Right
*/
public function drawString(string $text, FontDescriptor $font, Color $color, float $x, float $y, int $w = 0, int $align = DrawTextAlign::Left): void
{
win_draw_text_ex(
$this->hdc,
(int) $x, (int) $y, $text,
(int) $font->size,
ddz_rgb($color->hex),
$font->weight >= TextWeight::Bold ? 1 : 0,
(int) $w,
(int) $align
);
}
}

@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
/**
* 技能定义 + 效果实现。
*
* 效果分类:
* - onTurnStart 技能:在 armSkill() 时立即由 Skill::apply() 执行(冻结/石化/推牌/偷牌)
* - onPlay / onBombPlayed 技能:在 Game::play() 中通过预置标记附加到那一手牌(跳过/不可拦截/霸气/炸弹+1)
* - 赤犬反击:在 Game::counterBomb() 中处理(不属于 arm/apply 路径)
*/
final class Skill
{
public function __construct(
public string $id,
public string $name,
public string $desc,
public string $trigger, // onTurnStart | onPlay | onBombPlayed
public string $cost, // once | charges:N | haki:N
public string $faction,
) {
}
/** 应用 onTurnStart 类技能(立即生效)。 */
public static function apply(Game $g, int $actor, ?int $target): void
{
$skill = Character::byId($g->players[$actor]->characterId)->skill();
$target ??= self::defaultTarget($g, $actor);
switch ($skill->id) {
case 'aokiji_freeze':
$g->mods['frozen'][$target] = 1;
break;
case 'boa_petrify':
$g->mods['petrified'][$target] = 2;
break;
case 'kuma_push':
self::pushCard($g, $actor, $target);
break;
case 'bigmom_steal':
self::stealCard($g, $actor, $target);
break;
default:
// onPlay / onBombPlayed 类不在 apply 中处理
break;
}
}
private static function defaultTarget(Game $g, int $actor): int
{
// 默认瞄准「手牌最少」的对手(最具威胁)
$best = null;
$bestCount = PHP_INT_MAX;
foreach ([0, 1, 2] as $p) {
if ($p === $actor) {
continue;
}
$c = $g->handCount($p);
if ($c < $bestCount) {
$bestCount = $c;
$best = $p;
}
}
return $best ?? (($actor + 1) % 3);
}
private static function pushCard(Game $g, int $actor, int $target): void
{
$hand = $g->players[$actor]->hand;
if ($hand === []) {
return;
}
// 推出最小的一张
\usort($hand, static fn (Card $a, Card $b) => $a->rank <=> $b->rank);
$card = \array_shift($hand);
$g->players[$actor]->hand = $hand;
$g->players[$target]->hand[] = $card;
Deck::sort($g->players[$target]->hand);
}
private static function stealCard(Game $g, int $actor, int $target): void
{
$th = $g->players[$target]->hand;
if ($th === []) {
return;
}
$idx = \array_rand($th);
$card = $th[$idx];
unset($th[$idx]);
$th = \array_values($th);
$g->players[$target]->hand = $th;
$g->players[$actor]->hand[] = $card;
Deck::sort($g->players[$actor]->hand);
}
}

@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace Yangweijie\Ui2\Games\OnePieceDoudizhu;
/**
* Sound-effects manager (TypePHP / Win32 port).
*
* The original relied on procedural WAV files loaded through the
* Yangweijie\Ui2\System\Audio (miniaudio) bridge, which is not available in a
* statically-compiled TypePHP binary. This port keeps the exact same public
* API used by GameController (instance / trigger / setEnabled / unload …) but
* is a safe no-op, so the game logic and event hooks are unchanged.
*
* To add real audio later, generate assets/audio/*.wav and call
* win_message_beep() (or a miniaudio bridge) from trigger().
*/
final class Sound
{
public const CLICK = 'click';
public const DEAL = 'deal';
public const PLAY = 'play';
public const PASS = 'pass';
public const BOMB = 'bomb';
public const SKILL = 'skill';
public const BID = 'bid';
public const WIN = 'win';
public const LOSE = 'lose';
private const DEFAULT_BINDINGS = [
'click' => self::CLICK,
'deal' => self::DEAL,
'play' => self::PLAY,
'pass' => self::PASS,
'bomb' => self::BOMB,
'skill' => self::SKILL,
'bid' => self::BID,
'win' => self::WIN,
'lose' => self::LOSE,
];
private static ?self $instance = null;
private array $bindings;
private float $volume = 0.8;
private bool $enabled = true;
public function __construct()
{
$this->bindings = self::DEFAULT_BINDINGS;
}
public static function instance(): self
{
return self::$instance ??= new self();
}
public function setVolume(float $v): self
{
$this->volume = max(0.0, min(1.0, $v));
return $this;
}
public function setEnabled(bool $on): self
{
$this->enabled = $on;
return $this;
}
public function isEnabled(): bool
{
return $this->enabled;
}
public function bind(string $event, string $sound): self
{
$this->bindings[$event] = $sound;
return $this;
}
/** Play a sound by name. No-op in this port. */
public function play(string $name): void
{
// Intentionally silent: no audio assets in the compiled binary.
}
/** Fire a named game event; plays the bound sound if any. */
public function trigger(string $event): void
{
$sound = $this->bindings[$event] ?? $event;
$this->play($sound);
}
public function unload(): void
{
// nothing to release
}
public function __destruct()
{
$this->unload();
}
}

@ -0,0 +1,41 @@
<?php
/**
* Win32 API declarations (stub).
*
* C++ (cpp-src/win32.cc) only provides thin wrappers around Win32 APIs and
* GDI drawing primitives. ALL game logic and rendering live in PHP.
*
* These empty declarations tell the TypePHP compiler about the native
* functions; the real implementations are linked from win32.cc.
*/
// ---- Window & message loop ----
function win_create_window(string $title, int $width, int $height): int {}
/** Current client-area size as [width, height]. */
function win_get_client_size(int $hWnd): array {}
function win_show_window(int $hWnd, int $cmdShow): void {}
function win_quit_requested(): bool {}
function win_post_quit(int $exitCode): void {}
/** Returns [type, a, b, c]; empty array when no message pending. */
function win_peek_message(): array {}
function win_get_tick_count(): int {}
function win_message_box(int $hWnd, string $text, string $caption, int $uType): int {}
function win_message_beep(int $type): void {}
// ---- Double-buffered frame ----
function win_begin_paint(int $hWnd): int {}
function win_end_paint(int $hWnd, int $hdc): void {}
// ---- GDI primitives ----
function win_fill_rect(int $hdc, int $x, int $y, int $w, int $h, int $rgb): void {}
function win_draw_block(int $hdc, int $x, int $y, int $size, int $rgb): void {}
function win_draw_line(int $hdc, int $x1, int $y1, int $x2, int $y2, int $rgb): void {}
/** Ellipse centered at (cx, cy) with width/height. */
function win_fill_ellipse(int $hdc, int $cx, int $cy, int $w, int $h, int $rgb): void {}
function win_fill_rounded_rect(int $hdc, int $x, int $y, int $w, int $h, int $radius, int $rgb): void {}
function win_stroke_rounded_rect(int $hdc, int $x, int $y, int $w, int $h, int $radius, int $rgb, int $thickness): void {}
/** Plain ASCII text. */
function win_draw_text(int $hdc, int $x, int $y, string $text, int $fontSize, int $rgb, int $bold): void {}
/** UTF-8 text; align: 0=left, 1=center, 2=right. width=0 means no alignment. */
function win_draw_text_ex(int $hdc, int $x, int $y, string $text, int $fontSize, int $rgb, int $bold, int $width, int $align): void {}

@ -0,0 +1,7 @@
name: onepiece-doudizhu
version: 1.0.0
mode: bin
sources:
- main.php
- ./php-src
- ./cpp-src

@ -1,4 +1,4 @@
name: tpc
name: tpc3
build-mode: bin
version: 0.1.0
cxx-std: c++17
@ -7,21 +7,21 @@ cxx-flags:
# Windows 资源文件配置(图标、版本信息等)
# 仅在 Windows 平台编译 bin 模式时生效
resource:
# 图标文件路径(相对于 project.yml 所在目录,也支持绝对路径)
icon: swoole-logo.ico
# 版本信息
version-info:
file-version: 0.1.0.1052
product-version: 0.1.0
company-name: "上海识沃网络科技有限公司"
file-description: "TypePHP Compiler"
internal-name: "typephp"
legal-copyright: "Copyright (C) 2026 上海识沃网络科技有限公司"
legal-trademarks: "Swoole is a trademark of 上海识沃网络科技有限公司"
original-filename: "tpc.exe"
product-name: "TypePHP"
comments: "PHP 原生编译器,可将 PHP 项目编译为 Windows/Linux/macOS 平台原生的可执行文件"
# resource: (临时注释:绕过 RC2135,待带修复的 tpc.exe 重新编译后恢复)
# # 图标文件路径(相对于 project.yml 所在目录,也支持绝对路径)
# icon: swoole-logo.ico
# # 版本信息
# version-info:
# file-version: 0.1.0.1052
# product-version: 0.1.0
# company-name: "上海识沃网络科技有限公司"
# file-description: "TypePHP Compiler"
# internal-name: "typephp"
# legal-copyright: "Copyright (C) 2026 上海识沃网络科技有限公司"
# legal-trademarks: "Swoole is a trademark of 上海识沃网络科技有限公司"
# original-filename: "tpc.exe"
# product-name: "TypePHP"
# comments: "PHP 原生编译器,可将 PHP 项目编译为 Windows/Linux/macOS 平台原生的可执行文件"
# Windows 应用程序清单文件(缺省不携带 manifest)
# 仅在 Windows 平台编译 bin 模式时生效

@ -54,7 +54,10 @@ trait ResourceCompilationTrait
$rcFile = $this->getResourceRcFile();
$rcContent = $generator->generate();
$this->writeFile($rcFile, "\xEF\xBB\xBF" . $rcContent);
// 不写 UTF-8 BOM:rc.exe 在文件头遇到 BOM 字节会导致无法识别首行 `//` 注释,
// 进而把注释文本当成语句解析(RC2135 "file not found")。
// 文件内的中文由 `#pragma code_page(65001)` 正确处理,无需 BOM。
$this->writeFile($rcFile, $rcContent);
$this->climate->info('Generated resource file: ' . $rcFile);
$backend = $this->getCompilerBackend();

@ -87,6 +87,8 @@ use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\NodeAbstract;
use PhpParser\NodeFinder;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\CloningVisitor;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use PhpParser\PhpVersion;
@ -136,6 +138,16 @@ class CompilerBase implements PropertyAccessContext
use LoopVarOptimizer;
use SsaPropOptimizer;
/**
* Variables (and parameters) within the currently compiled function that must
* be emitted as a runtime variant (php::Var). A variable is recorded here when
* it is assigned incompatible types at different points in the body — which is
* perfectly legal in dynamically typed PHP but would otherwise force a strict
* backend to either fatal ("Cannot re-assign") or silently miscompile. The set
* is populated by computeVariantVars() right before the function body is parsed.
*/
protected array $variantVars = [];
public const string DEFAULT_PHP_VERSION = '8.5';
protected const string NATIVE_PROPERTY_VALUE_VAR = 'var';
protected const string NATIVE_PROPERTY_VALUE_DYNAMIC = 'dynamic';
@ -380,6 +392,13 @@ class CompilerBase implements PropertyAccessContext
protected Parser $parser;
protected string $phpVersion = self::DEFAULT_PHP_VERSION;
protected PrettyPrinter $printer;
/**
* Caches raw (un-traversed) AST per source file so prepare() and convert()
* share a single parse pass instead of parsing every file twice.
* @var array<string, array<PhpParser\Node>>
*/
protected array $astParseCache = [];
protected bool $isPhpZts = false; // PHP 是否为线程安全版本
// Windows 平台:保存检测到的 PHP lib 文件路径
@ -879,15 +898,27 @@ class CompilerBase implements PropertyAccessContext
return $this->getIndent() . 'php::appendCallExtraNamedArgs(' . $var . ');' . PHP_EOL;
}
public function writeFile(string $file, string $content): void
/**
* Write a generated file, skipping the write when the on-disk content is
* already identical. Keeping mtime stable for unchanged outputs prevents
* downstream rebuild cascades (header churn) and enables incremental
* object caching.
*
* @return bool true when the file was written, false when content was unchanged
*/
public function writeFile(string $file, string $content): bool
{
$dir = dirname($file);
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
if (is_file($file) && file_get_contents($file) === $content) {
return false;
}
if (!file_put_contents($file, $content)) {
throw new \RuntimeException('Can not write file: ' . $file);
}
return true;
}
public function getIncludeDir(): string
@ -978,6 +1009,7 @@ class CompilerBase implements PropertyAccessContext
$this->context = new FunctionContext();
$this->function = '';
$this->functionDef = null;
$this->variantVars = [];
}
protected function resetMethod(): void
@ -3826,6 +3858,48 @@ class CompilerBase implements PropertyAccessContext
return $phpCode;
}
/**
* Parse PHP source code into an AST, reusing a cached raw AST when the same
* file is parsed again (prepare + convert each used to parse once).
*
* The returned AST is always a deep clone of the cached raw tree. Visitors
* used by the pipeline (NameResolver, RuntimeAttributeFactoryLowering,
* Visitor) mutate nodes in place and hold per-traversal state, so every
* consumer must operate on its own copy.
*
* @param string $phpCode Source code previously read by loadFile().
* @return array<PhpParser\Node>
* @throws SyntaxError
*/
protected function parseCachedAst(string $phpCode): array
{
$cacheKey = $this->file;
if (!isset($this->astParseCache[$cacheKey])) {
try {
$this->astParseCache[$cacheKey] = $this->parser->parse($phpCode);
} catch (\PhpParser\Error $e) {
$this->climate->red("Fatal error: {$e->getMessage()} in {$this->file}");
throw new SyntaxError($e->getMessage(), $e->getCode());
}
}
return $this->cloneAst($this->astParseCache[$cacheKey]);
}
/**
* Deep-clone an AST so that a shared cached tree can be traversed by
* multiple stateful visitors without cross-contamination.
*
* @param array<PhpParser\Node> $stmts
* @return array<PhpParser\Node>
*/
protected function cloneAst(array $stmts): array
{
$traverser = new NodeTraverser();
$traverser->addVisitor(new CloningVisitor());
return $traverser->traverse($stmts);
}
protected function parseErrorSuppress(Expr\ErrorSuppress $expr): string
{
$tmpVar = $this->genTmpVarName();
@ -3883,6 +3957,33 @@ class CompilerBase implements PropertyAccessContext
$this->fatalError($left, "Cannot re-assign $varName from `{$fromType}` to `{$toType}`");
}
/**
* Mirror of checkVarAssignExpr()'s compatibility rules without the fatal side
* effect. Returns true when a variable of type $existing may legally receive a
* value of type $new (used by computeVariantVars() to decide whether a variable
* must be promoted to a runtime variant).
*/
protected function areTypesAssignable(string $existing, string $new): bool
{
if ($existing === Type::VAR or $new === Type::VAR) {
return true;
}
if ($existing === Type::REF or $new === Type::REF) {
return true;
}
if ($existing === $new) {
return true;
}
if ($this->isNativeType($existing) and $this->isNativeType($new)) {
return true;
}
$bigTypes = [Type::BIGINT, Type::DECIMAL, Type::BIGFLOAT];
if (in_array($existing, $bigTypes, true) or in_array($new, $bigTypes, true)) {
return true;
}
return false;
}
/**
* Check a value against a composite PHP type when the value's static type
* is precise enough to prove a mismatch. Composite declarations still use

@ -105,8 +105,8 @@ class ResourceFileGenerator
public function generate(): string
{
$content = '';
$content .= '// Generated by TypePHP - Windows Resource File' . PHP_EOL;
$content .= '// DO NOT EDIT - This file is auto-generated' . PHP_EOL;
$content .= '; Generated by TypePHP - Windows Resource File' . PHP_EOL;
$content .= '; DO NOT EDIT - This file is auto-generated' . PHP_EOL;
$content .= PHP_EOL;
// 告诉 rc.exe 此文件使用 UTF-8 编码,避免中文乱码
@ -121,7 +121,7 @@ class ResourceFileGenerator
$manifestPath = $this->getManifestPath();
if ($manifestPath) {
$manifestPathRc = str_replace('\\', '/', $manifestPath);
$content .= '// Manifest Resource' . PHP_EOL;
$content .= '; Manifest Resource' . PHP_EOL;
$content .= 'CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "' . addslashes($manifestPathRc) . '"' . PHP_EOL;
$content .= PHP_EOL;
}
@ -131,7 +131,7 @@ class ResourceFileGenerator
if ($iconPath) {
// 使用正斜杠,Windows RC 编译器更兼容
$iconPathRc = str_replace('\\', '/', $iconPath);
$content .= '// Icon Resource' . PHP_EOL;
$content .= '; Icon Resource' . PHP_EOL;
$content .= 'MAINICON ICON "' . addslashes($iconPathRc) . '"' . PHP_EOL;
$content .= PHP_EOL;
}
@ -154,7 +154,7 @@ class ResourceFileGenerator
$productVersion = $info['product-version'] ?? $fileVersion;
$content = '';
$content .= '// Version Information' . PHP_EOL;
$content .= '; Version Information' . PHP_EOL;
$content .= '1 VERSIONINFO' . PHP_EOL;
$content .= 'FILEVERSION ' . $this->formatVersionDots($fileVersion) . PHP_EOL;
$content .= 'PRODUCTVERSION ' . $this->formatVersionDots($productVersion) . PHP_EOL;

@ -331,6 +331,12 @@ trait AssignOpTrait
// 变量第一次被赋值,确定其类型,由于 PHP 的变量作用域是 function 级的,在 for/while 块中声明的变量,可以在块外使用
if (!$this->hasVar($var)) {
$finalVarType = $this->getNormalAssignType($type);
// A variable that is later reassigned an incompatible type must be
// declared as a runtime variant (php::Var) up front, otherwise the
// generated C++ would mismatch the scalar declaration.
if (isset($this->variantVars[$var])) {
$finalVarType = Type::VAR;
}
$finalVarType = $this->isNativeType($finalVarType) ? $this->getNativeType($finalVarType) : $finalVarType;
$this->addLocalVar($var, $finalVarType);
} else {

@ -126,12 +126,7 @@ class Preprocessor extends CompilerBase
$this->resetNamespace();
$this->climate->info('prepare: ' . $this->getRelativePath($this->file));
try {
$ast = $this->parser->parse($phpCode);
} catch (\PhpParser\Error $e) {
$this->climate->red("Fatal error: {$e->getMessage()} in {$this->file}");
throw new SyntaxError($e->getMessage(), $e->getCode());
}
$ast = $this->parseCachedAst($phpCode);
$this->stubImportLibrary = $this->stubFile && $this->hasLibraryImportAnnotation($ast)
? $this->getExternalImportLibraryName($this->file)
@ -700,7 +695,14 @@ class Preprocessor extends CompilerBase
$flags = Modifiers::PUBLIC;
}
if (isset($this->symbolDeclInFile[$fullClassNameLower])) {
$this->fatalError($class, "Duplicate class `{$fullClassName}`");
// AOT-tolerant duplicate handling: PHP autoloading only ever loads
// one declaration of a given FQN at runtime, so cross-file
// duplicates (common in frameworks like ThinkPHP, where several
// packages ship an identical copy of the same class) are not a
// fatal error. Keep the first declaration and skip the rest.
$this->warning($class, "Duplicate class `{$fullClassName}` (also declared in {$this->symbolDeclInFile[$fullClassNameLower]}); keeping the first declaration.");
$this->resetClass();
return '';
}
$this->classDef = new ClassDef($this->class, $flags, $this->namespace);
@ -1510,7 +1512,10 @@ class Preprocessor extends CompilerBase
}
if (isset($this->symbolDeclInFile[$interfaceNameLower])) {
$this->fatalError($v, "Duplicate interface `{$interfaceName}`");
// Same AOT-tolerant handling as classes: keep the first declaration
// of a duplicated interface FQN and skip the rest.
$this->warning($v, "Duplicate interface `{$interfaceName}` (also declared in {$this->symbolDeclInFile[$interfaceNameLower]}); keeping the first declaration.");
return;
}
$this->symbolDeclInFile[$interfaceNameLower] = $this->file;

@ -495,8 +495,10 @@ class Translator extends Preprocessor
public function save(string $code, string $file): void
{
$this->writeFile($file, $code);
$this->formatCppCode($file);
$written = $this->writeFile($file, $code);
if ($written) {
$this->formatCppCode($file);
}
}
public function convertFile(string $file): string
@ -1095,8 +1097,9 @@ CODE;
$this->indentLevel--;
$this->writeFile($file, $code);
$this->formatCppCode($file);
if ($this->writeFile($file, $code)) {
$this->formatCppCode($file);
}
$this->localHeaders = [];
return $file;
}
@ -1143,11 +1146,8 @@ CODE;
if (!is_dir($dir)) {
continue;
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->getExtension() === 'h' && $file->getMTime() > $objectMtime) {
foreach ($this->collectHeaderFiles($dir) as $header) {
if (filemtime($header) > $objectMtime) {
return false;
}
}
@ -1156,6 +1156,37 @@ CODE;
return true;
}
/**
* Recursively collect every .h header under $dir using scandir (no SPL
* iterators). Avoids AOT-miscompiled RecursiveDirectoryIterator.
*
* @return list<string>
*/
protected function collectHeaderFiles(string $dir): array
{
$result = [];
if (!is_dir($dir)) {
return $result;
}
$stack = [$dir];
while (!empty($stack)) {
$current = array_pop($stack);
$entries = scandir($current);
foreach ($entries as $name) {
if ($name === '.' || $name === '..') {
continue;
}
$path = $current . '/' . $name;
if (is_dir($path)) {
$stack[] = $path;
} elseif (str_ends_with($name, '.h')) {
$result[] = $path;
}
}
}
return $result;
}
protected function getMiscObjectCacheMetadataFile(string $objectFile): string
{
return $objectFile . '.typephp-cache';
@ -1191,6 +1222,117 @@ CODE;
}
}
/**
* Check whether a compiler-generated .cc file has a valid object cache.
* Mirrors hasMiscObjectFileCache() but applies to generated translation
* units, whose compiled output depends on the generated headers they
* include (func_decl.h / data_decl.h / arginfo / extension).
*
* The cache key incorporates the compile command, the PHP ABI, and the
* content of every generated header, so any change to the generated
* surface (including headers another file changed) invalidates it.
*/
public function hasGeneratedObjectFileCache(string $cppFile): bool
{
if ($this->climate->arguments->defined('force') || $this->enableProfiler) {
return false;
}
$objectFile = $this->getObjectFile($cppFile);
if (!is_file($objectFile)) {
return false;
}
$metadataFile = $this->getMiscObjectCacheMetadataFile($objectFile);
if (!is_file($metadataFile)) {
return false;
}
$cachedKey = file_get_contents($metadataFile);
if ($cachedKey === false || trim($cachedKey) !== $this->getGeneratedObjectCacheKey($cppFile, $objectFile)) {
return false;
}
$objectMtime = filemtime($objectFile);
if ($objectMtime <= filemtime($cppFile)) {
return false;
}
foreach ($this->getGeneratedHeaderDependencies() as $header) {
if (is_file($header) && filemtime($header) > $objectMtime) {
return false;
}
}
return true;
}
/**
* @return list<string> generated headers that every generated .cc depends on.
*
* Every generated translation unit includes the project func_decl.h and
* data_decl.h (plus the phpx headers handled by the misc cache). The
* per-file _arginfo.h headers are included by the extension unit, so they
* are part of the shared surface too. extension-<target>.cc is NOT listed:
* its content tracks any class change and would invalidate every .cc,
* defeating incremental builds. It is itself a generated unit and gets its
* own cache entry via isGeneratedSourceFile().
*/
protected function getGeneratedHeaderDependencies(): array
{
$includeDir = $this->getIncludeDir();
$headers = [
$includeDir . '/php_' . $this->targetName . '_func_decl.h',
$includeDir . '/php_' . $this->targetName . '_data_decl.h',
];
$prefix = 'php_' . $this->targetName . '_';
if (is_dir($includeDir)) {
$entries = scandir($includeDir);
foreach ($entries as $name) {
if ($name === '.' || $name === '..') {
continue;
}
if (str_starts_with($name, $prefix) && str_ends_with($name, '_arginfo.h')) {
$headers[] = $includeDir . '/' . $name;
}
}
}
sort($headers, SORT_STRING);
return $headers;
}
protected function getGeneratedObjectCacheKey(string $sourceFile, string $objectFile): string
{
$parts = [];
$parts[] = $this->buildCompileFileCommand($sourceFile, $objectFile);
$parts[] = serialize([
'php_version_id' => PHP_VERSION_ID,
'php_api_version' => defined('PHP_API_VERSION') ? constant('PHP_API_VERSION') : null,
'zend_module_api' => defined('ZEND_MODULE_API_NO') ? constant('ZEND_MODULE_API_NO') : null,
'php_zts' => defined('PHP_ZTS') ? PHP_ZTS : null,
'php_debug' => defined('PHP_DEBUG') ? PHP_DEBUG : null,
'integer_size' => PHP_INT_SIZE,
]);
foreach ($this->getGeneratedHeaderDependencies() as $header) {
$parts[] = $header;
$parts[] = is_file($header) ? hash_file('sha256', $header) : '';
}
return hash('sha256', implode("\0", $parts));
}
protected function writeGeneratedObjectCacheMetadata(string $sourceFile, string $objectFile): void
{
$metadataFile = $this->getMiscObjectCacheMetadataFile($objectFile);
if (file_put_contents($metadataFile, $this->getGeneratedObjectCacheKey($sourceFile, $objectFile) . PHP_EOL) === false) {
throw new \RuntimeException('Cannot write generated object cache metadata: ' . $metadataFile);
}
}
protected function invalidateGeneratedObjectCache(string $objectFile): void
{
$this->invalidateMiscObjectCache($objectFile);
}
public function isPhpxMiscFile(string $cppFile): bool
{
$miscDir = $this->getPhpxDir() . '/src/misc/';
@ -1235,18 +1377,14 @@ CODE;
public function compileFile(string $cppFile, string $objectFile, bool $parallel = false): void
{
if ($this->isPhpxMiscFile($cppFile) && $this->hasMiscObjectFileCache($cppFile)) {
if (!$parallel) {
$this->climate->darkGray('[cache] skip: ' . $cppFile);
}
return;
}
$isMiscFile = $this->isPhpxMiscFile($cppFile);
if ($isMiscFile) {
$this->invalidateMiscObjectCache($objectFile);
}
// S2 (incremental object cache) is intentionally DISABLED here.
// When this wrapper code is AOT-compiled by v1095, the compiler
// miscompiles this region and the subsequent getNativeBuilder()
// ->compile() call, producing a ~2^47-byte VirtualAlloc failure at
// runtime (see the OOM crash on hello.cc). This is a v1095 AOT
// miscompilation bug, not a logic bug. The S2 helper methods below
// are kept for reuse once a fixed compiler is available.
// (Details in .workbuddy/memory/2026-08-04.md.)
$language = $this->getLanguageFromExtension($cppFile);
$options = match ($language) {
null => $this->getCompileCommandOptions(),
@ -1265,10 +1403,20 @@ CODE;
}
$this->error('compile failed: ' . $cppFile);
}
}
if ($isMiscFile) {
$this->writeMiscObjectCacheMetadata($cppFile, $objectFile);
/**
* True for compiler-generated .cc translation units (as opposed to phpx
* misc sources and user-provided native sources). These live under the
* build directory and are safe to cache by generated-header fingerprint.
*/
protected function isGeneratedSourceFile(string $cppFile): bool
{
if ($this->isPhpxMiscFile($cppFile) || !$this->isCppFile($cppFile)) {
return false;
}
$buildDir = rtrim($this->getBuildDir(), '/\\') . DIRECTORY_SEPARATOR;
return str_starts_with($cppFile, $buildDir);
}
protected function buildCompileFileCommand(string $sourceFile, string $objectFile): string
@ -2322,7 +2470,7 @@ CODE;
{
$this->climate->info('convert: ' . $this->getRelativePath($this->file));
$ast = $this->parser->parse($phpCode);
$ast = $this->parseCachedAst($phpCode);
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver(null, ['replaceNodes' => false]));
$traverser->addVisitor(new Visitor(sourceFile: $this->file));
@ -3422,6 +3570,101 @@ CODE;
/**
* @throws \Exception
*/
/**
* Pre-scan a function/method body to find variables (including parameters)
* that are assigned incompatible types. PHP is dynamically typed, so a single
* variable may legitimately hold a string at one point and an array later; the
* AOT backend must declare such variables as a runtime variant (php::Var) so the
* generated C++ is valid. Without this, re-assigning a scalar-typed variable to a
* different type would either fatal ("Cannot re-assign") or silently miscompile.
*
* @return array<string, true>
*/
protected function computeVariantVars(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): array
{
$types = [];
// Seed with parameter declared types so a reassignment of a parameter is caught.
foreach ($this->functionDef->argInfoList as $argInfo) {
if (!$argInfo->variadic) {
$types[$argInfo->name][] = $argInfo->type;
}
}
$this->collectAssignTypes($v->stmts, $types);
$variant = [];
foreach ($types as $name => $assigned) {
$unique = array_values(array_unique($assigned));
if (count($unique) < 2) {
continue;
}
$conflict = false;
foreach ($unique as $i => $a) {
foreach ($unique as $j => $b) {
if ($i === $j) {
continue;
}
if (!$this->areTypesAssignable($a, $b)) {
$conflict = true;
break 2;
}
}
}
if ($conflict) {
$variant[$name] = true;
}
}
return $variant;
}
/**
* @param array<int, mixed> $nodes
* @param array<string, list<string>> $types
*/
protected function collectAssignTypes(array $nodes, array &$types): void
{
foreach ($nodes as $node) {
$this->collectAssignTypesNode($node, $types);
}
}
/**
* Recursively walk a statement list, recording the RHS type of every simple
* variable assignment. Descent stops at nested function-like nodes (closures,
* nested functions/methods) because those own their own variable scope.
*
* @param array<string, list<string>> $types
*/
protected function collectAssignTypesNode($node, array &$types): void
{
if ($node === null || is_scalar($node) || is_array($node)) {
return;
}
if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod
|| $node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) {
return;
}
if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignRef || $node instanceof Node\Expr\AssignOp) {
$lhs = $node->var;
if ($lhs instanceof Node\Expr\Variable && is_string($lhs->name) && $lhs->name !== 'this') {
$rhsType = $this->detectTypeOfExpr($node->expr);
if ($rhsType === Type::VOID) {
$rhsType = Type::VAR;
}
$types[$lhs->name][] = $rhsType;
}
}
if ($node instanceof Node) {
foreach ($node->getSubNodeNames() as $key) {
$sub = $node->$key;
if (is_array($sub)) {
$this->collectAssignTypes($sub, $types);
} elseif ($sub instanceof Node) {
$this->collectAssignTypesNode($sub, $types);
}
}
}
}
protected function parseFunction(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): string
{
$this->resetFunction();
@ -3484,6 +3727,24 @@ CODE;
$this->context->resetAnalysisTemporaries($oriLocalVars, $oriTmpVarIndex, $oriDeclaredObjects);
}
// Promote variables/parameters that are reassigned incompatible types to
// runtime variants so the generated C++ is valid (PHP is dynamically typed).
$this->variantVars = $this->computeVariantVars($v);
if ($this->variantVars !== []) {
$list = [];
foreach ($this->functionDef->argInfoList as $argInfo) {
if (isset($this->variantVars[$argInfo->name])) {
$argInfo->type = Type::VAR;
}
if ($argInfo->variadic) {
$list[] = Type::ARRAY . ' ' . $argInfo->name;
} else {
$list[] = $this->genArgumentDeclaration($argInfo);
}
}
$this->functionDef->params = implode(', ', $list);
}
$stmts = '';
if ($v->stmts) {
$this->indentLevel++;

Loading…
Cancel
Save