Compare commits
8 Commits
master
...
speed_buil
| Author | SHA1 | Date |
|---|---|---|
|
|
eb523afbf0 | 3 weeks ago |
|
|
e0581f8ccc | 3 weeks ago |
|
|
fbaac125ae | 3 weeks ago |
|
|
e79ec2cf73 | 3 weeks ago |
|
|
7fa91622e2 | 3 weeks ago |
|
|
473bd284ab | 3 weeks ago |
|
|
ac7813d7b2 | 3 weeks ago |
|
|
449afc1516 | 3 weeks ago |
79 changed files with 9408 additions and 55 deletions
@ -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["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() 并行"]</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["main()"] --> B["RINIT: php_app_init()"] |
||||||
|
B --> C["php::eval('main($argc,$argv)')"] |
||||||
|
C --> D["RSHUTDOWN: php_app_clean()"]</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_<NAME>_API</code> 导出宏;</li><li>生成 <code><target>.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, 'ClassName')</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-<target>.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["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"]</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 &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_<LIB>_IMPORT</code> 宏,库导出函数用 <code>TYPEPHP_<NAME>_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_<target>_func_decl.h</code>:所有函数前向声明</td></tr><tr><td><code>genDataDeclarations()</code></td><td><code>php_<target>_data_decl.h</code>:全局变量 / 常量 / 类映射表声明</td></tr><tr><td><code>genExtension()</code></td><td><code>extension-<target>.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><target>.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><?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>< > ==</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('sha256', |
||||||
|
buildCompileFileCommand(source, object) // 可复现的编译命令行 |
||||||
|
. "\0" . serialize($abi) // PHP ABI 快照 |
||||||
|
[. "\0" . header . "\0" . hash_file(header)] // 仅生成单元:逐头文件内容 |
||||||
|
)</code></pre> |
||||||
|
<p>键写入 <code><object>.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-<target>.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><?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 <level></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>?-></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->addVisitor(new NameResolver(...)); // 全限定名解析 |
||||||
|
$traverser->addVisitor(new Visitor(...)); // Transform lowering(见 transform) |
||||||
|
$traverser->addVisitor(new ConstantExpressionValidationVisitor(...)); |
||||||
|
$traverser->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["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</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->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><?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; |
||||||
|
}</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>['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']</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, 'ClassName')</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
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -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 |
||||||
Loading…
Reference in new issue