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 | 4 weeks ago |
|
|
449afc1516 | 4 weeks ago |
1046 changed files with 12805 additions and 80506 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,274 @@ |
|||||||
|
# Code Reuse Improvement Plan |
||||||
|
|
||||||
|
## Analysis Summary |
||||||
|
|
||||||
|
| Metric | Value | |
||||||
|
|--------|-------| |
||||||
|
| Total source lines | ~11,000 (PHP only) | |
||||||
|
| CompilerBase | 5,917 lines, 269 methods, 20 traits | |
||||||
|
| Gcc↔Clang duplication | ~70-80% of methods | |
||||||
|
| Linux↔Macos duplication | ~80% of methods | |
||||||
|
| `fatalError()` call sites | 174+ across codebase | |
||||||
|
| Test setUp/tearDown dup | 4+ test classes | |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 1: High-Impact Backend/Platform Deduplication (P0) |
||||||
|
|
||||||
|
### 1.1 Extract `UnixPlatform` base class |
||||||
|
|
||||||
|
**Files**: `Platform/Linux.php` (249 lines), `Platform/Macos.php` (278 lines) |
||||||
|
|
||||||
|
These 13 methods are 100% identical between Linux and Macos: |
||||||
|
- `getIncludeFlags()`, `getLibraryPathFlags()`, `getObjectExtension()`, `getExecutableExtension()`, `getPathSeparator()`, `getPhpDir()`, `getRpathOptions()`, `getPicFlag()`, `buildPhpIncludePaths()`, `findPhpConfig()`, `buildPhpLibPaths()` |
||||||
|
|
||||||
|
Near-identical with minor parameterization: |
||||||
|
- `getLibraryFlags()` — only the regex differs (`.a|.so` vs `.a|.dylib`) |
||||||
|
- `detectPhpLibs()` — only the lib name differs (`libphp.so` vs `libphp.dylib`) |
||||||
|
|
||||||
|
**Plan**: Create `UnixPlatform extends PlatformBase` between `PlatformBase` and `Linux`/`Macos`. Move all identical methods up. Add abstract `getSharedLibraryExtension()` (already exists) and a protected `getSharedLibName()` for the single differing method. |
||||||
|
|
||||||
|
**Expected savings**: ~180 lines removed, ~150 lines added = net ~30 lines but massive maintainability gain. |
||||||
|
|
||||||
|
### 1.2 Extract `GccLikeBackend` base class |
||||||
|
|
||||||
|
**Files**: `Backend/Gcc.php` (379 lines), `Backend/Clang.php` (515 lines) |
||||||
|
|
||||||
|
These methods are structurally identical with only Windows-specific branching: |
||||||
|
- `compileFile()`, `linkObjects()`, `buildCompileCommand()`, `buildCCompileCommand()`, `buildNativeCompileCommand()`, `buildLinkCommand()`, `buildCompileOptions()`, `buildLinkOptions()`, `buildFullCompileOptions()`, `buildFullLinkOptions()` |
||||||
|
|
||||||
|
**Plan**: Create `GccLikeBackend extends CompilerBackend` with all shared logic. Define template-method hooks for the differences: |
||||||
|
- `getCompilerSpecificFlags()` — empty for Gcc, MSVC compat flags for Clang/Windows |
||||||
|
- `getOutputFlag($isWindows)` — `-o` vs `/OUT:` |
||||||
|
- `getSanitizerFlag($type)` — handle the `address`/`addr` aliasing difference |
||||||
|
- `getPICHandling($config)` — Gcc always adds `-fPIC`, Clang skips on Windows |
||||||
|
|
||||||
|
**Expected savings**: ~250+ lines removed from Gcc.php and Clang.php. Msvc.php is sufficiently different (different flag syntax) to remain standalone. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 2: CompilerBase Internal Deduplication (P1) |
||||||
|
|
||||||
|
### 2.1 Consolidate Big* type dispatch in BinaryOpTrait |
||||||
|
|
||||||
|
**File**: `Parser/BinaryOpTrait.php` (lines 31-107) |
||||||
|
|
||||||
|
The three blocks for BigFloat (lines 31-54), Decimal (lines 56-76), and BigInt (lines 78-107) in `parseBinaryOp()` share identical structure: |
||||||
|
1. Check if either operand is the big type |
||||||
|
2. Guard against incompatible mixing |
||||||
|
3. Convert the non-matching operand |
||||||
|
4. Dispatch to arithmetic or comparison operator |
||||||
|
|
||||||
|
**Plan**: Extract `parseBigNumBinaryOp(string $type, string $left, string $right, ...)` parameterized by type name, conversion function, and operator maps. Same refactoring applies to `genBigNumericCmp()` (lines 315-355). |
||||||
|
|
||||||
|
**Expected savings**: ~40 lines. |
||||||
|
|
||||||
|
### 2.2 Data-driven operator dispatch tables |
||||||
|
|
||||||
|
**Files**: `Parser/BinaryOpTrait.php` (16 wrapper methods), `Parser/AssignOpTrait.php` (14 wrapper methods) |
||||||
|
|
||||||
|
30+ thin methods that are just `parseBinaryOp($left, $right, '+')` / `parseAssignOp($node, '+=')`. |
||||||
|
|
||||||
|
**Plan**: Replace with a static map in `parseExpr()`: |
||||||
|
```php |
||||||
|
private const BINARY_OP_MAP = [ |
||||||
|
'Expr_BinaryOp_Plus' => '+', |
||||||
|
'Expr_BinaryOp_Minus' => '-', |
||||||
|
// ... |
||||||
|
]; |
||||||
|
private const ASSIGN_OP_MAP = [ |
||||||
|
'Expr_AssignOp_Plus' => '+=', |
||||||
|
// ... |
||||||
|
]; |
||||||
|
``` |
||||||
|
|
||||||
|
**Expected savings**: ~200 lines removed (boilerplate method bodies). |
||||||
|
|
||||||
|
### 2.3 Deduplicate call dispatch patterns |
||||||
|
|
||||||
|
**Files**: `CompilerBase.php` (`parseFuncCall`, `parseMethodCall`, `parseStaticCall` — ~300 lines combined), `UniversalMethodCall.php` (`tryOptimizePhpFn` vs `dispatchFuncCall`) |
||||||
|
|
||||||
|
These share the same overall flow: resolve callable → try native/optimized path → on `PlaceHolder` fall back to placeholder → parse args → wrap in `php::call()`. Additionally, `tryOptimizePhpFn()` (UniversalMethodCall lines 720-772) duplicates the argument type conversion logic already present in `dispatchFuncCall()` (FuncCallOptimizer lines 234-269). |
||||||
|
|
||||||
|
**Plan**: Extract a shared `resolveCall(CallLike $expr, ...)` method. Unify arg conversion so `tryOptimizePhpFn` delegates to `dispatchFuncCall` instead of reimplementing it. |
||||||
|
|
||||||
|
**Expected savings**: ~40 lines, fixes double-calculation of arg conversions. |
||||||
|
|
||||||
|
### 2.4 Deduplicate UNIVERSAL_METHODS math entries |
||||||
|
|
||||||
|
**File**: `UniversalMethodCall.php` (lines 12-69) |
||||||
|
|
||||||
|
The INT block (lines 12-41) and FLOAT block (lines 42-69) contain 20 identical math method entries (`abs`, `ceil`, `floor`, `sqrt`, `sin`, `cos`, etc.) differing only in `return_type`. Also the `calc_op` entries (add/sub/mul/div) are duplicated. |
||||||
|
|
||||||
|
**Plan**: Define math method names once in a shared array, generate both INT and FLOAT entries in the constructor with the appropriate `return_type`. |
||||||
|
|
||||||
|
**Expected savings**: ~25 lines of config data. |
||||||
|
|
||||||
|
### 2.5 Deduplicate constant folding methods |
||||||
|
|
||||||
|
**File**: `Optimizer/FuncCallOptimizer.php` (lines 515-593) |
||||||
|
|
||||||
|
8 methods (`doFoldStringLen`, `doFoldStringCase`, `doFoldCmp2`, `doFoldCmp3`, `doFoldCountLiteral`, `doFoldKnownClass`, `doFoldKnownConstant`, `doFoldSsaType`) follow the identical pattern: extract args → check types → compute → return literal or false. |
||||||
|
|
||||||
|
**Plan**: Create a generic `tryFold(callable $check, callable $compute)` that handles the arg extraction and short-circuit boilerplate. Each folder becomes a one-liner. |
||||||
|
|
||||||
|
**Expected savings**: ~50 lines. |
||||||
|
|
||||||
|
### 2.6 Remove MSVC compat flag duplication in Clang |
||||||
|
|
||||||
|
**File**: `Backend/Clang.php` |
||||||
|
|
||||||
|
The 4-line MSVC compatibility block (`-fms-compatibility`, `-fms-compatibility-version=19.40`, `-fdelayed-template-parsing`, `-fms-extensions`) appears 7 times (compileFile, buildCompileCommand, buildCCompileCommand, buildNativeCompileCommand, buildFullCompileOptions, buildCompileOptions, buildLinkOptions). |
||||||
|
|
||||||
|
**Plan**: Extract `private function getMsvcCompatFlags(): string` method. Called once per method that needs it instead of repeated inline. |
||||||
|
|
||||||
|
**Expected savings**: ~24 lines, single point of change if MSVC compat flags need updating. |
||||||
|
|
||||||
|
### 2.7 Merge return-check blocks |
||||||
|
|
||||||
|
**File**: `CompilerBase.php`, `parseReturn()` (line 1621) and `genReturnCode()` (line 5741) |
||||||
|
|
||||||
|
Identical 7-line union type check blocks. |
||||||
|
|
||||||
|
**Plan**: Extract `genUnionReturnWrapper(string $exprVar)` method. |
||||||
|
|
||||||
|
**Expected savings**: ~10 lines, eliminates drift risk. |
||||||
|
|
||||||
|
### 2.8 Fix `buildCCompileCommand()` inconsistency between Gcc and Clang |
||||||
|
|
||||||
|
**Files**: `Backend/Gcc.php` (lines 131-137), `Backend/Clang.php` (lines 198-206) |
||||||
|
|
||||||
|
Gcc unconditionally appends `-O$level` then conditionally appends `-g`. Clang treats debug and optimization as mutually exclusive (`if debug: -O0 -g` else `-O$level`). This is a behavioral inconsistency between backends implementing the same abstract method. |
||||||
|
|
||||||
|
**Plan**: Standardize on one behavior (the Clang pattern of `-O0 -g` for debug is the correct one — debug builds should not optimize). This will be automatically resolved by Phase 1.2 (GccLikeBackend). |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 3: Structural Improvements (P2) |
||||||
|
|
||||||
|
### 3.1 Entity flag-check consistency |
||||||
|
|
||||||
|
**File**: `Entity/PropertyDef.php` has `isPrivate()`, `isProtected()`, `isPublic()`, `isStatic()`. `Entity/MethodDef.php` has none — flag checks are done inline in CompilerBase. |
||||||
|
|
||||||
|
**Plan**: Add a `HasFlags` trait used by both `PropertyDef` and `MethodDef`: |
||||||
|
```php |
||||||
|
trait HasFlags { |
||||||
|
public function isPrivate(): bool { return $this->flags & Modifiers::PRIVATE; } |
||||||
|
public function isProtected(): bool { return $this->flags & Modifiers::PROTECTED; } |
||||||
|
public function isPublic(): bool { return !$this->isPrivate() && !$this->isProtected(); } |
||||||
|
public function isStatic(): bool { return $this->flags & Modifiers::STATIC; } |
||||||
|
public function isAbstract(): bool { return $this->flags & Modifiers::ABSTRACT; } |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Expected savings**: Removes inline flag checks from CompilerBase, adds clarity. |
||||||
|
|
||||||
|
### 3.2 Test infrastructure base class |
||||||
|
|
||||||
|
**Files**: `phpunit/src/AstNodeTypeTest.php`, `CompilerBaseAdapterTest.php`, `TraitsTest.php`, `PreprocessorTest.php` |
||||||
|
|
||||||
|
All 4 duplicate the same setUp/tearDown pattern: create temp dir, `CompilerTest::create()`, recursive cleanup. |
||||||
|
|
||||||
|
**Plan**: Add `CompilerTestCase extends \PHPUnit\Framework\TestCase` to `phpunit/bootstrap.php`: |
||||||
|
```php |
||||||
|
abstract class CompilerTestCase extends TestCase { |
||||||
|
protected string $tmpDir; |
||||||
|
protected CompilerTest $compiler; |
||||||
|
|
||||||
|
protected function setUp(): void { |
||||||
|
parent::setUp(); |
||||||
|
$this->tmpDir = sys_get_temp_dir() . '/compiler_test_' . uniqid(); |
||||||
|
mkdir($this->tmpDir, 0777, true); |
||||||
|
$this->compiler = CompilerTest::create($this->tmpDir); |
||||||
|
} |
||||||
|
|
||||||
|
protected function tearDown(): void { |
||||||
|
parent::tearDown(); |
||||||
|
// recursive cleanup |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 3.3 Eliminate `buildFull*Options` / `build*Options` duality |
||||||
|
|
||||||
|
**Files**: `Backend/Gcc.php`, `Backend/Clang.php`, `Backend/Msvc.php` |
||||||
|
|
||||||
|
All three backends implement both `buildFullCompileOptions()` / `buildCompileOptions()` and `buildFullLinkOptions()` / `buildLinkOptions()`. The "full" variants are subsets of the "standard" variants working from differently-keyed option arrays. They have drifted independently (e.g., RPATH handling differs between the two in Gcc/Clang). |
||||||
|
|
||||||
|
**Plan**: Make the "full" variants delegate to the "standard" variants by normalizing their option keys once at the call site. Keep only one code path for each (compile/link). |
||||||
|
|
||||||
|
**Expected savings**: ~100+ lines, eliminates drift between the two variants. |
||||||
|
|
||||||
|
### 3.4 Deduplicate Preprocessor AST switch |
||||||
|
|
||||||
|
**File**: `Preprocessor.php` |
||||||
|
|
||||||
|
`prepareFile()` (lines 115-151) and `prepareNamespace()` (lines 196-223) both switch over the same set of AST `Stmt_*` types with nearly identical case bodies. |
||||||
|
|
||||||
|
**Plan**: Extract `processStmt(Node $v)` method that both callers share. |
||||||
|
|
||||||
|
**Expected savings**: ~25 lines. |
||||||
|
|
||||||
|
### 3.5 Remove dead code: `ScopeContext` |
||||||
|
|
||||||
|
**File**: `Context/ScopeContext.php` (7 lines) |
||||||
|
|
||||||
|
An empty class with no properties or methods. Used only as a type annotation in `FunctionContext`. Either populate it with scope-relevant state, or remove it and use plain `\stdClass` / array / null. |
||||||
|
|
||||||
|
### 3.6 Reduce StdContainerTrait coupling |
||||||
|
|
||||||
|
**File**: `Parser/StdContainerTrait.php` (823 lines, 48 methods) |
||||||
|
|
||||||
|
This is effectively a standalone subsystem for std container handling. As a trait, it has unrestricted access to CompilerBase's internals. |
||||||
|
|
||||||
|
**Plan**: Extract core logic into `StdContainerHandler` service class. The trait becomes a thin facade that delegates to the handler. |
||||||
|
|
||||||
|
**Expected savings**: Better testability, clearer boundaries, easier to understand. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 4: Longer-Term Architectural (P3) |
||||||
|
|
||||||
|
### 4.1 Break CompilerBase into domain-specific classes |
||||||
|
|
||||||
|
Currently CompilerBase is a 5,917-line god class using 20 traits as a workaround for PHP's single inheritance. Consider: |
||||||
|
|
||||||
|
- `ExpressionCompiler` — all parseExpr sub-dispatch (~500 lines) |
||||||
|
- `StatementCompiler` — parseStmts, parseIf, parseWhile, parseFor, parseSwitch, etc. |
||||||
|
- `TypeResolver` — parseTypeDecl, detectClassOfExpr, type checking |
||||||
|
- `CallResolver` — parseFuncCall, parseMethodCall, parseStaticCall, parseNew |
||||||
|
|
||||||
|
These would be injected services rather than traits, making CompilerBase a coordinator. |
||||||
|
|
||||||
|
### 4.2 Shared AST walker pattern with Python Translator |
||||||
|
|
||||||
|
Both PHP and Python translators implement the same "walk-collect-indent-emit" pipeline independently. `Core\Translator` could define a standard `walkAst($nodes, callable $visitor)` that handles indentation and line collection. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Implementation Order & Impact Matrix |
||||||
|
|
||||||
|
| # | Item | Savings | Risk | Effort | |
||||||
|
|---|------|---------|------|--------| |
||||||
|
| 1.1 | UnixPlatform base class | ~180 dup lines | Low | 2-3h | |
||||||
|
| 1.2 | GccLikeBackend base class | ~250 dup lines | Medium | 3-4h | |
||||||
|
| 2.1 | BigNum dispatch consolidation | ~40 lines | Low | 1h | |
||||||
|
| 2.2 | Data-driven op dispatch | ~200 lines | Low | 1-2h | |
||||||
|
| 2.3 | Unify call dispatch patterns | ~40 lines | Low | 1-2h | |
||||||
|
| 2.4 | UNIVERSAL_METHODS math dedup | ~25 lines | Low | 30m | |
||||||
|
| 2.5 | Fold method template | ~50 lines | Low | 1h | |
||||||
|
| 2.6 | MSVC compat flags in Clang | ~24 lines | Low | 30m | |
||||||
|
| 2.7 | Merge return-check blocks | ~10 lines | Low | 30m | |
||||||
|
| 2.8 | Fix buildCCompileCommand drift | bug fix | Low | 30m | |
||||||
|
| 3.1 | HasFlags trait | clarity | Low | 1h | |
||||||
|
| 3.2 | CompilerTestCase base class | boilerplate | Low | 1h | |
||||||
|
| 3.3 | Eliminate Full*Options duality | ~100 lines | Medium | 2h | |
||||||
|
| 3.4 | Preprocessor AST switch dedup | ~25 lines | Low | 1h | |
||||||
|
| 3.5 | Remove dead ScopeContext | 7 lines | Low | 15m | |
||||||
|
| 3.6 | StdContainer service class | boundary | Medium | 3-4h | |
||||||
|
| 4.1 | Domain classes | architecture | High | 1-2 weeks | |
||||||
|
| 4.2 | AST walker pattern | architecture | Medium | 3-5h | |
||||||
|
|
||||||
|
**Total estimated savings**: ~950+ lines of duplicated / dead code. |
||||||
|
|
||||||
|
**Recommended execution**: Phase 1 → Phase 2 → Phase 3. Items within each phase are independent and can be parallelized. |
||||||
@ -0,0 +1,325 @@ |
|||||||
|
# Encapsulation Review |
||||||
|
|
||||||
|
## Summary |
||||||
|
|
||||||
|
| Metric | Value | |
||||||
|
|--------|-------| |
||||||
|
| Entity classes with all-public fields | 9/9 (100%) | |
||||||
|
| FunctionContext public properties | 26 (all mutable) | |
||||||
|
| CompilerBase private methods | 5/269 (1.9%) | |
||||||
|
| Traits with direct `$this->context->` access | 6 traits, 60+ sites | |
||||||
|
| ScopeContext (dead code) | 7 lines, empty class | |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Issue 1: Entity classes — all-public mutable fields |
||||||
|
|
||||||
|
**Severity**: High. Every entity class exposes all internal state as public writable properties. External code in Preprocessor/CompilerBase directly mutates them. |
||||||
|
|
||||||
|
### 1.1 ClassDef (18 public properties) |
||||||
|
|
||||||
|
`src/Php/Entity/ClassDef.php` |
||||||
|
|
||||||
|
```php |
||||||
|
public array $methods = []; // externally populated: $classDef->properties[$name] = ... |
||||||
|
public array $properties = []; // externally populated |
||||||
|
public array $constants = []; // externally populated |
||||||
|
public array $implements = []; // externally populated |
||||||
|
public string $extends = ''; // externally set: $this->classDef->extends = ... |
||||||
|
public bool $requireCtor = false; |
||||||
|
public bool $enum = false; |
||||||
|
public ?string $enumBackingType = null; |
||||||
|
public array $enumCases = []; |
||||||
|
public array $abstractMethods = []; |
||||||
|
public ?Trait_ $trait = null; |
||||||
|
public array $traitAliases = []; |
||||||
|
public array $traitIgnored = []; |
||||||
|
public int $flags; // no visibility checks, raw bitmask |
||||||
|
public bool $inheritedFromInternalClass = false; |
||||||
|
public string $ctorInit = ''; // mutated during code generation |
||||||
|
public string $ctorClean = ''; // mutated during code generation |
||||||
|
public FunctionContext $propertyContext; // set after construction |
||||||
|
``` |
||||||
|
|
||||||
|
**Issues**: |
||||||
|
- `$properties`, `$methods`, `$constants` — exposed as raw arrays. External code does `$classDef->properties[$name] = $propDef`. No validation that the key matches `$propDef->name`, no type enforcement. |
||||||
|
- `$flags` — raw int, no guarantee it's a valid Modifiers bitmask. |
||||||
|
- `$ctorInit` / `$ctorClean` — mutated by CompilerBase during code generation, not initialization. |
||||||
|
- Property additions use `addMethod()`, `addAbstractMethod()` but array properties are also set directly via `[] =`. |
||||||
|
- `$extends` — set directly as raw string, bypasses `parent::__construct()` which also sets it on ClassLikeDef. |
||||||
|
|
||||||
|
**Recommendation**: |
||||||
|
- Make `$methods`, `$properties`, `$constants` private, expose via `addMethod()`/`getMethod()` (already exists) |
||||||
|
- Make `$flags` private, expose `isAbstract()` (already exists), add `isFinal()`, `isReadonly()` |
||||||
|
- Make `$extends` write-once via `setExtends(string)` with validation |
||||||
|
- Add `appendCtorInit(string)` and `appendCtorClean(string)` methods instead of direct string mutation |
||||||
|
|
||||||
|
### 1.2 FunctionDef (12 public properties) |
||||||
|
|
||||||
|
`src/Php/Entity/FunctionDef.php` |
||||||
|
|
||||||
|
```php |
||||||
|
public string $name; |
||||||
|
public string $returnType; |
||||||
|
public array $argInfoList = []; // externally populated: $functionDef->argInfoList[] = $argInfo |
||||||
|
public int $argCountRequired = 0; |
||||||
|
public string $params = ''; // generated C++ param string, mutated during compilation |
||||||
|
public string $namespace; |
||||||
|
public bool $method = false; |
||||||
|
public bool $stub = false; |
||||||
|
public bool $returnTypeUndeclared = false; |
||||||
|
public string $returnClass = ''; |
||||||
|
public ?array $returnTypeCheck = null; |
||||||
|
public string $returnTypeStr = ''; |
||||||
|
public ?NodeAbstract $returnTypeNode = null; |
||||||
|
``` |
||||||
|
|
||||||
|
**Issues**: |
||||||
|
- `$name` and `$namespace` are set in constructor but still publicly writable — should be readonly |
||||||
|
- `$argInfoList[]` is directly appended to by Preprocessor (line 329) |
||||||
|
- `$params` is a codegen artifact stored on the entity — belongs in a separate compilation context |
||||||
|
|
||||||
|
**Recommendation**: |
||||||
|
- Make constructor-set properties readonly (`$name`, `$namespace`, `$returnType`) |
||||||
|
- Add `addArg(ArgInfo $arg)` method instead of direct array mutation |
||||||
|
- Extract `$params` to a compilation context separate from the definition entity |
||||||
|
|
||||||
|
### 1.3 PropertyDef (7 public properties) |
||||||
|
|
||||||
|
`src/Php/Entity/PropertyDef.php` |
||||||
|
|
||||||
|
```php |
||||||
|
public string $name; |
||||||
|
public string $type; |
||||||
|
public int $flags; |
||||||
|
public ?string $default = null; |
||||||
|
public ?ArrayInitPlan $arrayInitPlan = null; |
||||||
|
public bool $nullable = false; |
||||||
|
public string $class = ''; // set after construction |
||||||
|
``` |
||||||
|
|
||||||
|
**Issues**: |
||||||
|
- `$class` is set after construction externally (`$propDef->class = $fullClassName`) |
||||||
|
- `$flags` is raw int — already has `isPrivate()`/`isProtected()`/`isPublic()`/`isStatic()` methods, good |
||||||
|
- Constructor already sets all core fields — `$class` should be added to the constructor |
||||||
|
|
||||||
|
**Recommendation**: |
||||||
|
- Add `$class` to the constructor (it's always known at construction time) |
||||||
|
- Make constructor-set fields readonly or private |
||||||
|
|
||||||
|
### 1.4 MethodDef (4 public properties) |
||||||
|
|
||||||
|
`src/Php/Entity/MethodDef.php` |
||||||
|
|
||||||
|
```php |
||||||
|
public int $flags; |
||||||
|
public string $name; |
||||||
|
public ?FunctionDef $functionDef = null; // set after construction |
||||||
|
public bool $hasDynamicCall = false; |
||||||
|
``` |
||||||
|
|
||||||
|
**Issues**: |
||||||
|
- No flag-check methods — inline checks in CompilerBase should use `$methodDef->isPrivate()` instead |
||||||
|
- `$functionDef` is set externally: `$this->methodDef->functionDef = $functionDef` (Preprocessor line 411) |
||||||
|
|
||||||
|
**Recommendation**: |
||||||
|
- Add `HasFlags` trait (from Phase 3.1 of reuse plan) |
||||||
|
- Add `setFunctionDef(FunctionDef $fd)` method with validation |
||||||
|
|
||||||
|
### 1.5 ConstantDef (8 public properties) |
||||||
|
|
||||||
|
```php |
||||||
|
public string $name; |
||||||
|
public string $type; |
||||||
|
public int $flags; |
||||||
|
public string $value; |
||||||
|
public string $arrayExpr = ''; |
||||||
|
public string $class = ''; |
||||||
|
public ?NodeAbstract $valueExpr = null; |
||||||
|
``` |
||||||
|
|
||||||
|
**Issues**: Same pattern — constructor sets core fields, but `$class` is set externally afterward. |
||||||
|
|
||||||
|
**Recommendation**: Add `$class` to constructor. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Issue 2: FunctionContext — public mutable grab-bag |
||||||
|
|
||||||
|
**Severity**: High. 26 public properties, all writable by any code with access to the context object. |
||||||
|
|
||||||
|
`src/Php/Context/FunctionContext.php` |
||||||
|
|
||||||
|
```php |
||||||
|
public ?SsaBuilder $ssaBuilder = null; // transient analysis state |
||||||
|
public array $stableObjects = []; // SSA optimizer state |
||||||
|
public array $hoistedProps = []; // SSA optimizer state |
||||||
|
public array $unsafeObjectProps = []; // SSA optimizer state |
||||||
|
public array $objects = []; // object variable tracking |
||||||
|
public array $stdArrays = []; // std container tracking |
||||||
|
public array $stdContainers = []; // std container tracking |
||||||
|
public array $localVars = []; // local variable table |
||||||
|
public array $staticVars = []; // static variable table |
||||||
|
public array $globalVars = []; // global variable table |
||||||
|
public array $ceWrappers = []; // class entry wrappers |
||||||
|
public int $tmpVarIndex = 0; // auto-increment counter |
||||||
|
public array $arguments = []; // function arguments |
||||||
|
public bool $inLoop = false; // control-flow state |
||||||
|
public bool $inClosure = false; // control-flow state |
||||||
|
public bool $hasMultiLevelBreak = false; |
||||||
|
public bool $hasMultiLevelContinue = false; |
||||||
|
public bool $inAssignExpr = false; // expression context |
||||||
|
public array $beforeStmtLines = []; // deferred code (flushed before stmts) |
||||||
|
public array $afterStmtLines = []; // deferred code (flushed after stmts) |
||||||
|
public array $objectProps; // (uninitialized!) |
||||||
|
public array $staticPropRefs = []; // static property references |
||||||
|
public int $scopeLevel = 0; // lexical scope depth |
||||||
|
/** @var array<int, ScopeContext> */ |
||||||
|
public array $scopeLayouts = []; // per-scope data |
||||||
|
``` |
||||||
|
|
||||||
|
**Issues**: |
||||||
|
- Traits directly mutate deeply nested state: `$this->context->stdArrays[$var] = ...`, `$this->context->localVars[$name] = ...` |
||||||
|
- No semantic grouping — analysis state, variable tracking, control-flow flags all mixed |
||||||
|
- `$objectProps` is declared but never initialized (could be null at runtime) |
||||||
|
- `$scopeLayouts` is managed through `enterScope()`/`leaveScope()` — but can be bypassed |
||||||
|
- `$tmpVarIndex` auto-increment — should use a method instead of direct `++` |
||||||
|
|
||||||
|
**Recommendation**: |
||||||
|
- Group related properties into sub-objects: `VariableTable`, `ControlFlowState`, `ScopeManager` |
||||||
|
- Make properties that should only be read by the compiler layer private/protected with getters |
||||||
|
- Add `incrementTmpVar(): int`, `addLocalVar()`, `addBeforeStmt()` methods |
||||||
|
- Initialize `$objectProps = []` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Issue 3: CompilerBase — only 1.9% private methods |
||||||
|
|
||||||
|
**Severity**: Medium. Virtually everything is public or protected. |
||||||
|
|
||||||
|
`src/Php/CompilerBase.php` — 269 methods total: |
||||||
|
- ~25 public methods (many should be protected or internal) |
||||||
|
- ~239 protected methods (most should be private — internal helpers) |
||||||
|
- **5 private methods** (1.9%) |
||||||
|
|
||||||
|
### 3.1 Methods that should be private |
||||||
|
|
||||||
|
The following methods are internal helpers only called from within CompilerBase (not from Preprocessor, Translator, or traits). They are unnecessarily `protected`: |
||||||
|
|
||||||
|
| Method | Line | Called from | |
||||||
|
|--------|------|-------------| |
||||||
|
| `resetFunction()` | 833 | Internal only | |
||||||
|
| `resetMethod()` | 840 | Internal only | |
||||||
|
| `resetClass()` | 846 | Internal only | |
||||||
|
| `resolveObjectClassDef()` | 816 | Already private ✓ | |
||||||
|
| `getBigIntLiteralString()` | 1183 | Already private ✓ | |
||||||
|
| `getDecimalLiteralString()` | 1188 | Already private ✓ | |
||||||
|
| `parseBeforeStmtLines()` | 1321 | Internal, but accessed by traits | |
||||||
|
| `parseAfterStmtLines()` | 1331 | Internal, but accessed by traits | |
||||||
|
| `genTmpVarName()` | 734 | Public — should at least be protected | |
||||||
|
|
||||||
|
### 3.2 Public methods that are internal concern |
||||||
|
|
||||||
|
| Method | Current visibility | Issue | |
||||||
|
|--------|-------------------|-------| |
||||||
|
| `genTmpVarName()` | public | Only used internally for variable name generation | |
||||||
|
| `writeFile()` | public | File I/O — should be a separate service | |
||||||
|
| `stop()` | public | Error helper — could be internal | |
||||||
|
| `isScalarInt()` | public | AST helper, only used internally | |
||||||
|
| `getType()` | public | AST helper, only used internally | |
||||||
|
| `getObjectType()` | public | Type mapping, used internally | |
||||||
|
| `getTypeFromZendType()` | public | Type mapping, used internally | |
||||||
|
| `getIncludeDir()` | public | Config getter — should be on a Config object | |
||||||
|
| `getBuildDir()` | public | Config getter — should be on a Config object | |
||||||
|
|
||||||
|
### 3.3 Public constants leaked as API |
||||||
|
|
||||||
|
30 public constants for internal type names, literal values, etc. These are needed by traits but expose internal naming conventions. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Issue 4: Trait → Context coupling |
||||||
|
|
||||||
|
**Severity**: Medium. Traits bypass any encapsulation boundary and directly mutate `$this->context`. |
||||||
|
|
||||||
|
| Trait | `$this->context->` accesses | |
||||||
|
|-------|---------------------------| |
||||||
|
| `StdContainerTrait` | 40+ accesses to `stdArrays`, `stdContainers`, `objects`, `localVars` | |
||||||
|
| `LoopVarOptimizer` | accesses to `localVars`, `arguments`, `scopeLevel`, `inLoop` | |
||||||
|
| `SsaPropOptimizer` | accesses to `stableObjects`, `hoistedProps`, `unsafeObjectProps`, `objects` | |
||||||
|
| `FuncCallOptimizer` | accesses to `beforeStmtLines`, `arguments`, `localVars` | |
||||||
|
| `SsaTypeOptimizer` | accesses to `localVars` | |
||||||
|
| `BinaryOpTrait` | accesses to `objects`, `localVars` | |
||||||
|
|
||||||
|
**Issues**: |
||||||
|
- Traits have no declared contract — they assume `$this->context` exists and has specific properties |
||||||
|
- If a property name changes in FunctionContext, all 6 traits break silently |
||||||
|
- No type safety — arrays are indexed by string but accessed with arbitrary keys |
||||||
|
|
||||||
|
**Recommendation**: |
||||||
|
- Define a `ContextAccess` interface that traits must use instead of direct property access |
||||||
|
- Or: inject context into trait methods as a parameter instead of reading from `$this` |
||||||
|
- Short-term: add `@property-read` annotations to document the contract |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Issue 5: Preprocessor directly mutates entity state |
||||||
|
|
||||||
|
**Severity**: Medium. Preprocessor bypasses entity boundaries. |
||||||
|
|
||||||
|
`src/Php/Preprocessor.php`: |
||||||
|
```php |
||||||
|
line 279: $argInfo->name = $name; // direct property set |
||||||
|
line 329: $functionDef->argInfoList[] = $argInfo; // direct array append |
||||||
|
line 411: $this->methodDef->functionDef = $functionDef; // direct property set |
||||||
|
line 447: $this->classDef->extends = $this->parentClass; // direct property set |
||||||
|
line 585: $this->classDef->constants[$constInfo->name] = ...; // direct array set |
||||||
|
line 618: $this->classDef->properties[$name] = $propDef; // direct array set |
||||||
|
``` |
||||||
|
|
||||||
|
**Recommendation**: Use entity methods: `$functionDef->addArg($argInfo)`, `$this->methodDef->setFunctionDef($functionDef)`, `$classDef->addProperty($propDef)`, etc. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Issue 6: CompilerBase protected state leaked to inheritance chain |
||||||
|
|
||||||
|
**Severity**: Low-Medium. The chain CompilerBase → Preprocessor → Translator means any protected property in CompilerBase is accessible from Translator. |
||||||
|
|
||||||
|
CompilerBase has ~50 protected properties. Translator is 3301 lines and accesses many of them. There's no way to know which properties are "safe to use" vs "internal to CompilerBase." |
||||||
|
|
||||||
|
**Recommendation**: Migrate internal-only properties to `private` over time, with explicit getter methods where needed. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Issue 7: Platform/Backend — well encapsulated |
||||||
|
|
||||||
|
**Severity**: None. The Platform and Backend layers are well-encapsulated: |
||||||
|
- All state is private (e.g., `$compilerCommand`, `$linkerCommand` in GccLikeBackend) |
||||||
|
- Only methods are public |
||||||
|
- Abstract contracts are clear |
||||||
|
- Factory pattern is used consistently |
||||||
|
|
||||||
|
**No changes needed in this layer.** |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Issue 8: ScopeContext is dead code |
||||||
|
|
||||||
|
**Severity**: Low. `src/Php/Context/ScopeContext.php` — 7 lines, empty class body. Used as a placeholder type in FunctionContext's `$scopeLayouts` array. Either populate it or remove it. |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Implementation Priority |
||||||
|
|
||||||
|
| # | Issue | Impact | Effort | Risk | |
||||||
|
|---|-------|--------|--------|------| |
||||||
|
| 1.1 | Entity: readonly for constructor fields | Data integrity | 2h | Low | |
||||||
|
| 1.2 | Entity: add mutation methods (addArg, addProperty, etc.) | Safe mutation | 3h | Medium | |
||||||
|
| 2.1 | FunctionContext: group properties into sub-objects | Clarity | 4h | Medium | |
||||||
|
| 2.2 | FunctionContext: add accessor methods | Controlled mutation | 3h | Medium | |
||||||
|
| 3 | CompilerBase: demote public→protected, protected→private | Boundary clarity | 4h | Medium | |
||||||
|
| 4 | Define trait context contract | Safe coupling | 3h | Medium | |
||||||
|
| 5 | Preprocessor: use entity methods | Consistent mutation | 2h | Low | |
||||||
|
| 8 | Remove ScopeContext dead code | Cleanup | 15m | None | |
||||||
|
|
||||||
|
**Recommended order**: Start with 8 (quick win), then 1.1 + 1.2 (entity cleanup), then 2.1 + 2.2 (context cleanup), then 3 + 5 + 4 (CompilerBase boundary). |
||||||
@ -1,21 +0,0 @@ |
|||||||
name: Patch PHP headers for C++ |
|
||||||
description: Apply temporary upstream PHP header fixes required by generated C++. |
|
||||||
|
|
||||||
runs: |
|
||||||
using: composite |
|
||||||
steps: |
|
||||||
# Temporary workaround for php/php-src#22935. Some supported PHP packages |
|
||||||
# contain a php_hash.h revision that is valid C but invalid C++. Remove |
|
||||||
# this action when all PHP 8.4/8.5 packages include php/php-src#22940. |
|
||||||
- name: Patch php_hash.h |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php_include_dir="$(php-config --include-dir)" |
|
||||||
php_hash_header="${php_include_dir}/ext/hash/php_hash.h" |
|
||||||
|
|
||||||
if grep -Fq 'char *base = ecalloc(' "${php_hash_header}"; then |
|
||||||
sudo patch --directory="${php_include_dir}" --strip=1 \ |
|
||||||
< "${GITHUB_ACTION_PATH}/../../patches/php-hash-cxx.patch" |
|
||||||
else |
|
||||||
echo "php_hash.h already contains the upstream C++ fix" |
|
||||||
fi |
|
||||||
@ -1,155 +0,0 @@ |
|||||||
name: Unix ARM64 build |
|
||||||
description: Build and smoke-test TypePHP on a Unix ARM64 runner |
|
||||||
|
|
||||||
inputs: |
|
||||||
php-version: |
|
||||||
description: PHP minor version |
|
||||||
required: true |
|
||||||
os: |
|
||||||
description: Package operating-system identifier |
|
||||||
required: true |
|
||||||
library-extension: |
|
||||||
description: Shared-library extension used by PHPX |
|
||||||
required: true |
|
||||||
smoke-directory: |
|
||||||
description: Platform smoke project directory below .github/smoke |
|
||||||
required: true |
|
||||||
smoke-binary: |
|
||||||
description: Platform smoke executable name |
|
||||||
required: true |
|
||||||
smoke-output: |
|
||||||
description: Expected platform smoke output |
|
||||||
required: true |
|
||||||
|
|
||||||
runs: |
|
||||||
using: composite |
|
||||||
steps: |
|
||||||
- name: Setup PHP |
|
||||||
uses: shivammathur/setup-php@v2 |
|
||||||
with: |
|
||||||
php-version: ${{ inputs.php-version }} |
|
||||||
coverage: none |
|
||||||
extensions: mbstring |
|
||||||
ini-values: precision=17, memory_limit=4G, error_reporting=E_ERROR|E_WARNING, display_errors=1, display_startup_errors=1, log_errors=0 |
|
||||||
tools: composer:v2 |
|
||||||
env: |
|
||||||
fail-fast: true |
|
||||||
phpts: ts |
|
||||||
update: true |
|
||||||
|
|
||||||
- name: Install Linux build dependencies |
|
||||||
if: inputs.os == 'linux' |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
sudo apt-get update |
|
||||||
sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config |
|
||||||
|
|
||||||
- name: Install macOS build dependencies |
|
||||||
if: inputs.os == 'macos' |
|
||||||
shell: bash |
|
||||||
run: brew install cmake gmp mpfr pkg-config |
|
||||||
|
|
||||||
- name: Verify ZTS PHP and architecture |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php -r 'if (!PHP_ZTS) { fwrite(STDERR, "Expected a ZTS PHP build\n"); exit(1); }' |
|
||||||
case "$(php -r 'echo PHP_VERSION;')" in |
|
||||||
"${{ inputs.php-version }}"*) ;; |
|
||||||
*) echo "setup-php installed an unexpected PHP version" >&2; exit 1 ;; |
|
||||||
esac |
|
||||||
case "$(uname -m)" in |
|
||||||
arm64|aarch64) ;; |
|
||||||
*) echo "Expected an ARM64 runner, got $(uname -m)" >&2; exit 1 ;; |
|
||||||
esac |
|
||||||
|
|
||||||
php_home="$(php-config --prefix)" |
|
||||||
embed_library="${php_home}/lib/libphp.${{ inputs.library-extension }}" |
|
||||||
test -x "${php_home}/bin/php-config" |
|
||||||
test -f "${embed_library}" |
|
||||||
echo "PHP_HOME=${php_home}" >> "${GITHUB_ENV}" |
|
||||||
if [[ '${{ inputs.os }}' == 'linux' ]]; then |
|
||||||
echo "LD_LIBRARY_PATH=${PHPX_HOME}/lib:${php_home}/lib" >> "${GITHUB_ENV}" |
|
||||||
else |
|
||||||
echo "DYLD_LIBRARY_PATH=${PHPX_HOME}/lib:${php_home}/lib" >> "${GITHUB_ENV}" |
|
||||||
fi |
|
||||||
php -v |
|
||||||
php-config --configure-options |
|
||||||
|
|
||||||
- name: Patch php_hash.h C++ compatibility |
|
||||||
uses: ./.github/actions/patch-php-headers |
|
||||||
|
|
||||||
- name: Install Composer dependencies |
|
||||||
shell: bash |
|
||||||
run: composer install --prefer-dist --no-progress |
|
||||||
|
|
||||||
- name: Build PHPX |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
cmake -S "${PHPX_HOME}" -B "${PHPX_HOME}/build" \ |
|
||||||
-D CMAKE_BUILD_TYPE=Release \ |
|
||||||
-D BUILD_TESTS=OFF \ |
|
||||||
-D BUILD_EXT=OFF \ |
|
||||||
-D GITHUB_ACTION=ON \ |
|
||||||
-D php_dir="${PHP_HOME}" |
|
||||||
cmake --build "${PHPX_HOME}/build" --target phpx --parallel 2 |
|
||||||
test -f "${PHPX_HOME}/lib/libphpx.${{ inputs.library-extension }}" |
|
||||||
|
|
||||||
- name: Build tpc |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php bin/tpc.php project.yml --job 2 --no-progress |
|
||||||
test -x ./tpc |
|
||||||
file ./tpc |
|
||||||
file ./tpc | grep -Eiq 'arm64|aarch64|ARM aarch64' |
|
||||||
./tpc --version |
|
||||||
|
|
||||||
- name: Run platform smoke test |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
smoke_root=".github/smoke/${{ inputs.smoke-directory }}" |
|
||||||
./tpc "${smoke_root}/project.yml" --job 1 --no-progress |
|
||||||
smoke_binary="${smoke_root}/${{ inputs.smoke-binary }}" |
|
||||||
test -x "${smoke_binary}" |
|
||||||
output="$("${smoke_binary}")" |
|
||||||
test "${output}" = '${{ inputs.smoke-output }}' |
|
||||||
|
|
||||||
- name: Show native dependencies |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
if [[ '${{ inputs.os }}' == 'linux' ]]; then |
|
||||||
ldd ./tpc |
|
||||||
else |
|
||||||
otool -L ./tpc |
|
||||||
fi |
|
||||||
|
|
||||||
- name: Package tested compiler |
|
||||||
if: startsWith(github.ref, 'refs/tags/') && inputs.php-version == '8.5' |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
composer install --no-dev --prefer-dist --no-progress --classmap-authoritative |
|
||||||
export TYPEPHP_PACKAGE_VERSION="${GITHUB_REF_NAME}" |
|
||||||
php package.php |
|
||||||
test "$(find . -maxdepth 1 -name 'tpc_v*_${{ inputs.os }}_arm64.tar.gz' -type f | wc -l)" -eq 1 |
|
||||||
|
|
||||||
- name: Upload release package |
|
||||||
if: startsWith(github.ref, 'refs/tags/') && inputs.php-version == '8.5' |
|
||||||
uses: actions/upload-artifact@v4 |
|
||||||
with: |
|
||||||
name: release-${{ inputs.os }}-arm64-php-${{ inputs.php-version }}-zts |
|
||||||
if-no-files-found: error |
|
||||||
retention-days: 1 |
|
||||||
path: tpc_v*_${{ inputs.os }}_arm64.tar.gz |
|
||||||
|
|
||||||
- name: Upload platform build outputs |
|
||||||
if: always() |
|
||||||
uses: actions/upload-artifact@v4 |
|
||||||
with: |
|
||||||
name: tpc-${{ inputs.os }}-arm64-php-${{ inputs.php-version }}-zts |
|
||||||
include-hidden-files: true |
|
||||||
if-no-files-found: warn |
|
||||||
retention-days: 7 |
|
||||||
path: | |
|
||||||
tpc |
|
||||||
.github/smoke/${{ inputs.smoke-directory }}/${{ inputs.smoke-binary }} |
|
||||||
.github/smoke/${{ inputs.smoke-directory }}/build/**/*.cc |
|
||||||
.github/smoke/${{ inputs.smoke-directory }}/build/**/*.h |
|
||||||
@ -0,0 +1,90 @@ |
|||||||
|
# Copilot instructions for this repository |
||||||
|
|
||||||
|
## Project overview |
||||||
|
|
||||||
|
TypePHP is a PHP native compilation project. Its `tpc` command is TypePHP Compiler (AOT), which translates PHP source into C++, then compiles and links it into a native binary or a PHP extension. The primary entrypoint boots `src/compiler.php`; that drives `TypePhp\Translator` through a fixed pipeline: |
||||||
|
|
||||||
|
1. `prepare()` scans files, parses ASTs, collects symbols, and topologically sorts PHP files by cross-file symbol usage. |
||||||
|
2. `convert()` turns PHP ASTs into generated `.cc` files while passing through native source files (`.cpp`, `.c`, `.s`, `.m`, `.mm`). |
||||||
|
3. `compile()` chooses the platform/compiler backend, generates support sources and headers, and compiles sources, using `pcntl` parallelism when available. |
||||||
|
4. `build()` links object files into the final executable or extension. |
||||||
|
|
||||||
|
`src/CompilerBase.php` contains most PHP-to-C++ translation logic and mixes in many traits for syntax handling and optimizations. `src/Preprocessor.php` owns dependency discovery and file ordering. Platform-specific behavior lives under `src/Platform/`, compiler backends under `src/Backend/`, and metadata/state objects under `src/Entity/` and `src/Context/`. |
||||||
|
|
||||||
|
## Setup and build commands |
||||||
|
|
||||||
|
The repo expects PHP 8.4+, GCC 9+ with C++17, CMake 3.24+, and a compiled `swoole/phpx` dependency. Install PHP dependencies with: |
||||||
|
|
||||||
|
```bash |
||||||
|
composer install |
||||||
|
``` |
||||||
|
|
||||||
|
Build `phpx` before relying on compiler runs: |
||||||
|
|
||||||
|
```bash |
||||||
|
cd vendor/swoole/phpx |
||||||
|
cmake . |
||||||
|
make -j32 |
||||||
|
``` |
||||||
|
|
||||||
|
Compile a project, directory, single file, or `project.yml`: |
||||||
|
|
||||||
|
```bash |
||||||
|
./tpc <path-to-project-or-file> |
||||||
|
./tpc <path> -O2 |
||||||
|
./tpc <path> --mode=ext -o <output_name> |
||||||
|
``` |
||||||
|
|
||||||
|
## Test commands |
||||||
|
|
||||||
|
Run the PHPUnit suite: |
||||||
|
|
||||||
|
```bash |
||||||
|
./vendor/bin/phpunit |
||||||
|
``` |
||||||
|
|
||||||
|
Run a single PHPUnit file or a single test method: |
||||||
|
|
||||||
|
```bash |
||||||
|
./vendor/bin/phpunit phpunit/src/Platform/PlatformTest.php |
||||||
|
./vendor/bin/phpunit --filter testWindowsBasic phpunit/src/Platform/PlatformTest.php |
||||||
|
``` |
||||||
|
|
||||||
|
Run PHPT integration tests: |
||||||
|
|
||||||
|
```bash |
||||||
|
php run-tests.php tests/compiler/ |
||||||
|
php run-tests.php tests/compiler/arrays.phpt |
||||||
|
``` |
||||||
|
|
||||||
|
For parser/runtime comparison without AOT compilation, there are docs using: |
||||||
|
|
||||||
|
```bash |
||||||
|
php run-tests.php --no-aot tests/compiler/arrow-functions.phpt |
||||||
|
``` |
||||||
|
|
||||||
|
## Formatting |
||||||
|
|
||||||
|
The repo ships a PHP CS Fixer config in `.php-cs-fixer.dist.php`: |
||||||
|
|
||||||
|
```bash |
||||||
|
php vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php <path> |
||||||
|
``` |
||||||
|
|
||||||
|
Generated C++ is auto-formatted by the compiler itself when `clang-format` is available. |
||||||
|
|
||||||
|
## Configuration and repository conventions |
||||||
|
|
||||||
|
- `project.yml` is the project-level build config. Important keys include `name`, `build-mode`, `cxx-std`, `cxx-flags`, `ld-flags`, `sources`, `ignore`, and `resource`. |
||||||
|
- Command-line options intentionally override YAML values. `Translator` parses YAML first, then applies CLI arguments last. |
||||||
|
- YAML parsing accepts both hyphenated and underscored variants for several keys, but existing examples use hyphenated names such as `build-mode` and `cxx-std`. |
||||||
|
- In `bin` mode, compiled programs must define `main()`. In `ext` mode they do not. |
||||||
|
- File discovery is mixed-language by design: PHP is translated, while native sources are compiled directly if they appear in configured sources. |
||||||
|
- Generated files are written under `build/`, with generated C++ paths mirroring the source tree and generated headers under `build/include/`. |
||||||
|
- Platform/compiler selection is centralized: `PlatformFactory` detects the OS, and `CompilerFactory` picks the backend (`Gcc`, `Clang`, `Msvc`) with environment/config overrides. |
||||||
|
|
||||||
|
## Test-specific conventions |
||||||
|
|
||||||
|
- PHPUnit tests for compiler internals should use `CompilerTest::create(ROOT_PATH)`, which enables test mode instead of normal fatal exits. |
||||||
|
- `phpunit/bootstrap.php` exposes a `BaseTest::exec()` helper that expects compilation failures to surface as `TypePhp\Exception\TestError`. |
||||||
|
- PHPT end-to-end tests live in `tests/compiler/`; existing guidance and examples generally put executable test logic inside a `main()` function. |
||||||
@ -1,11 +0,0 @@ |
|||||||
--- a/ext/hash/php_hash.h
|
|
||||||
+++ b/ext/hash/php_hash.h
|
|
||||||
@@ -158,7 +158,7 @@ static inline void *php_hash_alloc_context(const php_hash_ops *ops) {
|
|
||||||
/* Zero out context memory so serialization doesn't expose internals */
|
|
||||||
if (ops->context_align > 0) {
|
|
||||||
size_t align = ops->context_align;
|
|
||||||
- char *base = ecalloc(1, ops->context_size + align);
|
|
||||||
+ char *base = (char *) ecalloc(1, ops->context_size + align);
|
|
||||||
size_t offset = align - ((uintptr_t)base & (align - 1));
|
|
||||||
char *ptr = base + offset;
|
|
||||||
ptr[-1] = (char)offset;
|
|
||||||
@ -1,44 +0,0 @@ |
|||||||
#include <phpx.h> |
|
||||||
|
|
||||||
#include <cstring> |
|
||||||
#include <sys/utsname.h> |
|
||||||
#include <unistd.h> |
|
||||||
|
|
||||||
using namespace php; |
|
||||||
|
|
||||||
Int php_linux_current_process_id() |
|
||||||
{ |
|
||||||
return static_cast<Int>(getpid()); |
|
||||||
} |
|
||||||
|
|
||||||
Int php_linux_online_processor_count() |
|
||||||
{ |
|
||||||
return static_cast<Int>(sysconf(_SC_NPROCESSORS_ONLN)); |
|
||||||
} |
|
||||||
|
|
||||||
Bool php_linux_uname_machine_is_arm64() |
|
||||||
{ |
|
||||||
utsname info{}; |
|
||||||
if (uname(&info) != 0) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
return std::strcmp(info.machine, "aarch64") == 0 || std::strcmp(info.machine, "arm64") == 0; |
|
||||||
} |
|
||||||
|
|
||||||
Bool php_linux_native_is_arm64() |
|
||||||
{ |
|
||||||
#if defined(__aarch64__) || defined(__arm64__) |
|
||||||
return true; |
|
||||||
#else |
|
||||||
return false; |
|
||||||
#endif |
|
||||||
} |
|
||||||
|
|
||||||
Bool php_linux_native_php_is_zts() |
|
||||||
{ |
|
||||||
#ifdef ZTS |
|
||||||
return true; |
|
||||||
#else |
|
||||||
return false; |
|
||||||
#endif |
|
||||||
} |
|
||||||
@ -1,12 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
/** Linux ARM64 platform declarations implemented by platform.cc. */ |
|
||||||
function linux_current_process_id(): int {} |
|
||||||
|
|
||||||
function linux_online_processor_count(): int {} |
|
||||||
|
|
||||||
function linux_uname_machine_is_arm64(): bool {} |
|
||||||
|
|
||||||
function linux_native_is_arm64(): bool {} |
|
||||||
|
|
||||||
function linux_native_php_is_zts(): bool {} |
|
||||||
@ -1,22 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
function requireLinuxArm64Condition(bool $condition, string $message): void |
|
||||||
{ |
|
||||||
if (!$condition) { |
|
||||||
throw new RuntimeException($message); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
requireLinuxArm64Condition(PHP_OS_FAMILY === 'Linux', 'Expected PHP_OS_FAMILY=Linux'); |
|
||||||
requireLinuxArm64Condition(DIRECTORY_SEPARATOR === '/', 'Expected the Unix directory separator'); |
|
||||||
requireLinuxArm64Condition(PHP_ZTS !== 0 && PHP_ZTS !== false, 'Expected a ZTS PHP runtime'); |
|
||||||
requireLinuxArm64Condition(linux_native_php_is_zts(), 'The native ZTS macro is not enabled'); |
|
||||||
requireLinuxArm64Condition(linux_native_is_arm64(), 'The native compiler target is not ARM64'); |
|
||||||
requireLinuxArm64Condition(linux_current_process_id() > 0, 'getpid() failed'); |
|
||||||
requireLinuxArm64Condition(linux_online_processor_count() > 0, 'sysconf() returned no processors'); |
|
||||||
requireLinuxArm64Condition(linux_uname_machine_is_arm64(), 'uname() did not report ARM64'); |
|
||||||
|
|
||||||
echo 'linux-arm64-smoke-ok:zts'; |
|
||||||
} |
|
||||||
@ -1,9 +0,0 @@ |
|||||||
name: linux-arm64-smoke |
|
||||||
mode: bin |
|
||||||
build-dir: build |
|
||||||
output: linux_arm64_smoke |
|
||||||
cxx-std: c++17 |
|
||||||
|
|
||||||
sources: |
|
||||||
- main.php |
|
||||||
- cpp-src |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
#include <phpx.h> |
|
||||||
|
|
||||||
#include <mach/mach.h> |
|
||||||
#include <sys/sysctl.h> |
|
||||||
#include <unistd.h> |
|
||||||
|
|
||||||
using namespace php; |
|
||||||
|
|
||||||
Int php_macos_current_process_id() |
|
||||||
{ |
|
||||||
return static_cast<Int>(getpid()); |
|
||||||
} |
|
||||||
|
|
||||||
Int php_macos_logical_processor_count() |
|
||||||
{ |
|
||||||
int count = 0; |
|
||||||
size_t size = sizeof(count); |
|
||||||
if (sysctlbyname("hw.logicalcpu", &count, &size, nullptr, 0) != 0) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
return static_cast<Int>(count); |
|
||||||
} |
|
||||||
|
|
||||||
Bool php_macos_has_mach_host_port() |
|
||||||
{ |
|
||||||
return mach_host_self() != MACH_PORT_NULL; |
|
||||||
} |
|
||||||
|
|
||||||
Bool php_macos_native_is_arm64() |
|
||||||
{ |
|
||||||
#if defined(__aarch64__) || defined(__arm64__) |
|
||||||
return true; |
|
||||||
#else |
|
||||||
return false; |
|
||||||
#endif |
|
||||||
} |
|
||||||
|
|
||||||
Bool php_macos_native_php_is_zts() |
|
||||||
{ |
|
||||||
#ifdef ZTS |
|
||||||
return true; |
|
||||||
#else |
|
||||||
return false; |
|
||||||
#endif |
|
||||||
} |
|
||||||
@ -1,12 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
/** macOS platform declarations implemented by platform.cc. */ |
|
||||||
function macos_current_process_id(): int {} |
|
||||||
|
|
||||||
function macos_logical_processor_count(): int {} |
|
||||||
|
|
||||||
function macos_has_mach_host_port(): bool {} |
|
||||||
|
|
||||||
function macos_native_is_arm64(): bool {} |
|
||||||
|
|
||||||
function macos_native_php_is_zts(): bool {} |
|
||||||
@ -1,22 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
function requireMacosCondition(bool $condition, string $message): void |
|
||||||
{ |
|
||||||
if (!$condition) { |
|
||||||
throw new RuntimeException($message); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
requireMacosCondition(PHP_OS_FAMILY === 'Darwin', 'Expected PHP_OS_FAMILY=Darwin'); |
|
||||||
requireMacosCondition(DIRECTORY_SEPARATOR === '/', 'Expected the Unix directory separator'); |
|
||||||
requireMacosCondition(PHP_ZTS !== 0 && PHP_ZTS !== false, 'Expected a ZTS PHP runtime'); |
|
||||||
requireMacosCondition(macos_native_php_is_zts(), 'The native ZTS macro is not enabled'); |
|
||||||
requireMacosCondition(macos_native_is_arm64(), 'The native compiler target is not ARM64'); |
|
||||||
requireMacosCondition(macos_current_process_id() > 0, 'getpid() failed'); |
|
||||||
requireMacosCondition(macos_logical_processor_count() > 0, 'sysctl() returned no processors'); |
|
||||||
requireMacosCondition(macos_has_mach_host_port(), 'mach_host_self() failed'); |
|
||||||
|
|
||||||
echo 'macos-arm64-smoke-ok:zts'; |
|
||||||
} |
|
||||||
@ -1,9 +0,0 @@ |
|||||||
name: macos-smoke |
|
||||||
mode: bin |
|
||||||
build-dir: build |
|
||||||
output: macos_smoke |
|
||||||
cxx-std: c++17 |
|
||||||
|
|
||||||
sources: |
|
||||||
- main.php |
|
||||||
- cpp-src |
|
||||||
@ -1,43 +0,0 @@ |
|||||||
name: Linux ARM64 |
|
||||||
|
|
||||||
on: |
|
||||||
push: |
|
||||||
pull_request: |
|
||||||
workflow_dispatch: |
|
||||||
|
|
||||||
permissions: |
|
||||||
contents: read |
|
||||||
|
|
||||||
concurrency: |
|
||||||
group: linux-arm64-${{ github.ref }} |
|
||||||
cancel-in-progress: true |
|
||||||
|
|
||||||
env: |
|
||||||
COMPOSER_NO_INTERACTION: 1 |
|
||||||
COMPOSER_PROCESS_TIMEOUT: 0 |
|
||||||
|
|
||||||
jobs: |
|
||||||
build: |
|
||||||
name: Build - PHP ${{ matrix.php }} ZTS |
|
||||||
runs-on: ubuntu-22.04-arm |
|
||||||
timeout-minutes: 90 |
|
||||||
strategy: |
|
||||||
fail-fast: false |
|
||||||
matrix: |
|
||||||
php: ["8.4", "8.5"] |
|
||||||
env: |
|
||||||
PHPX_HOME: ${{ github.workspace }}/vendor/swoole/phpx |
|
||||||
|
|
||||||
steps: |
|
||||||
- name: Checkout TypePHP |
|
||||||
uses: actions/checkout@v4 |
|
||||||
|
|
||||||
- name: Build and smoke test |
|
||||||
uses: ./.github/actions/unix-arm64-build |
|
||||||
with: |
|
||||||
php-version: ${{ matrix.php }} |
|
||||||
os: linux |
|
||||||
library-extension: so |
|
||||||
smoke-directory: linux-arm64 |
|
||||||
smoke-binary: linux_arm64_smoke |
|
||||||
smoke-output: linux-arm64-smoke-ok:zts |
|
||||||
@ -1,360 +0,0 @@ |
|||||||
name: Linux x64 |
|
||||||
|
|
||||||
on: |
|
||||||
push: |
|
||||||
pull_request: |
|
||||||
workflow_dispatch: |
|
||||||
|
|
||||||
permissions: |
|
||||||
contents: read |
|
||||||
|
|
||||||
concurrency: |
|
||||||
group: tests-${{ github.workflow }}-${{ github.ref }} |
|
||||||
cancel-in-progress: true |
|
||||||
|
|
||||||
env: |
|
||||||
COMPOSER_NO_INTERACTION: 1 |
|
||||||
COMPOSER_PROCESS_TIMEOUT: 0 |
|
||||||
|
|
||||||
jobs: |
|
||||||
build: |
|
||||||
# Compilation is always required; `--skip-tests` only suppresses downstream test jobs. |
|
||||||
name: Build - PHP ${{ matrix.php }} ZTS |
|
||||||
runs-on: ubuntu-22.04 |
|
||||||
timeout-minutes: 60 |
|
||||||
strategy: |
|
||||||
fail-fast: false |
|
||||||
matrix: |
|
||||||
php: ["8.4", "8.5"] |
|
||||||
env: |
|
||||||
PHPX_HOME: ${{ github.workspace }}/vendor/swoole/phpx |
|
||||||
|
|
||||||
steps: |
|
||||||
- name: Checkout TypePHP |
|
||||||
uses: actions/checkout@v4 |
|
||||||
|
|
||||||
- name: Checkout phpy |
|
||||||
uses: actions/checkout@v4 |
|
||||||
with: |
|
||||||
repository: swoole/phpy |
|
||||||
path: third_party/phpy |
|
||||||
|
|
||||||
- name: Setup PHP |
|
||||||
uses: shivammathur/setup-php@v2 |
|
||||||
with: |
|
||||||
php-version: ${{ matrix.php }} |
|
||||||
coverage: none |
|
||||||
ini-values: precision=17, memory_limit=4G, error_reporting=E_ERROR|E_WARNING, display_errors=1, display_startup_errors=1, log_errors=0 |
|
||||||
tools: composer:v2 |
|
||||||
env: |
|
||||||
fail-fast: true |
|
||||||
phpts: ts |
|
||||||
update: true |
|
||||||
|
|
||||||
- name: Verify ZTS PHP |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php -r 'if (!PHP_ZTS) { fwrite(STDERR, "Expected a ZTS PHP build\n"); exit(1); }' |
|
||||||
case "$(php -r 'echo PHP_VERSION;')" in |
|
||||||
"${{ matrix.php }}"*) ;; |
|
||||||
*) echo "setup-php installed an unexpected PHP version" >&2; exit 1 ;; |
|
||||||
esac |
|
||||||
|
|
||||||
- name: Patch php_hash.h C++ compatibility |
|
||||||
uses: ./.github/actions/patch-php-headers |
|
||||||
|
|
||||||
- name: Install native build dependencies |
|
||||||
run: | |
|
||||||
sudo apt-get update |
|
||||||
sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config python3-dev |
|
||||||
|
|
||||||
- name: Configure ZTS PHP embed library |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php_home="$(php-config --prefix)" |
|
||||||
embed_library="${php_home}/lib/libphp.so" |
|
||||||
test -x "${php_home}/bin/php-config" |
|
||||||
test -f "${embed_library}" |
|
||||||
echo "PHP_HOME=${php_home}" >> "${GITHUB_ENV}" |
|
||||||
echo "Using ZTS PHP $(php-config --version) embed library: ${embed_library}" |
|
||||||
|
|
||||||
- name: Install Composer dependencies |
|
||||||
run: composer install --prefer-dist --no-progress |
|
||||||
|
|
||||||
- name: Build PHPX |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
cmake -S "${PHPX_HOME}" -B "${PHPX_HOME}/build" \ |
|
||||||
-D CMAKE_BUILD_TYPE=Release \ |
|
||||||
-D BUILD_TESTS=OFF \ |
|
||||||
-D BUILD_EXT=OFF \ |
|
||||||
-D GITHUB_ACTION=ON \ |
|
||||||
-D php_dir="${PHP_HOME}" |
|
||||||
cmake --build "${PHPX_HOME}/build" --target phpx --parallel 2 |
|
||||||
test -f "${PHPX_HOME}/lib/libphpx.so" |
|
||||||
|
|
||||||
- name: Build phpy |
|
||||||
working-directory: third_party/phpy |
|
||||||
run: | |
|
||||||
phpize |
|
||||||
./configure |
|
||||||
make -j2 |
|
||||||
test -f modules/phpy.so |
|
||||||
|
|
||||||
- name: Enable phpy extension |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php_ini_dir="$(php-config --ini-dir)" |
|
||||||
echo "extension=${GITHUB_WORKSPACE}/third_party/phpy/modules/phpy.so" \ |
|
||||||
| sudo tee "${php_ini_dir}/90-phpy.ini" |
|
||||||
echo "PHP_INI_SCAN_DIR=${php_ini_dir}" >> "${GITHUB_ENV}" |
|
||||||
php --ri phpy |
|
||||||
|
|
||||||
- name: Configure native library path |
|
||||||
shell: bash |
|
||||||
run: echo "LD_LIBRARY_PATH=${PHPX_HOME}/lib:${PHP_HOME}/lib" >> "${GITHUB_ENV}" |
|
||||||
|
|
||||||
- name: Build tpc |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php bin/tpc.php project.yml --job 2 --no-progress |
|
||||||
test -x ./tpc |
|
||||||
file ./tpc |
|
||||||
file ./tpc | grep -Eiq 'x86-64|x86_64|amd64' |
|
||||||
./tpc --version |
|
||||||
|
|
||||||
- name: Upload Linux x64 build outputs |
|
||||||
uses: actions/upload-artifact@v4 |
|
||||||
with: |
|
||||||
name: tpc-linux-x64-php-${{ matrix.php }}-zts |
|
||||||
if-no-files-found: error |
|
||||||
retention-days: 7 |
|
||||||
path: | |
|
||||||
tpc |
|
||||||
vendor/swoole/phpx/lib/libphpx.so |
|
||||||
third_party/phpy/modules/phpy.so |
|
||||||
|
|
||||||
phpunit: |
|
||||||
name: PHPUnit - PHP ${{ matrix.php }} ZTS |
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') || !contains(github.event.head_commit.message || '', '--skip-tests') }} |
|
||||||
runs-on: ubuntu-22.04 |
|
||||||
timeout-minutes: 30 |
|
||||||
needs: build |
|
||||||
strategy: |
|
||||||
fail-fast: false |
|
||||||
matrix: |
|
||||||
php: ["8.4", "8.5"] |
|
||||||
env: |
|
||||||
PHPX_HOME: ${{ github.workspace }}/vendor/swoole/phpx |
|
||||||
|
|
||||||
steps: |
|
||||||
- name: Checkout TypePHP |
|
||||||
uses: actions/checkout@v4 |
|
||||||
|
|
||||||
- name: Setup PHP |
|
||||||
uses: shivammathur/setup-php@v2 |
|
||||||
with: |
|
||||||
php-version: ${{ matrix.php }} |
|
||||||
coverage: none |
|
||||||
extensions: curl, redis, mbstring, ffi |
|
||||||
ini-values: ffi.enable=1, phpy.enable_operator_overloading=0, precision=17, memory_limit=4G, error_reporting=E_ERROR|E_WARNING, display_errors=1, display_startup_errors=1, log_errors=0 |
|
||||||
tools: composer:v2 |
|
||||||
env: |
|
||||||
fail-fast: true |
|
||||||
phpts: ts |
|
||||||
update: true |
|
||||||
|
|
||||||
- name: Verify ZTS PHP |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php -r 'if (!PHP_ZTS) { fwrite(STDERR, "Expected a ZTS PHP build\n"); exit(1); }' |
|
||||||
case "$(php -r 'echo PHP_VERSION;')" in |
|
||||||
"${{ matrix.php }}"*) ;; |
|
||||||
*) echo "setup-php installed an unexpected PHP version" >&2; exit 1 ;; |
|
||||||
esac |
|
||||||
|
|
||||||
- name: Patch php_hash.h C++ compatibility |
|
||||||
uses: ./.github/actions/patch-php-headers |
|
||||||
|
|
||||||
- name: Install native build dependencies |
|
||||||
run: | |
|
||||||
sudo apt-get update |
|
||||||
sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config python3-dev |
|
||||||
|
|
||||||
- name: Install Composer dependencies |
|
||||||
run: composer install --prefer-dist --no-progress |
|
||||||
|
|
||||||
- name: Download Linux x64 build outputs |
|
||||||
uses: actions/download-artifact@v4 |
|
||||||
with: |
|
||||||
name: tpc-linux-x64-php-${{ matrix.php }}-zts |
|
||||||
path: . |
|
||||||
|
|
||||||
- name: Verify Linux x64 build outputs |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
test -f ./tpc |
|
||||||
test -f "${PHPX_HOME}/lib/libphpx.so" |
|
||||||
test -f third_party/phpy/modules/phpy.so |
|
||||||
chmod +x ./tpc |
|
||||||
file ./tpc | grep -Eiq 'x86-64|x86_64|amd64' |
|
||||||
|
|
||||||
- name: Enable phpy extension |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php_ini_dir="$(php-config --ini-dir)" |
|
||||||
echo "extension=${GITHUB_WORKSPACE}/third_party/phpy/modules/phpy.so" \ |
|
||||||
| sudo tee "${php_ini_dir}/90-phpy.ini" |
|
||||||
echo "PHP_INI_SCAN_DIR=${php_ini_dir}" >> "${GITHUB_ENV}" |
|
||||||
php --ri phpy |
|
||||||
|
|
||||||
- name: Configure native library path |
|
||||||
shell: bash |
|
||||||
run: echo "LD_LIBRARY_PATH=${PHPX_HOME}/lib:$(php-config --prefix)/lib" >> "${GITHUB_ENV}" |
|
||||||
|
|
||||||
- name: Run PHPUnit |
|
||||||
run: vendor/bin/phpunit |
|
||||||
|
|
||||||
phpt: |
|
||||||
name: PHPT - PHP ${{ matrix.php }} ZTS |
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/') || !contains(github.event.head_commit.message || '', '--skip-tests') }} |
|
||||||
runs-on: ubuntu-22.04 |
|
||||||
timeout-minutes: 180 |
|
||||||
needs: build |
|
||||||
strategy: |
|
||||||
fail-fast: false |
|
||||||
matrix: |
|
||||||
php: ["8.4", "8.5"] |
|
||||||
env: |
|
||||||
PHPX_HOME: ${{ github.workspace }}/vendor/swoole/phpx |
|
||||||
NO_INTERACTION: 1 |
|
||||||
REPORT_EXIT_STATUS: 1 |
|
||||||
TYPEPHP_PHPT_GENERATED_ARTIFACT_DIR: ${{ github.workspace }}/build/phpt-generated |
|
||||||
|
|
||||||
steps: |
|
||||||
- name: Checkout TypePHP |
|
||||||
uses: actions/checkout@v4 |
|
||||||
|
|
||||||
- name: Setup PHP |
|
||||||
uses: shivammathur/setup-php@v2 |
|
||||||
with: |
|
||||||
php-version: ${{ matrix.php }} |
|
||||||
coverage: none |
|
||||||
extensions: curl, redis, mbstring, ffi |
|
||||||
ini-values: ffi.enable=1, phpy.enable_operator_overloading=0, opcache.jit=0, precision=17, memory_limit=4G, error_reporting=E_ERROR|E_WARNING, display_errors=1, display_startup_errors=1, log_errors=0 |
|
||||||
tools: composer:v2 |
|
||||||
env: |
|
||||||
fail-fast: true |
|
||||||
phpts: ts |
|
||||||
update: true |
|
||||||
|
|
||||||
- name: Verify ZTS PHP |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php -r 'if (!PHP_ZTS) { fwrite(STDERR, "Expected a ZTS PHP build\n"); exit(1); }' |
|
||||||
case "$(php -r 'echo PHP_VERSION;')" in |
|
||||||
"${{ matrix.php }}"*) ;; |
|
||||||
*) echo "setup-php installed an unexpected PHP version" >&2; exit 1 ;; |
|
||||||
esac |
|
||||||
|
|
||||||
- name: Patch php_hash.h C++ compatibility |
|
||||||
uses: ./.github/actions/patch-php-headers |
|
||||||
|
|
||||||
- name: Install native build dependencies |
|
||||||
run: | |
|
||||||
sudo apt-get update |
|
||||||
sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config python3-dev |
|
||||||
|
|
||||||
- name: Configure ZTS PHP embed library |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php_home="$(php-config --prefix)" |
|
||||||
embed_library="${php_home}/lib/libphp.so" |
|
||||||
test -x "${php_home}/bin/php-config" |
|
||||||
test -f "${embed_library}" |
|
||||||
echo "PHP_HOME=${php_home}" >> "${GITHUB_ENV}" |
|
||||||
echo "Using ZTS PHP $(php-config --version) embed library: ${embed_library}" |
|
||||||
|
|
||||||
- name: Install Composer dependencies |
|
||||||
run: composer install --prefer-dist --no-progress |
|
||||||
|
|
||||||
- name: Download Linux x64 build outputs |
|
||||||
uses: actions/download-artifact@v4 |
|
||||||
with: |
|
||||||
name: tpc-linux-x64-php-${{ matrix.php }}-zts |
|
||||||
path: . |
|
||||||
|
|
||||||
- name: Verify Linux x64 build outputs |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
test -f ./tpc |
|
||||||
test -f "${PHPX_HOME}/lib/libphpx.so" |
|
||||||
test -f third_party/phpy/modules/phpy.so |
|
||||||
chmod +x ./tpc |
|
||||||
file ./tpc | grep -Eiq 'x86-64|x86_64|amd64' |
|
||||||
|
|
||||||
- name: Enable phpy extension |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
php_ini_dir="$(php-config --ini-dir)" |
|
||||||
echo "extension=${GITHUB_WORKSPACE}/third_party/phpy/modules/phpy.so" \ |
|
||||||
| sudo tee "${php_ini_dir}/90-phpy.ini" |
|
||||||
echo "PHP_INI_SCAN_DIR=${php_ini_dir}" >> "${GITHUB_ENV}" |
|
||||||
php --ri phpy |
|
||||||
|
|
||||||
- name: Configure native library path |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
test -f "${PHPX_HOME}/lib/libphpx.so" |
|
||||||
test -f "${PHP_HOME}/lib/libphp.so" |
|
||||||
echo "LD_LIBRARY_PATH=${PHPX_HOME}/lib:${PHP_HOME}/lib" >> "${GITHUB_ENV}" |
|
||||||
|
|
||||||
- name: Show build environment |
|
||||||
run: | |
|
||||||
./tpc --version |
|
||||||
php -v |
|
||||||
php --ini |
|
||||||
ldd ./tpc | grep -E 'libphp(x)?[0-9.]*\.so' |
|
||||||
cmake --version |
|
||||||
c++ --version |
|
||||||
|
|
||||||
- name: Run compiler PHPT suite with bootstrap compiler |
|
||||||
run: | |
|
||||||
mkdir -p build |
|
||||||
php run-tests.php -q -j8 --compiler ./tpc \ |
|
||||||
-w build/failed-tests.txt -W build/test-results.txt tests/compiler |
|
||||||
|
|
||||||
- name: Package tested Linux compiler |
|
||||||
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5' |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
composer install --no-dev --prefer-dist --no-progress --classmap-authoritative |
|
||||||
export TYPEPHP_PACKAGE_VERSION="${GITHUB_REF_NAME}" |
|
||||||
php package.php |
|
||||||
test "$(find . -maxdepth 1 -name 'tpc_v*_linux_*.tar.gz' -type f | wc -l)" -eq 1 |
|
||||||
|
|
||||||
- name: Upload Linux release package |
|
||||||
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5' |
|
||||||
uses: actions/upload-artifact@v4 |
|
||||||
with: |
|
||||||
name: release-linux-x64-php-${{ matrix.php }}-zts |
|
||||||
if-no-files-found: error |
|
||||||
retention-days: 1 |
|
||||||
path: tpc_v*_linux_*.tar.gz |
|
||||||
|
|
||||||
- name: Upload PHPT failure artifacts |
|
||||||
if: failure() |
|
||||||
uses: actions/upload-artifact@v4 |
|
||||||
with: |
|
||||||
name: phpt-failures-linux-x64-php-${{ matrix.php }}-zts |
|
||||||
if-no-files-found: ignore |
|
||||||
retention-days: 7 |
|
||||||
path: | |
|
||||||
build/failed-tests.txt |
|
||||||
build/test-results.txt |
|
||||||
build/**/*.cc |
|
||||||
build/**/*.h |
|
||||||
php_test_results_*.txt |
|
||||||
tests/compiler/**/*.diff |
|
||||||
tests/compiler/**/*.log |
|
||||||
tests/compiler/**/*.out |
|
||||||
@ -1,43 +0,0 @@ |
|||||||
name: macOS ARM64 |
|
||||||
|
|
||||||
on: |
|
||||||
push: |
|
||||||
pull_request: |
|
||||||
workflow_dispatch: |
|
||||||
|
|
||||||
permissions: |
|
||||||
contents: read |
|
||||||
|
|
||||||
concurrency: |
|
||||||
group: macos-arm64-${{ github.ref }} |
|
||||||
cancel-in-progress: true |
|
||||||
|
|
||||||
env: |
|
||||||
COMPOSER_NO_INTERACTION: 1 |
|
||||||
COMPOSER_PROCESS_TIMEOUT: 0 |
|
||||||
|
|
||||||
jobs: |
|
||||||
build: |
|
||||||
name: Build - PHP ${{ matrix.php }} ZTS |
|
||||||
runs-on: macos-15 |
|
||||||
timeout-minutes: 90 |
|
||||||
strategy: |
|
||||||
fail-fast: false |
|
||||||
matrix: |
|
||||||
php: ["8.4", "8.5"] |
|
||||||
env: |
|
||||||
PHPX_HOME: ${{ github.workspace }}/vendor/swoole/phpx |
|
||||||
|
|
||||||
steps: |
|
||||||
- name: Checkout TypePHP |
|
||||||
uses: actions/checkout@v4 |
|
||||||
|
|
||||||
- name: Build and smoke test |
|
||||||
uses: ./.github/actions/unix-arm64-build |
|
||||||
with: |
|
||||||
php-version: ${{ matrix.php }} |
|
||||||
os: macos |
|
||||||
library-extension: dylib |
|
||||||
smoke-directory: macos |
|
||||||
smoke-binary: macos_smoke |
|
||||||
smoke-output: macos-arm64-smoke-ok:zts |
|
||||||
@ -1,141 +0,0 @@ |
|||||||
name: Package release |
|
||||||
|
|
||||||
on: |
|
||||||
push: |
|
||||||
tags: |
|
||||||
- "v*" |
|
||||||
|
|
||||||
permissions: |
|
||||||
actions: read |
|
||||||
contents: write |
|
||||||
|
|
||||||
concurrency: |
|
||||||
group: release-${{ github.ref }} |
|
||||||
cancel-in-progress: false |
|
||||||
|
|
||||||
jobs: |
|
||||||
publish: |
|
||||||
name: Publish ${{ github.ref_name }} |
|
||||||
runs-on: ubuntu-22.04 |
|
||||||
timeout-minutes: 240 |
|
||||||
env: |
|
||||||
GH_TOKEN: ${{ github.token }} |
|
||||||
|
|
||||||
steps: |
|
||||||
- name: Wait for tested binary packages |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
set -euo pipefail |
|
||||||
|
|
||||||
wait_for_workflow() { |
|
||||||
local workflow="$1" |
|
||||||
local output_name="$2" |
|
||||||
local run_json run_id status conclusion |
|
||||||
|
|
||||||
for attempt in $(seq 1 960); do |
|
||||||
run_json="$(gh run list \ |
|
||||||
--repo "${GITHUB_REPOSITORY}" \ |
|
||||||
--workflow "${workflow}" \ |
|
||||||
--commit "${GITHUB_SHA}" \ |
|
||||||
--event push \ |
|
||||||
--limit 20 \ |
|
||||||
--json databaseId,status,conclusion,headBranch,createdAt)" |
|
||||||
run_id="$(jq -r --arg tag "${GITHUB_REF_NAME}" ' |
|
||||||
[.[] | select(.headBranch == $tag)] |
|
||||||
| sort_by(.createdAt) |
|
||||||
| last |
|
||||||
| .databaseId // empty |
|
||||||
' <<<"${run_json}")" |
|
||||||
|
|
||||||
if [[ -z "${run_id}" ]]; then |
|
||||||
echo "Waiting for ${workflow} to start for tag ${GITHUB_REF_NAME}..." |
|
||||||
sleep 15 |
|
||||||
continue |
|
||||||
fi |
|
||||||
|
|
||||||
status="$(jq -r --argjson id "${run_id}" ' |
|
||||||
.[] | select(.databaseId == $id) | .status |
|
||||||
' <<<"${run_json}")" |
|
||||||
conclusion="$(jq -r --argjson id "${run_id}" ' |
|
||||||
.[] | select(.databaseId == $id) | .conclusion |
|
||||||
' <<<"${run_json}")" |
|
||||||
if [[ "${status}" == "completed" ]]; then |
|
||||||
if [[ "${conclusion}" != "success" ]]; then |
|
||||||
echo "${workflow} run ${run_id} completed with ${conclusion}" >&2 |
|
||||||
exit 1 |
|
||||||
fi |
|
||||||
echo "${workflow} run ${run_id} succeeded" |
|
||||||
echo "${output_name}=${run_id}" >> "${GITHUB_ENV}" |
|
||||||
return 0 |
|
||||||
fi |
|
||||||
|
|
||||||
echo "Waiting for ${workflow} run ${run_id}: ${status}" |
|
||||||
sleep 15 |
|
||||||
done |
|
||||||
|
|
||||||
echo "Timed out waiting for ${workflow}" >&2 |
|
||||||
exit 1 |
|
||||||
} |
|
||||||
|
|
||||||
wait_for_workflow linux-x64.yml TESTS_RUN_ID |
|
||||||
wait_for_workflow windows-build.yml WINDOWS_RUN_ID |
|
||||||
wait_for_workflow linux-arm64.yml LINUX_ARM64_RUN_ID |
|
||||||
wait_for_workflow macos-arm64.yml MACOS_ARM64_RUN_ID |
|
||||||
|
|
||||||
- name: Download tested packages |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
set -euo pipefail |
|
||||||
mkdir -p dist/linux-x64 dist/linux-arm64 dist/macos-arm64 dist/windows |
|
||||||
gh run download "${TESTS_RUN_ID}" \ |
|
||||||
--repo "${GITHUB_REPOSITORY}" \ |
|
||||||
--pattern 'release-linux-*' \ |
|
||||||
--dir dist/linux-x64 |
|
||||||
gh run download "${WINDOWS_RUN_ID}" \ |
|
||||||
--repo "${GITHUB_REPOSITORY}" \ |
|
||||||
--pattern 'release-windows-*' \ |
|
||||||
--dir dist/windows |
|
||||||
gh run download "${LINUX_ARM64_RUN_ID}" \ |
|
||||||
--repo "${GITHUB_REPOSITORY}" \ |
|
||||||
--pattern 'release-linux-arm64-*' \ |
|
||||||
--dir dist/linux-arm64 |
|
||||||
gh run download "${MACOS_ARM64_RUN_ID}" \ |
|
||||||
--repo "${GITHUB_REPOSITORY}" \ |
|
||||||
--pattern 'release-macos-arm64-*' \ |
|
||||||
--dir dist/macos-arm64 |
|
||||||
|
|
||||||
test "$(find dist -type f -name 'tpc_v*_linux_x64.tar.gz' | wc -l)" -eq 1 |
|
||||||
test "$(find dist -type f -name 'tpc_v*_linux_arm64.tar.gz' | wc -l)" -eq 1 |
|
||||||
test "$(find dist -type f -name 'tpc_v*_macos_arm64.tar.gz' | wc -l)" -eq 1 |
|
||||||
test "$(find dist -type f -name 'tpc_v*_windows_x64.zip' | wc -l)" -eq 1 |
|
||||||
|
|
||||||
- name: Prepare release assets |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
set -euo pipefail |
|
||||||
mkdir -p release-assets |
|
||||||
find dist -type f \( -name '*.tar.gz' -o -name '*.zip' \) \ |
|
||||||
-exec cp '{}' release-assets/ \; |
|
||||||
test "$(find release-assets -maxdepth 1 -type f \( -name '*.tar.gz' -o -name '*.zip' \) | wc -l)" -eq 4 |
|
||||||
( |
|
||||||
cd release-assets |
|
||||||
sha256sum ./*.tar.gz ./*.zip > SHA256SUMS |
|
||||||
) |
|
||||||
ls -lh release-assets |
|
||||||
|
|
||||||
- name: Publish GitHub release |
|
||||||
shell: bash |
|
||||||
run: | |
|
||||||
set -euo pipefail |
|
||||||
mapfile -t assets < <(find release-assets -maxdepth 1 -type f | sort) |
|
||||||
if gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then |
|
||||||
gh release upload "${GITHUB_REF_NAME}" "${assets[@]}" \ |
|
||||||
--repo "${GITHUB_REPOSITORY}" \ |
|
||||||
--clobber |
|
||||||
else |
|
||||||
gh release create "${GITHUB_REF_NAME}" "${assets[@]}" \ |
|
||||||
--repo "${GITHUB_REPOSITORY}" \ |
|
||||||
--verify-tag \ |
|
||||||
--title "TypePHP ${GITHUB_REF_NAME}" \ |
|
||||||
--generate-notes |
|
||||||
fi |
|
||||||
@ -1,449 +0,0 @@ |
|||||||
name: Windows x64 |
|
||||||
|
|
||||||
on: |
|
||||||
push: |
|
||||||
pull_request: |
|
||||||
workflow_dispatch: |
|
||||||
|
|
||||||
permissions: |
|
||||||
contents: read |
|
||||||
|
|
||||||
concurrency: |
|
||||||
group: windows-tpc-${{ github.workflow }}-${{ github.ref }} |
|
||||||
cancel-in-progress: true |
|
||||||
|
|
||||||
env: |
|
||||||
COMPOSER_NO_INTERACTION: 1 |
|
||||||
COMPOSER_PROCESS_TIMEOUT: 0 |
|
||||||
|
|
||||||
jobs: |
|
||||||
build-tpc: |
|
||||||
name: Build - PHP ${{ matrix.php }} ZTS |
|
||||||
runs-on: windows-2022 |
|
||||||
timeout-minutes: 90 |
|
||||||
strategy: |
|
||||||
fail-fast: false |
|
||||||
matrix: |
|
||||||
php: ["8.4", "8.5"] |
|
||||||
|
|
||||||
steps: |
|
||||||
- name: Checkout TypePHP |
|
||||||
uses: actions/checkout@v4 |
|
||||||
|
|
||||||
- name: Setup PHP |
|
||||||
uses: shivammathur/setup-php@v2 |
|
||||||
with: |
|
||||||
php-version: ${{ matrix.php }} |
|
||||||
coverage: none |
|
||||||
extensions: zip |
|
||||||
tools: composer:v2 |
|
||||||
env: |
|
||||||
fail-fast: true |
|
||||||
phpts: ts |
|
||||||
update: true |
|
||||||
|
|
||||||
- name: Resolve PHP build environment |
|
||||||
id: php-build-env |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
$ErrorActionPreference = 'Stop' |
|
||||||
|
|
||||||
$phpVersion = php -r 'echo PHP_VERSION;' |
|
||||||
if (-not $phpVersion.StartsWith('${{ matrix.php }}.')) { |
|
||||||
throw "setup-php installed PHP $phpVersion, expected ${{ matrix.php }}.x" |
|
||||||
} |
|
||||||
$threadSafety = php -r 'echo PHP_ZTS ? "zts" : "nts";' |
|
||||||
if ($threadSafety -ne 'zts') { |
|
||||||
throw "setup-php installed $threadSafety PHP, expected zts" |
|
||||||
} |
|
||||||
$zipAvailable = php -r 'echo class_exists("ZipArchive") ? "yes" : "no";' |
|
||||||
if ($zipAvailable -ne 'yes') { |
|
||||||
throw 'setup-php did not enable the ZipArchive extension required by package.php' |
|
||||||
} |
|
||||||
$phpExe = (Get-Command php).Source |
|
||||||
$phpHome = Split-Path -Parent $phpExe |
|
||||||
$archiveName = "php-devel-pack-$phpVersion-Win32-vs17-x64.zip" |
|
||||||
$archive = Join-Path $env:RUNNER_TEMP $archiveName |
|
||||||
|
|
||||||
"PHP_VERSION=$phpVersion" | Out-File $env:GITHUB_ENV -Append -Encoding utf8 |
|
||||||
"PHP_THREAD_SAFETY=$threadSafety" | Out-File $env:GITHUB_ENV -Append -Encoding utf8 |
|
||||||
"PHP_HOME=$phpHome" | Out-File $env:GITHUB_ENV -Append -Encoding utf8 |
|
||||||
"PHPX_HOME=${{ github.workspace }}\vendor\swoole\phpx" | |
|
||||||
Out-File $env:GITHUB_ENV -Append -Encoding utf8 |
|
||||||
"PHP_DEVEL_ARCHIVE=$archive" | Out-File $env:GITHUB_ENV -Append -Encoding utf8 |
|
||||||
"version=$phpVersion" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 |
|
||||||
"thread_safety=$threadSafety" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 |
|
||||||
"archive=$archive" | Out-File $env:GITHUB_OUTPUT -Append -Encoding utf8 |
|
||||||
|
|
||||||
- name: Cache PHP development package |
|
||||||
id: cache-php-devel |
|
||||||
uses: actions/cache/restore@v4 |
|
||||||
with: |
|
||||||
path: ${{ steps.php-build-env.outputs.archive }} |
|
||||||
key: windows-2022-php-devel-${{ steps.php-build-env.outputs.version }}-${{ steps.php-build-env.outputs.thread_safety }}-vs17-x64 |
|
||||||
|
|
||||||
- name: Install matching PHP development SDK |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
$ErrorActionPreference = 'Stop' |
|
||||||
|
|
||||||
$phpVersion = $env:PHP_VERSION |
|
||||||
$phpHome = $env:PHP_HOME |
|
||||||
$threadSafety = $env:PHP_THREAD_SAFETY |
|
||||||
$archive = $env:PHP_DEVEL_ARCHIVE |
|
||||||
$archiveName = Split-Path -Leaf $archive |
|
||||||
$extractDir = Join-Path $env:RUNNER_TEMP "php-devel-$phpVersion" |
|
||||||
|
|
||||||
if (-not (Test-Path $archive)) { |
|
||||||
$downloaded = $false |
|
||||||
foreach ($baseUrl in @( |
|
||||||
'https://downloads.php.net/~windows/releases', |
|
||||||
'https://downloads.php.net/~windows/releases/archives' |
|
||||||
)) { |
|
||||||
try { |
|
||||||
Invoke-WebRequest -Uri "$baseUrl/$archiveName" -OutFile $archive |
|
||||||
$downloaded = $true |
|
||||||
break |
|
||||||
} catch { |
|
||||||
Remove-Item $archive -Force -ErrorAction SilentlyContinue |
|
||||||
} |
|
||||||
} |
|
||||||
if (-not $downloaded) { |
|
||||||
throw "Unable to download the PHP $phpVersion ZTS development pack" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Expand-Archive -Path $archive -DestinationPath $extractDir -Force |
|
||||||
$sdkSource = Get-ChildItem $extractDir -Directory | |
|
||||||
Where-Object { Test-Path (Join-Path $_.FullName 'include\main\php.h') } | |
|
||||||
Select-Object -First 1 |
|
||||||
if ($null -eq $sdkSource) { |
|
||||||
throw "The PHP development archive has an unexpected layout: $archiveName" |
|
||||||
} |
|
||||||
|
|
||||||
$sdk = Join-Path $phpHome 'SDK' |
|
||||||
$sdkInclude = Join-Path $sdk 'include' |
|
||||||
$sdkLib = Join-Path $sdk 'lib' |
|
||||||
New-Item $sdkInclude, $sdkLib -ItemType Directory -Force | Out-Null |
|
||||||
Copy-Item (Join-Path $sdkSource.FullName 'include\*') $sdkInclude -Recurse -Force |
|
||||||
Copy-Item (Join-Path $sdkSource.FullName 'lib\*') $sdkLib -Force |
|
||||||
|
|
||||||
$embedLibrary = Join-Path $phpHome 'php8embed.lib' |
|
||||||
if (-not (Test-Path $embedLibrary)) { |
|
||||||
throw "setup-php did not install php8embed.lib in $phpHome" |
|
||||||
} |
|
||||||
Copy-Item $embedLibrary $sdkLib -Force |
|
||||||
|
|
||||||
$coreLibrary = 'php8ts.lib' |
|
||||||
$runtimeLibrary = 'php8ts.dll' |
|
||||||
|
|
||||||
# Temporary compatibility fix for PHP packages predating php/php-src#22940. |
|
||||||
$hashHeader = Join-Path $sdkInclude 'ext\hash\php_hash.h' |
|
||||||
$hashSource = [IO.File]::ReadAllText($hashHeader) |
|
||||||
$invalidAllocation = 'char *base = ecalloc(' |
|
||||||
if ($hashSource.Contains($invalidAllocation)) { |
|
||||||
$hashSource = $hashSource.Replace( |
|
||||||
$invalidAllocation, |
|
||||||
'char *base = (char *) ecalloc(' |
|
||||||
) |
|
||||||
[IO.File]::WriteAllText( |
|
||||||
$hashHeader, |
|
||||||
$hashSource, |
|
||||||
[Text.UTF8Encoding]::new($false) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
foreach ($required in @( |
|
||||||
(Join-Path $sdkInclude 'main\php.h'), |
|
||||||
(Join-Path $sdkLib $coreLibrary), |
|
||||||
(Join-Path $sdkLib 'php8embed.lib'), |
|
||||||
(Join-Path $phpHome $runtimeLibrary) |
|
||||||
)) { |
|
||||||
if (-not (Test-Path $required)) { |
|
||||||
throw "Required PHP SDK file is missing: $required" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Write-Host "Using PHP $phpVersion from $phpHome" |
|
||||||
|
|
||||||
- name: Save PHP development package |
|
||||||
if: steps.cache-php-devel.outputs.cache-hit != 'true' |
|
||||||
uses: actions/cache/save@v4 |
|
||||||
with: |
|
||||||
path: ${{ steps.php-build-env.outputs.archive }} |
|
||||||
key: ${{ steps.cache-php-devel.outputs.cache-primary-key }} |
|
||||||
|
|
||||||
- name: Install Composer dependencies |
|
||||||
run: composer install --prefer-dist --no-progress |
|
||||||
|
|
||||||
- name: Patch PHPX Windows CMake target ordering |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
$ErrorActionPreference = 'Stop' |
|
||||||
|
|
||||||
$cmakeFile = Join-Path $env:PHPX_HOME 'CMakeLists.txt' |
|
||||||
$source = [IO.File]::ReadAllText($cmakeFile).Replace("`r`n", "`n") |
|
||||||
$copyBlockPattern = '(?ms)^ # 复制 DLL 到输出目录\n file\(GLOB MPDEC_DLLS .*?^ endforeach\(\)\n' |
|
||||||
$copyBlock = [regex]::Match($source, $copyBlockPattern) |
|
||||||
if (-not $copyBlock.Success) { |
|
||||||
throw 'Unable to locate the pre-target PHPX mpdecimal copy block' |
|
||||||
} |
|
||||||
$source = $source.Remove($copyBlock.Index, $copyBlock.Length) |
|
||||||
|
|
||||||
$targetPattern = '(?ms)(add_library\(phpx SHARED \$\{SRC_FILES\}\)\nset_target_properties\(phpx PROPERTIES\n CLEAN_DIRECT_OUTPUT 1\n\)\n)' |
|
||||||
$target = [regex]::Match($source, $targetPattern) |
|
||||||
if (-not $target.Success) { |
|
||||||
throw 'Unable to locate the PHPX target declaration' |
|
||||||
} |
|
||||||
|
|
||||||
$copyBlockText = $copyBlock.Value.Replace(' # 复制 DLL 到输出目录', ' # Copy mpdecimal DLLs after the phpx target exists.') |
|
||||||
$guardedCopyBlock = "`nif (IS_WINDOWS)`n$copyBlockText" + "endif()`n" |
|
||||||
$source = $source.Insert($target.Index + $target.Length, $guardedCopyBlock) |
|
||||||
[IO.File]::WriteAllText($cmakeFile, $source, [Text.UTF8Encoding]::new($false)) |
|
||||||
|
|
||||||
$targetOffset = $source.IndexOf('add_library(phpx SHARED') |
|
||||||
$copyOffset = $source.IndexOf('add_custom_command(TARGET phpx POST_BUILD') |
|
||||||
if ($targetOffset -lt 0 -or $copyOffset -le $targetOffset) { |
|
||||||
throw 'PHPX post-build command still precedes its target declaration' |
|
||||||
} |
|
||||||
|
|
||||||
- name: Configure MSVC |
|
||||||
uses: ilammy/msvc-dev-cmd@v1 |
|
||||||
with: |
|
||||||
arch: x64 |
|
||||||
|
|
||||||
- name: Cache GMP and MPFR |
|
||||||
id: cache-gmp-mpfr |
|
||||||
uses: actions/cache/restore@v4 |
|
||||||
with: |
|
||||||
path: ${{ runner.temp }}\typephp-cache\vcpkg-x64-windows |
|
||||||
key: windows-2022-msvc-vcpkg-gmp-mpfr-x64-v1 |
|
||||||
|
|
||||||
- name: Install GMP and MPFR |
|
||||||
if: steps.cache-gmp-mpfr.outputs.cache-hit != 'true' |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
$ErrorActionPreference = 'Stop' |
|
||||||
|
|
||||||
$triplet = 'x64-windows' |
|
||||||
$vcpkg = Join-Path $env:VCPKG_INSTALLATION_ROOT 'vcpkg.exe' |
|
||||||
& $vcpkg install "gmp:$triplet" "mpfr:$triplet" |
|
||||||
if ($LASTEXITCODE -ne 0) { |
|
||||||
throw "vcpkg failed with exit code $LASTEXITCODE" |
|
||||||
} |
|
||||||
|
|
||||||
$installed = Join-Path $env:VCPKG_INSTALLATION_ROOT "installed\$triplet" |
|
||||||
$cache = Join-Path $env:RUNNER_TEMP 'typephp-cache\vcpkg-x64-windows' |
|
||||||
New-Item "$cache\include", "$cache\lib", "$cache\bin" -ItemType Directory -Force | |
|
||||||
Out-Null |
|
||||||
Copy-Item (Join-Path $installed 'include\*') "$cache\include" -Recurse -Force |
|
||||||
|
|
||||||
foreach ($library in @('gmp.lib', 'gmpxx.lib', 'mpfr.lib')) { |
|
||||||
$source = Join-Path $installed "lib\$library" |
|
||||||
if (-not (Test-Path $source)) { |
|
||||||
throw "vcpkg did not install $library" |
|
||||||
} |
|
||||||
Copy-Item $source "$cache\lib" -Force |
|
||||||
} |
|
||||||
Copy-Item (Join-Path $installed 'bin\*.dll') "$cache\bin" -Force |
|
||||||
|
|
||||||
- name: Save GMP and MPFR |
|
||||||
if: steps.cache-gmp-mpfr.outputs.cache-hit != 'true' |
|
||||||
uses: actions/cache/save@v4 |
|
||||||
with: |
|
||||||
path: ${{ runner.temp }}\typephp-cache\vcpkg-x64-windows |
|
||||||
key: ${{ steps.cache-gmp-mpfr.outputs.cache-primary-key }} |
|
||||||
|
|
||||||
- name: Stage GMP and MPFR |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
$ErrorActionPreference = 'Stop' |
|
||||||
|
|
||||||
$cache = Join-Path $env:RUNNER_TEMP 'typephp-cache\vcpkg-x64-windows' |
|
||||||
$sdkInclude = Join-Path $env:PHP_HOME 'SDK\include' |
|
||||||
$sdkLib = Join-Path $env:PHP_HOME 'SDK\lib' |
|
||||||
Copy-Item "$cache\include\*" $sdkInclude -Recurse -Force |
|
||||||
|
|
||||||
foreach ($library in @('gmp.lib', 'gmpxx.lib', 'mpfr.lib')) { |
|
||||||
$source = Join-Path $cache "lib\$library" |
|
||||||
if (-not (Test-Path $source)) { |
|
||||||
throw "The dependency cache does not contain $library" |
|
||||||
} |
|
||||||
Copy-Item $source $sdkLib -Force |
|
||||||
} |
|
||||||
Copy-Item "$cache\bin\*.dll" $env:PHP_HOME -Force |
|
||||||
|
|
||||||
- name: Cache mpdecimal |
|
||||||
id: cache-mpdecimal |
|
||||||
uses: actions/cache/restore@v4 |
|
||||||
with: |
|
||||||
path: ${{ env.PHPX_HOME }}\thirdparty\mpdecimal\vcbuild\dist64 |
|
||||||
key: windows-2022-msvc-mpdecimal-x64-${{ hashFiles('vendor/swoole/phpx/thirdparty/mpdecimal/**') }} |
|
||||||
|
|
||||||
- name: Build mpdecimal |
|
||||||
if: steps.cache-mpdecimal.outputs.cache-hit != 'true' |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
$ErrorActionPreference = 'Stop' |
|
||||||
|
|
||||||
$buildScript = Join-Path $env:PHPX_HOME 'thirdparty\mpdecimal\vcbuild\vcbuild64.bat' |
|
||||||
$buildDirectory = Split-Path -Parent $buildScript |
|
||||||
Push-Location $buildDirectory |
|
||||||
try { |
|
||||||
& cmd.exe /d /s /c vcbuild64.bat |
|
||||||
if ($LASTEXITCODE -ne 0) { |
|
||||||
throw "mpdecimal build failed with exit code $LASTEXITCODE" |
|
||||||
} |
|
||||||
} finally { |
|
||||||
Pop-Location |
|
||||||
} |
|
||||||
|
|
||||||
- name: Save mpdecimal |
|
||||||
if: steps.cache-mpdecimal.outputs.cache-hit != 'true' |
|
||||||
uses: actions/cache/save@v4 |
|
||||||
with: |
|
||||||
path: ${{ env.PHPX_HOME }}\thirdparty\mpdecimal\vcbuild\dist64 |
|
||||||
key: ${{ steps.cache-mpdecimal.outputs.cache-primary-key }} |
|
||||||
|
|
||||||
- name: Stage mpdecimal |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
$ErrorActionPreference = 'Stop' |
|
||||||
|
|
||||||
$dist = Join-Path $env:PHPX_HOME 'thirdparty\mpdecimal\vcbuild\dist64' |
|
||||||
$sdkInclude = Join-Path $env:PHP_HOME 'SDK\include' |
|
||||||
$sdkLib = Join-Path $env:PHP_HOME 'SDK\lib' |
|
||||||
Copy-Item (Join-Path $dist '*.lib') $sdkLib -Force |
|
||||||
Copy-Item (Join-Path $dist '*.dll') $sdkLib -Force |
|
||||||
Copy-Item (Join-Path $dist '*.dll') $env:PHP_HOME -Force |
|
||||||
Copy-Item (Join-Path $dist 'mpdecimal.h') $sdkInclude -Force |
|
||||||
Copy-Item (Join-Path $dist 'decimal.hh') $sdkInclude -Force |
|
||||||
|
|
||||||
- name: Build PHPX |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
$ErrorActionPreference = 'Stop' |
|
||||||
|
|
||||||
$phpxBuild = Join-Path $env:PHPX_HOME 'build' |
|
||||||
$sdkLib = Join-Path $env:PHP_HOME 'SDK\lib' |
|
||||||
cmake -S $env:PHPX_HOME -B $phpxBuild -G Ninja ` |
|
||||||
-D CMAKE_BUILD_TYPE=Release ` |
|
||||||
-D BUILD_TESTS=OFF ` |
|
||||||
-D BUILD_EXT=OFF ` |
|
||||||
-D GITHUB_ACTION=ON ` |
|
||||||
-D MPDECIMAL_LIBRARY="$sdkLib\libmpdec-4.0.1.dll.lib" ` |
|
||||||
-D MPDECIMALXX_LIBRARY="$sdkLib\libmpdec++-4.0.1.dll.lib" |
|
||||||
if ($LASTEXITCODE -ne 0) { |
|
||||||
throw "PHPX configuration failed with exit code $LASTEXITCODE" |
|
||||||
} |
|
||||||
|
|
||||||
cmake --build $phpxBuild --target phpx --parallel 2 |
|
||||||
if ($LASTEXITCODE -ne 0) { |
|
||||||
throw "PHPX build failed with exit code $LASTEXITCODE" |
|
||||||
} |
|
||||||
|
|
||||||
foreach ($required in @( |
|
||||||
(Join-Path $env:PHPX_HOME 'lib\phpx.lib'), |
|
||||||
(Join-Path $phpxBuild 'phpx.dll') |
|
||||||
)) { |
|
||||||
if (-not (Test-Path $required)) { |
|
||||||
throw "Required PHPX build output is missing: $required" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
- name: Build tpc.exe |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
php bin\tpc.php project.yml --job 1 --no-progress |
|
||||||
if ($LASTEXITCODE -ne 0) { |
|
||||||
throw "TypePHP build failed with exit code $LASTEXITCODE" |
|
||||||
} |
|
||||||
if (-not (Test-Path 'tpc.exe')) { |
|
||||||
throw 'The compiler did not produce tpc.exe' |
|
||||||
} |
|
||||||
|
|
||||||
- name: Run Windows smoke tests |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
$ErrorActionPreference = 'Stop' |
|
||||||
|
|
||||||
$env:PATH = "$env:PHPX_HOME\build;$env:PATH" |
|
||||||
& .\tpc.exe tests\windows\smoke\project.yml --job 1 --no-progress |
|
||||||
if ($LASTEXITCODE -ne 0) { |
|
||||||
throw "Windows smoke project compilation failed with exit code $LASTEXITCODE" |
|
||||||
} |
|
||||||
|
|
||||||
$smokeExe = Join-Path '${{ github.workspace }}' 'tests\windows\smoke\windows_smoke.exe' |
|
||||||
if (-not (Test-Path $smokeExe)) { |
|
||||||
throw "Windows smoke executable was not generated: $smokeExe" |
|
||||||
} |
|
||||||
|
|
||||||
$processInfo = [Diagnostics.ProcessStartInfo]::new() |
|
||||||
$processInfo.FileName = $smokeExe |
|
||||||
$processInfo.ArgumentList.Add('zts') |
|
||||||
$processInfo.UseShellExecute = $false |
|
||||||
$processInfo.RedirectStandardOutput = $true |
|
||||||
$processInfo.RedirectStandardError = $true |
|
||||||
|
|
||||||
$process = [Diagnostics.Process]::new() |
|
||||||
$process.StartInfo = $processInfo |
|
||||||
if (-not $process.Start()) { |
|
||||||
throw 'Unable to start the Windows smoke executable' |
|
||||||
} |
|
||||||
$stdout = $process.StandardOutput.ReadToEnd() |
|
||||||
$stderr = $process.StandardError.ReadToEnd() |
|
||||||
$process.WaitForExit() |
|
||||||
|
|
||||||
Write-Host "Windows smoke stdout: $stdout" |
|
||||||
Write-Host "Windows smoke stderr: $stderr" |
|
||||||
Write-Host "Windows smoke exit code: $($process.ExitCode)" |
|
||||||
if ($process.ExitCode -ne 0) { |
|
||||||
throw "Windows smoke executable failed with exit code $($process.ExitCode)" |
|
||||||
} |
|
||||||
if ($stdout.Trim() -ne 'windows-smoke-ok:zts') { |
|
||||||
throw "Unexpected Windows smoke output: $stdout" |
|
||||||
} |
|
||||||
|
|
||||||
- name: Package tested Windows compiler |
|
||||||
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5' |
|
||||||
shell: pwsh |
|
||||||
run: | |
|
||||||
$ErrorActionPreference = 'Stop' |
|
||||||
|
|
||||||
composer install --no-dev --prefer-dist --no-progress --classmap-authoritative |
|
||||||
if ($LASTEXITCODE -ne 0) { |
|
||||||
throw "Production Composer install failed with exit code $LASTEXITCODE" |
|
||||||
} |
|
||||||
$env:TYPEPHP_PACKAGE_VERSION = $env:GITHUB_REF_NAME |
|
||||||
php package.php |
|
||||||
if ($LASTEXITCODE -ne 0) { |
|
||||||
throw "Windows packaging failed with exit code $LASTEXITCODE" |
|
||||||
} |
|
||||||
|
|
||||||
$packages = @(Get-ChildItem 'tpc_v*_windows_*.zip' -File) |
|
||||||
if ($packages.Count -ne 1) { |
|
||||||
throw "Expected one Windows release package, found $($packages.Count)" |
|
||||||
} |
|
||||||
|
|
||||||
- name: Upload Windows release package |
|
||||||
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5' |
|
||||||
uses: actions/upload-artifact@v4 |
|
||||||
with: |
|
||||||
name: release-windows-x64-php-${{ matrix.php }}-zts |
|
||||||
if-no-files-found: error |
|
||||||
retention-days: 1 |
|
||||||
path: tpc_v*_windows_*.zip |
|
||||||
|
|
||||||
- name: Upload Windows build outputs |
|
||||||
if: always() |
|
||||||
uses: actions/upload-artifact@v4 |
|
||||||
with: |
|
||||||
name: tpc-windows-x64-php-${{ matrix.php }}-zts |
|
||||||
if-no-files-found: warn |
|
||||||
retention-days: 7 |
|
||||||
path: | |
|
||||||
tpc.exe |
|
||||||
tests/windows/smoke/windows_smoke.exe |
|
||||||
tests/windows/smoke/build/**/*.cc |
|
||||||
tests/windows/smoke/build/**/*.h |
|
||||||
tests/windows/smoke/build/**/*.rsp |
|
||||||
@ -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,103 @@ |
|||||||
|
# CLAUDE.md |
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
||||||
|
|
||||||
|
## Project Overview |
||||||
|
|
||||||
|
TypePHP is a PHP native compilation project. Its `tpc` command is TypePHP Compiler (AOT), which translates PHP source code to C++, then compiles it with GCC/Clang/MSVC into native binaries. It supports Linux (primary), macOS, and Windows. |
||||||
|
|
||||||
|
**Prerequisites**: PHP 8.2+, GCC 9+ (C++17), CMake 3.24+. The `swoole/phpx` extension must be compiled (see README.md). |
||||||
|
|
||||||
|
## TypePHP Language Design Principles |
||||||
|
|
||||||
|
TypePHP should not blindly mirror every PHP language behavior. Most PHP syntax and semantics should remain compatible with ZendPHP, but some legal PHP constructs are historical baggage or language-design mistakes that conflict with static compilation, clear semantics, or robust generated C++ code. |
||||||
|
|
||||||
|
When reviewing or changing compiler behavior: |
||||||
|
|
||||||
|
- Prefer PHP compatibility for common, well-defined syntax that does not weaken the TypePHP static model. |
||||||
|
- Reject PHP historical baggage when the syntax is ambiguous, surprising, or only preserved for legacy compatibility. |
||||||
|
- Diagnose such cases as early as possible during preprocessing/static compilation, instead of deferring to runtime TypeCheck or ZendVM errors. |
||||||
|
- Provide precise errors that include the relevant function/method name, parameter/property name, and type information where applicable. |
||||||
|
- Compare with other statically compiled languages such as C/C++, Java, C#, Go, Rust, Kotlin, and TypeScript before deciding whether TypePHP should preserve or reject a PHP behavior. |
||||||
|
|
||||||
|
Example: `function test($a = 1, $b, $c) {}` is legal in PHP, but the default value for `$a` is effectively ignored and all parameters become required. This is a PHP historical compatibility artifact. TypePHP should reject it during preprocessing instead of preserving the behavior. |
||||||
|
|
||||||
|
Example: PHP permits `return $value;` inside `__construct()` and lets callers consume `parent::__construct()` as a value, even though constructors cannot declare a return type. TypePHP treats constructors consistently with C++/Java-style semantics: constructors initialize objects and must not return values. `return;` is allowed, but `return $value;` or using a constructor call as a value must be rejected during static compilation. |
||||||
|
|
||||||
|
## Build & Test Commands |
||||||
|
|
||||||
|
```bash |
||||||
|
# Install PHP dependencies |
||||||
|
composer install |
||||||
|
|
||||||
|
# Compile a PHP project to a native binary |
||||||
|
./tpc <path-to-project-or-file> |
||||||
|
|
||||||
|
# Run all PHPUnit tests |
||||||
|
./vendor/bin/phpunit |
||||||
|
|
||||||
|
# Run a single PHPUnit test class |
||||||
|
./vendor/bin/phpunit phpunit/src/AstNodeTypeTest.php |
||||||
|
|
||||||
|
# Run PHPT integration tests (all) |
||||||
|
php run-tests.php tests/compiler/ |
||||||
|
|
||||||
|
# Run a single PHPT test |
||||||
|
php run-tests.php tests/compiler/arrays.phpt |
||||||
|
``` |
||||||
|
|
||||||
|
## Architecture |
||||||
|
|
||||||
|
### Translation Pipeline |
||||||
|
|
||||||
|
The compiler follows a 4-stage pipeline, orchestrated by `src/Translator.php` (the main entry point): |
||||||
|
|
||||||
|
1. **prepare()** — Scan PHP files, collect symbol declarations and dependencies, topological-sort for compilation order |
||||||
|
2. **convert()** — Parse PHP AST via `nikic/php-parser`, translate each node to C++ source code |
||||||
|
3. **compile()** — Invoke the platform C++ compiler (GCC/Clang/MSVC) on generated `.cc` files |
||||||
|
4. **build()** — Link object files into a native binary executable |
||||||
|
|
||||||
|
### Class Hierarchy |
||||||
|
|
||||||
|
``` |
||||||
|
src/CompilerBase.php (core PHP→C++ translation logic, indent/output/mode helpers) |
||||||
|
├─ uses traits: AstNodeType, FuncCallOptimizer, ClosureGenerator, |
||||||
|
│ PlaceHolderGenerator, PropertyPromotion, MagicMethodDetector |
||||||
|
└─ src/Preprocessor.php (scanning, symbol tables, dependency sort, YAML config) |
||||||
|
└─ src/Translator.php (full pipeline: prepare→convert→compile→build) |
||||||
|
└─ src/CompilerTest.php (test-only subclass, used by PHPUnit tests) |
||||||
|
``` |
||||||
|
|
||||||
|
### Key Components |
||||||
|
|
||||||
|
| Directory | Purpose | |
||||||
|
|-----------|---------| |
||||||
|
| `src/Entity/` | Data classes: `ClassDef`, `FunctionDef`, `MethodDef`, `PropertyDef`, `ConstantDef`, `InterfaceDef` | |
||||||
|
| `src/Generator/` | Codegen helpers: `ClosureGenerator`, `PlaceHolderGenerator`, `PropertyPromotion`, `Utils` | |
||||||
|
| `src/Backend/` | Compiler abstraction: `CompilerBackend` (abstract) → `Gcc`, `Clang`, `Msvc`. Factory pattern via `CompilerFactory` | |
||||||
|
| `src/Platform/` | OS abstraction: `PlatformBase` → `Linux`, `Macos`, `Windows`. Factory via `PlatformFactory` | |
||||||
|
| `src/Context/` | `ScopeContext` and `FunctionContext` for variable scoping and type tracking | |
||||||
|
| `src/Exception/` | `SyntaxError`, `Unsupported`, `DynamicCall`, `PlaceHolder`, `Skip`, `Redo`, `TestError` | |
||||||
|
| `src/Parser/` | Special-purpose parsers like `StdContainerParser` (C++ std container foreach support) | |
||||||
|
| `src/Resolver/Reflection.php` | Static helpers wrapping PHP reflection (internal class/function detection) | |
||||||
|
| `src/Generator/Symbol.php` | Maps PHP operations to `phpx` C++ API symbol names | |
||||||
|
| `src/Build/FileScanner.php` | Recursive file discovery with extension filtering (supports `.php`, `.cpp`, `.c`, `.s`, `.m`, `.mm`) | |
||||||
|
| `src/Entity/ArgInfo.php` | Generates C function argument info structures for internal function registration | |
||||||
|
| `src/Extractor.php` | Extracts interfaces from PHP classes | |
||||||
|
| `src/Transform/Visitor.php` | Base `NodeVisitorAbstract` extension (skeleton for custom AST visitors) | |
||||||
|
|
||||||
|
### Configuration |
||||||
|
|
||||||
|
- `project.yml` — per-project build config (name, build-mode, C++ standard, compiler flags, sources, resources/icon) |
||||||
|
- Command-line arguments and YAML config are merged in `Preprocessor`, with CLI taking highest priority |
||||||
|
|
||||||
|
### Generated Output |
||||||
|
|
||||||
|
Generated `.cc` and `.o` files land in `build/` directory. The compiled binary is named from `project.yml`'s `name` field (default: `app`). |
||||||
|
|
||||||
|
### Test Infrastructure |
||||||
|
|
||||||
|
- **PHPUnit tests** (`phpunit/src/`) — unit/integration tests for compiler internals. Bootstrap at `phpunit/bootstrap.php` defines a `BaseTest` class with an `exec()` helper that runs the compiler and expects a `TestError` exception containing a given string |
||||||
|
- **PHPT tests** (`tests/compiler/`) — end-to-end tests using the standard PHPT format (`run-tests.php`). Each `.phpt` contains PHP source and expected output sections |
||||||
|
|
||||||
|
When writing new compiler tests, use `CompilerTest::create(ROOT_PATH)` (in `src/CompilerTest.php`) which sets `forTest = true` to enable test-specific behavior without writing files to disk. |
||||||
@ -1,674 +0,0 @@ |
|||||||
GNU GENERAL PUBLIC LICENSE |
|
||||||
Version 3, 29 June 2007 |
|
||||||
|
|
||||||
Copyright (C) 2026 上海识沃网络科技有限公司. <https://www.swoole.com/> |
|
||||||
Everyone is permitted to copy and distribute verbatim copies |
|
||||||
of this license document, but changing it is not allowed. |
|
||||||
|
|
||||||
Preamble |
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for |
|
||||||
software and other kinds of works. |
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed |
|
||||||
to take away your freedom to share and change the works. By contrast, |
|
||||||
the GNU General Public License is intended to guarantee your freedom to |
|
||||||
share and change all versions of a program--to make sure it remains free |
|
||||||
software for all its users. We, the Free Software Foundation, use the |
|
||||||
GNU General Public License for most of our software; it applies also to |
|
||||||
any other work released this way by its authors. You can apply it to |
|
||||||
your programs, too. |
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not |
|
||||||
price. Our General Public Licenses are designed to make sure that you |
|
||||||
have the freedom to distribute copies of free software (and charge for |
|
||||||
them if you wish), that you receive source code or can get it if you |
|
||||||
want it, that you can change the software or use pieces of it in new |
|
||||||
free programs, and that you know you can do these things. |
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you |
|
||||||
these rights or asking you to surrender the rights. Therefore, you have |
|
||||||
certain responsibilities if you distribute copies of the software, or if |
|
||||||
you modify it: responsibilities to respect the freedom of others. |
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether |
|
||||||
gratis or for a fee, you must pass on to the recipients the same |
|
||||||
freedoms that you received. You must make sure that they, too, receive |
|
||||||
or can get the source code. And you must show them these terms so they |
|
||||||
know their rights. |
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps: |
|
||||||
(1) assert copyright on the software, and (2) offer you this License |
|
||||||
giving you legal permission to copy, distribute and/or modify it. |
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains |
|
||||||
that there is no warranty for this free software. For both users' and |
|
||||||
authors' sake, the GPL requires that modified versions be marked as |
|
||||||
changed, so that their problems will not be attributed erroneously to |
|
||||||
authors of previous versions. |
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run |
|
||||||
modified versions of the software inside them, although the manufacturer |
|
||||||
can do so. This is fundamentally incompatible with the aim of |
|
||||||
protecting users' freedom to change the software. The systematic |
|
||||||
pattern of such abuse occurs in the area of products for individuals to |
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we |
|
||||||
have designed this version of the GPL to prohibit the practice for those |
|
||||||
products. If such problems arise substantially in other domains, we |
|
||||||
stand ready to extend this provision to those domains in future versions |
|
||||||
of the GPL, as needed to protect the freedom of users. |
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents. |
|
||||||
States should not allow patents to restrict development and use of |
|
||||||
software on general-purpose computers, but in those that do, we wish to |
|
||||||
avoid the special danger that patents applied to a free program could |
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that |
|
||||||
patents cannot be used to render the program non-free. |
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and |
|
||||||
modification follow. |
|
||||||
|
|
||||||
TERMS AND CONDITIONS |
|
||||||
|
|
||||||
0. Definitions. |
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License. |
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of |
|
||||||
works, such as semiconductor masks. |
|
||||||
|
|
||||||
"The Program" refers to any copyrightable work licensed under this |
|
||||||
License. Each licensee is addressed as "you". "Licensees" and |
|
||||||
"recipients" may be individuals or organizations. |
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work |
|
||||||
in a fashion requiring copyright permission, other than the making of an |
|
||||||
exact copy. The resulting work is called a "modified version" of the |
|
||||||
earlier work or a work "based on" the earlier work. |
|
||||||
|
|
||||||
A "covered work" means either the unmodified Program or a work based |
|
||||||
on the Program. |
|
||||||
|
|
||||||
To "propagate" a work means to do anything with it that, without |
|
||||||
permission, would make you directly or secondarily liable for |
|
||||||
infringement under applicable copyright law, except executing it on a |
|
||||||
computer or modifying a private copy. Propagation includes copying, |
|
||||||
distribution (with or without modification), making available to the |
|
||||||
public, and in some countries other activities as well. |
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other |
|
||||||
parties to make or receive copies. Mere interaction with a user through |
|
||||||
a computer network, with no transfer of a copy, is not conveying. |
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices" |
|
||||||
to the extent that it includes a convenient and prominently visible |
|
||||||
feature that (1) displays an appropriate copyright notice, and (2) |
|
||||||
tells the user that there is no warranty for the work (except to the |
|
||||||
extent that warranties are provided), that licensees may convey the |
|
||||||
work under this License, and how to view a copy of this License. If |
|
||||||
the interface presents a list of user commands or options, such as a |
|
||||||
menu, a prominent item in the list meets this criterion. |
|
||||||
|
|
||||||
1. Source Code. |
|
||||||
|
|
||||||
The "source code" for a work means the preferred form of the work |
|
||||||
for making modifications to it. "Object code" means any non-source |
|
||||||
form of a work. |
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official |
|
||||||
standard defined by a recognized standards body, or, in the case of |
|
||||||
interfaces specified for a particular programming language, one that |
|
||||||
is widely used among developers working in that language. |
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other |
|
||||||
than the work as a whole, that (a) is included in the normal form of |
|
||||||
packaging a Major Component, but which is not part of that Major |
|
||||||
Component, and (b) serves only to enable use of the work with that |
|
||||||
Major Component, or to implement a Standard Interface for which an |
|
||||||
implementation is available to the public in source code form. A |
|
||||||
"Major Component", in this context, means a major essential component |
|
||||||
(kernel, window system, and so on) of the specific operating system |
|
||||||
(if any) on which the executable work runs, or a compiler used to |
|
||||||
produce the work, or an object code interpreter used to run it. |
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all |
|
||||||
the source code needed to generate, install, and (for an executable |
|
||||||
work) run the object code and to modify the work, including scripts to |
|
||||||
control those activities. However, it does not include the work's |
|
||||||
System Libraries, or general-purpose tools or generally available free |
|
||||||
programs which are used unmodified in performing those activities but |
|
||||||
which are not part of the work. For example, Corresponding Source |
|
||||||
includes interface definition files associated with source files for |
|
||||||
the work, and the source code for shared libraries and dynamically |
|
||||||
linked subprograms that the work is specifically designed to require, |
|
||||||
such as by intimate data communication or control flow between those |
|
||||||
subprograms and other parts of the work. |
|
||||||
|
|
||||||
The Corresponding Source need not include anything that users |
|
||||||
can regenerate automatically from other parts of the Corresponding |
|
||||||
Source. |
|
||||||
|
|
||||||
The Corresponding Source for a work in source code form is that |
|
||||||
same work. |
|
||||||
|
|
||||||
2. Basic Permissions. |
|
||||||
|
|
||||||
All rights granted under this License are granted for the term of |
|
||||||
copyright on the Program, and are irrevocable provided the stated |
|
||||||
conditions are met. This License explicitly affirms your unlimited |
|
||||||
permission to run the unmodified Program. The output from running a |
|
||||||
covered work is covered by this License only if the output, given its |
|
||||||
content, constitutes a covered work. This License acknowledges your |
|
||||||
rights of fair use or other equivalent, as provided by copyright law. |
|
||||||
|
|
||||||
You may make, run and propagate covered works that you do not |
|
||||||
convey, without conditions so long as your license otherwise remains |
|
||||||
in force. You may convey covered works to others for the sole purpose |
|
||||||
of having them make modifications exclusively for you, or provide you |
|
||||||
with facilities for running those works, provided that you comply with |
|
||||||
the terms of this License in conveying all material for which you do |
|
||||||
not control copyright. Those thus making or running the covered works |
|
||||||
for you must do so exclusively on your behalf, under your direction |
|
||||||
and control, on terms that prohibit them from making any copies of |
|
||||||
your copyrighted material outside their relationship with you. |
|
||||||
|
|
||||||
Conveying under any other circumstances is permitted solely under |
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10 |
|
||||||
makes it unnecessary. |
|
||||||
|
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
|
||||||
|
|
||||||
No covered work shall be deemed part of an effective technological |
|
||||||
measure under any applicable law fulfilling obligations under article |
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
|
||||||
similar laws prohibiting or restricting circumvention of such |
|
||||||
measures. |
|
||||||
|
|
||||||
When you convey a covered work, you waive any legal power to forbid |
|
||||||
circumvention of technological measures to the extent such circumvention |
|
||||||
is effected by exercising rights under this License with respect to |
|
||||||
the covered work, and you disclaim any intention to limit operation or |
|
||||||
modification of the work as a means of enforcing, against the work's |
|
||||||
users, your or third parties' legal rights to forbid circumvention of |
|
||||||
technological measures. |
|
||||||
|
|
||||||
4. Conveying Verbatim Copies. |
|
||||||
|
|
||||||
You may convey verbatim copies of the Program's source code as you |
|
||||||
receive it, in any medium, provided that you conspicuously and |
|
||||||
appropriately publish on each copy an appropriate copyright notice; |
|
||||||
keep intact all notices stating that this License and any |
|
||||||
non-permissive terms added in accord with section 7 apply to the code; |
|
||||||
keep intact all notices of the absence of any warranty; and give all |
|
||||||
recipients a copy of this License along with the Program. |
|
||||||
|
|
||||||
You may charge any price or no price for each copy that you convey, |
|
||||||
and you may offer support or warranty protection for a fee. |
|
||||||
|
|
||||||
5. Conveying Modified Source Versions. |
|
||||||
|
|
||||||
You may convey a work based on the Program, or the modifications to |
|
||||||
produce it from the Program, in the form of source code under the |
|
||||||
terms of section 4, provided that you also meet all of these conditions: |
|
||||||
|
|
||||||
a) The work must carry prominent notices stating that you modified |
|
||||||
it, and giving a relevant date. |
|
||||||
|
|
||||||
b) The work must carry prominent notices stating that it is |
|
||||||
released under this License and any conditions added under section |
|
||||||
7. This requirement modifies the requirement in section 4 to |
|
||||||
"keep intact all notices". |
|
||||||
|
|
||||||
c) You must license the entire work, as a whole, under this |
|
||||||
License to anyone who comes into possession of a copy. This |
|
||||||
License will therefore apply, along with any applicable section 7 |
|
||||||
additional terms, to the whole of the work, and all its parts, |
|
||||||
regardless of how they are packaged. This License gives no |
|
||||||
permission to license the work in any other way, but it does not |
|
||||||
invalidate such permission if you have separately received it. |
|
||||||
|
|
||||||
d) If the work has interactive user interfaces, each must display |
|
||||||
Appropriate Legal Notices; however, if the Program has interactive |
|
||||||
interfaces that do not display Appropriate Legal Notices, your |
|
||||||
work need not make them do so. |
|
||||||
|
|
||||||
A compilation of a covered work with other separate and independent |
|
||||||
works, which are not by their nature extensions of the covered work, |
|
||||||
and which are not combined with it such as to form a larger program, |
|
||||||
in or on a volume of a storage or distribution medium, is called an |
|
||||||
"aggregate" if the compilation and its resulting copyright are not |
|
||||||
used to limit the access or legal rights of the compilation's users |
|
||||||
beyond what the individual works permit. Inclusion of a covered work |
|
||||||
in an aggregate does not cause this License to apply to the other |
|
||||||
parts of the aggregate. |
|
||||||
|
|
||||||
6. Conveying Non-Source Forms. |
|
||||||
|
|
||||||
You may convey a covered work in object code form under the terms |
|
||||||
of sections 4 and 5, provided that you also convey the |
|
||||||
machine-readable Corresponding Source under the terms of this License, |
|
||||||
in one of these ways: |
|
||||||
|
|
||||||
a) Convey the object code in, or embodied in, a physical product |
|
||||||
(including a physical distribution medium), accompanied by the |
|
||||||
Corresponding Source fixed on a durable physical medium |
|
||||||
customarily used for software interchange. |
|
||||||
|
|
||||||
b) Convey the object code in, or embodied in, a physical product |
|
||||||
(including a physical distribution medium), accompanied by a |
|
||||||
written offer, valid for at least three years and valid for as |
|
||||||
long as you offer spare parts or customer support for that product |
|
||||||
model, to give anyone who possesses the object code either (1) a |
|
||||||
copy of the Corresponding Source for all the software in the |
|
||||||
product that is covered by this License, on a durable physical |
|
||||||
medium customarily used for software interchange, for a price no |
|
||||||
more than your reasonable cost of physically performing this |
|
||||||
conveying of source, or (2) access to copy the |
|
||||||
Corresponding Source from a network server at no charge. |
|
||||||
|
|
||||||
c) Convey individual copies of the object code with a copy of the |
|
||||||
written offer to provide the Corresponding Source. This |
|
||||||
alternative is allowed only occasionally and noncommercially, and |
|
||||||
only if you received the object code with such an offer, in accord |
|
||||||
with subsection 6b. |
|
||||||
|
|
||||||
d) Convey the object code by offering access from a designated |
|
||||||
place (gratis or for a charge), and offer equivalent access to the |
|
||||||
Corresponding Source in the same way through the same place at no |
|
||||||
further charge. You need not require recipients to copy the |
|
||||||
Corresponding Source along with the object code. If the place to |
|
||||||
copy the object code is a network server, the Corresponding Source |
|
||||||
may be on a different server (operated by you or a third party) |
|
||||||
that supports equivalent copying facilities, provided you maintain |
|
||||||
clear directions next to the object code saying where to find the |
|
||||||
Corresponding Source. Regardless of what server hosts the |
|
||||||
Corresponding Source, you remain obligated to ensure that it is |
|
||||||
available for as long as needed to satisfy these requirements. |
|
||||||
|
|
||||||
e) Convey the object code using peer-to-peer transmission, provided |
|
||||||
you inform other peers where the object code and Corresponding |
|
||||||
Source of the work are being offered to the general public at no |
|
||||||
charge under subsection 6d. |
|
||||||
|
|
||||||
A separable portion of the object code, whose source code is excluded |
|
||||||
from the Corresponding Source as a System Library, need not be |
|
||||||
included in conveying the object code work. |
|
||||||
|
|
||||||
A "User Product" is either (1) a "consumer product", which means any |
|
||||||
tangible personal property which is normally used for personal, family, |
|
||||||
or household purposes, or (2) anything designed or sold for incorporation |
|
||||||
into a dwelling. In determining whether a product is a consumer product, |
|
||||||
doubtful cases shall be resolved in favor of coverage. For a particular |
|
||||||
product received by a particular user, "normally used" refers to a |
|
||||||
typical or common use of that class of product, regardless of the status |
|
||||||
of the particular user or of the way in which the particular user |
|
||||||
actually uses, or expects or is expected to use, the product. A product |
|
||||||
is a consumer product regardless of whether the product has substantial |
|
||||||
commercial, industrial or non-consumer uses, unless such uses represent |
|
||||||
the only significant mode of use of the product. |
|
||||||
|
|
||||||
"Installation Information" for a User Product means any methods, |
|
||||||
procedures, authorization keys, or other information required to install |
|
||||||
and execute modified versions of a covered work in that User Product from |
|
||||||
a modified version of its Corresponding Source. The information must |
|
||||||
suffice to ensure that the continued functioning of the modified object |
|
||||||
code is in no case prevented or interfered with solely because |
|
||||||
modification has been made. |
|
||||||
|
|
||||||
If you convey an object code work under this section in, or with, or |
|
||||||
specifically for use in, a User Product, and the conveying occurs as |
|
||||||
part of a transaction in which the right of possession and use of the |
|
||||||
User Product is transferred to the recipient in perpetuity or for a |
|
||||||
fixed term (regardless of how the transaction is characterized), the |
|
||||||
Corresponding Source conveyed under this section must be accompanied |
|
||||||
by the Installation Information. But this requirement does not apply |
|
||||||
if neither you nor any third party retains the ability to install |
|
||||||
modified object code on the User Product (for example, the work has |
|
||||||
been installed in ROM). |
|
||||||
|
|
||||||
The requirement to provide Installation Information does not include a |
|
||||||
requirement to continue to provide support service, warranty, or updates |
|
||||||
for a work that has been modified or installed by the recipient, or for |
|
||||||
the User Product in which it has been modified or installed. Access to a |
|
||||||
network may be denied when the modification itself materially and |
|
||||||
adversely affects the operation of the network or violates the rules and |
|
||||||
protocols for communication across the network. |
|
||||||
|
|
||||||
Corresponding Source conveyed, and Installation Information provided, |
|
||||||
in accord with this section must be in a format that is publicly |
|
||||||
documented (and with an implementation available to the public in |
|
||||||
source code form), and must require no special password or key for |
|
||||||
unpacking, reading or copying. |
|
||||||
|
|
||||||
7. Additional Terms. |
|
||||||
|
|
||||||
"Additional permissions" are terms that supplement the terms of this |
|
||||||
License by making exceptions from one or more of its conditions. |
|
||||||
Additional permissions that are applicable to the entire Program shall |
|
||||||
be treated as though they were included in this License, to the extent |
|
||||||
that they are valid under applicable law. If additional permissions |
|
||||||
apply only to part of the Program, that part may be used separately |
|
||||||
under those permissions, but the entire Program remains governed by |
|
||||||
this License without regard to the additional permissions. |
|
||||||
|
|
||||||
When you convey a copy of a covered work, you may at your option |
|
||||||
remove any additional permissions from that copy, or from any part of |
|
||||||
it. (Additional permissions may be written to require their own |
|
||||||
removal in certain cases when you modify the work.) You may place |
|
||||||
additional permissions on material, added by you to a covered work, |
|
||||||
for which you have or can give appropriate copyright permission. |
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, for material you |
|
||||||
add to a covered work, you may (if authorized by the copyright holders of |
|
||||||
that material) supplement the terms of this License with terms: |
|
||||||
|
|
||||||
a) Disclaiming warranty or limiting liability differently from the |
|
||||||
terms of sections 15 and 16 of this License; or |
|
||||||
|
|
||||||
b) Requiring preservation of specified reasonable legal notices or |
|
||||||
author attributions in that material or in the Appropriate Legal |
|
||||||
Notices displayed by works containing it; or |
|
||||||
|
|
||||||
c) Prohibiting misrepresentation of the origin of that material, or |
|
||||||
requiring that modified versions of such material be marked in |
|
||||||
reasonable ways as different from the original version; or |
|
||||||
|
|
||||||
d) Limiting the use for publicity purposes of names of licensors or |
|
||||||
authors of the material; or |
|
||||||
|
|
||||||
e) Declining to grant rights under trademark law for use of some |
|
||||||
trade names, trademarks, or service marks; or |
|
||||||
|
|
||||||
f) Requiring indemnification of licensors and authors of that |
|
||||||
material by anyone who conveys the material (or modified versions of |
|
||||||
it) with contractual assumptions of liability to the recipient, for |
|
||||||
any liability that these contractual assumptions directly impose on |
|
||||||
those licensors and authors. |
|
||||||
|
|
||||||
All other non-permissive additional terms are considered "further |
|
||||||
restrictions" within the meaning of section 10. If the Program as you |
|
||||||
received it, or any part of it, contains a notice stating that it is |
|
||||||
governed by this License along with a term that is a further |
|
||||||
restriction, you may remove that term. If a license document contains |
|
||||||
a further restriction but permits relicensing or conveying under this |
|
||||||
License, you may add to a covered work material governed by the terms |
|
||||||
of that license document, provided that the further restriction does |
|
||||||
not survive such relicensing or conveying. |
|
||||||
|
|
||||||
If you add terms to a covered work in accord with this section, you |
|
||||||
must place, in the relevant source files, a statement of the |
|
||||||
additional terms that apply to those files, or a notice indicating |
|
||||||
where to find the applicable terms. |
|
||||||
|
|
||||||
Additional terms, permissive or non-permissive, may be stated in the |
|
||||||
form of a separately written license, or stated as exceptions; |
|
||||||
the above requirements apply either way. |
|
||||||
|
|
||||||
8. Termination. |
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly |
|
||||||
provided under this License. Any attempt otherwise to propagate or |
|
||||||
modify it is void, and will automatically terminate your rights under |
|
||||||
this License (including any patent licenses granted under the third |
|
||||||
paragraph of section 11). |
|
||||||
|
|
||||||
However, if you cease all violation of this License, then your |
|
||||||
license from a particular copyright holder is reinstated (a) |
|
||||||
provisionally, unless and until the copyright holder explicitly and |
|
||||||
finally terminates your license, and (b) permanently, if the copyright |
|
||||||
holder fails to notify you of the violation by some reasonable means |
|
||||||
prior to 60 days after the cessation. |
|
||||||
|
|
||||||
Moreover, your license from a particular copyright holder is |
|
||||||
reinstated permanently if the copyright holder notifies you of the |
|
||||||
violation by some reasonable means, this is the first time you have |
|
||||||
received notice of violation of this License (for any work) from that |
|
||||||
copyright holder, and you cure the violation prior to 30 days after |
|
||||||
your receipt of the notice. |
|
||||||
|
|
||||||
Termination of your rights under this section does not terminate the |
|
||||||
licenses of parties who have received copies or rights from you under |
|
||||||
this License. If your rights have been terminated and not permanently |
|
||||||
reinstated, you do not qualify to receive new licenses for the same |
|
||||||
material under section 10. |
|
||||||
|
|
||||||
9. Acceptance Not Required for Having Copies. |
|
||||||
|
|
||||||
You are not required to accept this License in order to receive or |
|
||||||
run a copy of the Program. Ancillary propagation of a covered work |
|
||||||
occurring solely as a consequence of using peer-to-peer transmission |
|
||||||
to receive a copy likewise does not require acceptance. However, |
|
||||||
nothing other than this License grants you permission to propagate or |
|
||||||
modify any covered work. These actions infringe copyright if you do |
|
||||||
not accept this License. Therefore, by modifying or propagating a |
|
||||||
covered work, you indicate your acceptance of this License to do so. |
|
||||||
|
|
||||||
10. Automatic Licensing of Downstream Recipients. |
|
||||||
|
|
||||||
Each time you convey a covered work, the recipient automatically |
|
||||||
receives a license from the original licensors, to run, modify and |
|
||||||
propagate that work, subject to this License. You are not responsible |
|
||||||
for enforcing compliance by third parties with this License. |
|
||||||
|
|
||||||
An "entity transaction" is a transaction transferring control of an |
|
||||||
organization, or substantially all assets of one, or subdividing an |
|
||||||
organization, or merging organizations. If propagation of a covered |
|
||||||
work results from an entity transaction, each party to that |
|
||||||
transaction who receives a copy of the work also receives whatever |
|
||||||
licenses to the work the party's predecessor in interest had or could |
|
||||||
give under the previous paragraph, plus a right to possession of the |
|
||||||
Corresponding Source of the work from the predecessor in interest, if |
|
||||||
the predecessor has it or can get it with reasonable efforts. |
|
||||||
|
|
||||||
You may not impose any further restrictions on the exercise of the |
|
||||||
rights granted or affirmed under this License. For example, you may |
|
||||||
not impose a license fee, royalty, or other charge for exercise of |
|
||||||
rights granted under this License, and you may not initiate litigation |
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that |
|
||||||
any patent claim is infringed by making, using, selling, offering for |
|
||||||
sale, or importing the Program or any portion of it. |
|
||||||
|
|
||||||
11. Patents. |
|
||||||
|
|
||||||
A "contributor" is a copyright holder who authorizes use under this |
|
||||||
License of the Program or a work on which the Program is based. The |
|
||||||
work thus licensed is called the contributor's "contributor version". |
|
||||||
|
|
||||||
A contributor's "essential patent claims" are all patent claims |
|
||||||
owned or controlled by the contributor, whether already acquired or |
|
||||||
hereafter acquired, that would be infringed by some manner, permitted |
|
||||||
by this License, of making, using, or selling its contributor version, |
|
||||||
but do not include claims that would be infringed only as a |
|
||||||
consequence of further modification of the contributor version. For |
|
||||||
purposes of this definition, "control" includes the right to grant |
|
||||||
patent sublicenses in a manner consistent with the requirements of |
|
||||||
this License. |
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free |
|
||||||
patent license under the contributor's essential patent claims, to |
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and |
|
||||||
propagate the contents of its contributor version. |
|
||||||
|
|
||||||
In the following three paragraphs, a "patent license" is any express |
|
||||||
agreement or commitment, however denominated, not to enforce a patent |
|
||||||
(such as an express permission to practice a patent or covenant not to |
|
||||||
sue for patent infringement). To "grant" such a patent license to a |
|
||||||
party means to make such an agreement or commitment not to enforce a |
|
||||||
patent against the party. |
|
||||||
|
|
||||||
If you convey a covered work, knowingly relying on a patent license, |
|
||||||
and the Corresponding Source of the work is not available for anyone |
|
||||||
to copy, free of charge and under the terms of this License, through a |
|
||||||
publicly available network server or other readily accessible means, |
|
||||||
then you must either (1) cause the Corresponding Source to be so |
|
||||||
available, or (2) arrange to deprive yourself of the benefit of the |
|
||||||
patent license for this particular work, or (3) arrange, in a manner |
|
||||||
consistent with the requirements of this License, to extend the patent |
|
||||||
license to downstream recipients. "Knowingly relying" means you have |
|
||||||
actual knowledge that, but for the patent license, your conveying the |
|
||||||
covered work in a country, or your recipient's use of the covered work |
|
||||||
in a country, would infringe one or more identifiable patents in that |
|
||||||
country that you have reason to believe are valid. |
|
||||||
|
|
||||||
If, pursuant to or in connection with a single transaction or |
|
||||||
arrangement, you convey, or propagate by procuring conveyance of, a |
|
||||||
covered work, and grant a patent license to some of the parties |
|
||||||
receiving the covered work authorizing them to use, propagate, modify |
|
||||||
or convey a specific copy of the covered work, then the patent license |
|
||||||
you grant is automatically extended to all recipients of the covered |
|
||||||
work and works based on it. |
|
||||||
|
|
||||||
A patent license is "discriminatory" if it does not include within |
|
||||||
the scope of its coverage, prohibits the exercise of, or is |
|
||||||
conditioned on the non-exercise of one or more of the rights that are |
|
||||||
specifically granted under this License. You may not convey a covered |
|
||||||
work if you are a party to an arrangement with a third party that is |
|
||||||
in the business of distributing software, under which you make payment |
|
||||||
to the third party based on the extent of your activity of conveying |
|
||||||
the work, and under which the third party grants, to any of the |
|
||||||
parties who would receive the covered work from you, a discriminatory |
|
||||||
patent license (a) in connection with copies of the covered work |
|
||||||
conveyed by you (or copies made from those copies), or (b) primarily |
|
||||||
for and in connection with specific products or compilations that |
|
||||||
contain the covered work, unless you entered into that arrangement, |
|
||||||
or that patent license was granted, prior to 28 March 2007. |
|
||||||
|
|
||||||
Nothing in this License shall be construed as excluding or limiting |
|
||||||
any implied license or other defenses to infringement that may |
|
||||||
otherwise be available to you under applicable patent law. |
|
||||||
|
|
||||||
12. No Surrender of Others' Freedom. |
|
||||||
|
|
||||||
If conditions are imposed on you (whether by court order, agreement or |
|
||||||
otherwise) that contradict the conditions of this License, they do not |
|
||||||
excuse you from the conditions of this License. If you cannot convey a |
|
||||||
covered work so as to satisfy simultaneously your obligations under this |
|
||||||
License and any other pertinent obligations, then as a consequence you may |
|
||||||
not convey it at all. For example, if you agree to terms that obligate you |
|
||||||
to collect a royalty for further conveying from those to whom you convey |
|
||||||
the Program, the only way you could satisfy both those terms and this |
|
||||||
License would be to refrain entirely from conveying the Program. |
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License. |
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have |
|
||||||
permission to link or combine any covered work with a work licensed |
|
||||||
under version 3 of the GNU Affero General Public License into a single |
|
||||||
combined work, and to convey the resulting work. The terms of this |
|
||||||
License will continue to apply to the part which is the covered work, |
|
||||||
but the special requirements of the GNU Affero General Public License, |
|
||||||
section 13, concerning interaction through a network will apply to the |
|
||||||
combination as such. |
|
||||||
|
|
||||||
14. Revised Versions of this License. |
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of |
|
||||||
the GNU General Public License from time to time. Such new versions will |
|
||||||
be similar in spirit to the present version, but may differ in detail to |
|
||||||
address new problems or concerns. |
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the |
|
||||||
Program specifies that a certain numbered version of the GNU General |
|
||||||
Public License "or any later version" applies to it, you have the |
|
||||||
option of following the terms and conditions either of that numbered |
|
||||||
version or of any later version published by the Free Software |
|
||||||
Foundation. If the Program does not specify a version number of the |
|
||||||
GNU General Public License, you may choose any version ever published |
|
||||||
by the Free Software Foundation. |
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future |
|
||||||
versions of the GNU General Public License can be used, that proxy's |
|
||||||
public statement of acceptance of a version permanently authorizes you |
|
||||||
to choose that version for the Program. |
|
||||||
|
|
||||||
Later license versions may give you additional or different |
|
||||||
permissions. However, no additional obligations are imposed on any |
|
||||||
author or copyright holder as a result of your choosing to follow a |
|
||||||
later version. |
|
||||||
|
|
||||||
15. Disclaimer of Warranty. |
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
|
||||||
|
|
||||||
16. Limitation of Liability. |
|
||||||
|
|
||||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
|
||||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
|
||||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
|
||||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
|
||||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
|
||||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
|
||||||
SUCH DAMAGES. |
|
||||||
|
|
||||||
17. Interpretation of Sections 15 and 16. |
|
||||||
|
|
||||||
If the disclaimer of warranty and limitation of liability provided |
|
||||||
above cannot be given local legal effect according to their terms, |
|
||||||
reviewing courts shall apply local law that most closely approximates |
|
||||||
an absolute waiver of all civil liability in connection with the |
|
||||||
Program, unless a warranty or assumption of liability accompanies a |
|
||||||
copy of the Program in return for a fee. |
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS |
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs |
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest |
|
||||||
possible use to the public, the best way to achieve this is to make it |
|
||||||
free software which everyone can redistribute and change under these terms. |
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest |
|
||||||
to attach them to the start of each source file to most effectively |
|
||||||
state the exclusion of warranty; and each file should have at least |
|
||||||
the "copyright" line and a pointer to where the full notice is found. |
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.> |
|
||||||
Copyright (C) <year> <name of author> |
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify |
|
||||||
it under the terms of the GNU General Public License as published by |
|
||||||
the Free Software Foundation, either version 3 of the License, or |
|
||||||
(at your option) any later version. |
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
GNU General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License |
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail. |
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short |
|
||||||
notice like this when it starts in an interactive mode: |
|
||||||
|
|
||||||
<program> Copyright (C) <year> <name of author> |
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
|
||||||
This is free software, and you are welcome to redistribute it |
|
||||||
under certain conditions; type `show c' for details. |
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate |
|
||||||
parts of the General Public License. Of course, your program's commands |
|
||||||
might be different; for a GUI interface, you would use an "about box". |
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school, |
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary. |
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see |
|
||||||
<https://www.gnu.org/licenses/>. |
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program |
|
||||||
into proprietary programs. If your program is a subroutine library, you |
|
||||||
may consider it more useful to permit linking proprietary applications with |
|
||||||
the library. If this is what you want to do, use the GNU Lesser General |
|
||||||
Public License instead of this License. But first, please read |
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>. |
|
||||||
@ -1,700 +0,0 @@ |
|||||||
[简体中文](README-CN.md) | [English](README.md) |
|
||||||
|
|
||||||
<div align="center"> |
|
||||||
|
|
||||||
# TypePHP |
|
||||||
|
|
||||||
**PHP 原生 AOT 编译器** |
|
||||||
|
|
||||||
将 PHP 源码提前(AOT)编译为原生机器码,生成原生可执行文件、PHP 扩展和共享库, |
|
||||||
同时保留你熟悉的 PHP 语法。 |
|
||||||
|
|
||||||
[](https://github.com/swoole/typephp/actions/workflows/linux-x64.yml) |
|
||||||
[](https://github.com/swoole/typephp/actions/workflows/linux-arm64.yml) |
|
||||||
[](https://github.com/swoole/typephp/actions/workflows/macos-arm64.yml) |
|
||||||
[](https://github.com/swoole/typephp/actions/workflows/windows-build.yml) |
|
||||||
[](https://www.php.net/) |
|
||||||
[](LICENSE) |
|
||||||
|
|
||||||
</div> |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## 什么是 TypePHP? |
|
||||||
|
|
||||||
TypePHP 是一个 AOT(Ahead-Of-Time,提前编译)编译器,它把 PHP 源码翻译为 C++, |
|
||||||
再编译为原生机器码。与字节码缓存或虚拟机不同,它不会在运行时解释 opcode, |
|
||||||
而是直接生成在 CPU 上运行的原生二进制。 |
|
||||||
|
|
||||||
它保留熟悉的 PHP 语法,同时引入编译期类型信息,让编译器为性能热点生成快速、 |
|
||||||
静态类型的 C++ 代码。动态 PHP 值、内置函数、反射和对象元数据继续通过 PHPX |
|
||||||
与 Zend runtime 互操作;用户函数编译完成后不再以 Zend opcode 方式执行。 |
|
||||||
|
|
||||||
TypePHP **完全由 PHP 语言编写**,并且**完全自举**:`tpc` 编译器二进制就是 |
|
||||||
用 TypePHP 编译编译器自身的 PHP 源码得到的。整个自举链路是纯 PHP——编译器 |
|
||||||
本身没有任何 C 或 C++ 胶水代码。 |
|
||||||
|
|
||||||
TypePHP 仍在积极开发中。它提供的是边界明确、可测试的 PHP 子集,而不是宣称可以 |
|
||||||
无修改替代所有高度动态的 PHP 程序。在将现有项目迁移到 TypePHP 前,请先阅读 |
|
||||||
[兼容性模型](#兼容性模型)和[不兼容特性清单](docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md)。 |
|
||||||
|
|
||||||
## 工作原理 |
|
||||||
|
|
||||||
```text |
|
||||||
PHP 源码 + .stub.php 声明 + 可选 C/C++ 源码 |
|
||||||
│ |
|
||||||
▼ |
|
||||||
解析、校验并收集全部声明 |
|
||||||
│ |
|
||||||
▼ |
|
||||||
将函数实现和常量表达式降级为 C++17 |
|
||||||
│ |
|
||||||
▼ |
|
||||||
原生编译器 + 可复用对象/PCH 缓存 |
|
||||||
│ |
|
||||||
▼ |
|
||||||
可执行文件 | PHP 扩展 | 共享库 | WASI Component |
|
||||||
``` |
|
||||||
|
|
||||||
prepare 阶段只建立完整符号模型,不分配运行时 Cache ID。常量和声明默认值只保留 |
|
||||||
AST,待全部项目符号就绪后再在 convert 阶段解析。这一两阶段设计保证多文件构建和 |
|
||||||
编译器自举过程具有确定性。 |
|
||||||
|
|
||||||
## 特性 |
|
||||||
|
|
||||||
- **完全自举、纯 PHP 实现** —— TypePHP 编译器完全由 PHP 语言编写,并能自举: |
|
||||||
用 `tpc` 编译编译器自身的源码,即可生成原生二进制。 |
|
||||||
- **真正的 AOT 编译** —— PHP 先降级为 C++17,再编译为原生机器码。无解释器、 |
|
||||||
无 opcode 缓存、无 JIT 预热。 |
|
||||||
- **三种原生构建模式** —— 同一份代码可编译为原生 `bin` 可执行文件、可加载的 |
|
||||||
PHP `ext` 扩展,或可复用的 `lib` 共享库。 |
|
||||||
- **原生类型系统** —— `int`、`float`、`bool` 直接映射为 C++ 标量类型 |
|
||||||
(`int64_t`、`double`、`bool`),数值代码可获得数量级的性能提升。 |
|
||||||
- **高精度数值** —— `bigInt`(GMP)、`decimal`(libmpdec)、`bigFloat`(MPFR), |
|
||||||
提供强类型运算符和方法 API。 |
|
||||||
- **强类型容器** —— `std::array`、`std::vector`、`std::map`、`std::ordered_map`, |
|
||||||
元素类型在编译期确定;最高比 PHP 数组快 **10 倍**,性能与 C++ `std::vector` 相当。 |
|
||||||
- **通用方法(Universal Methods)** —— 在原生类型上直接调用方法 |
|
||||||
(`$s->upper()`、`$arr->contains()`、`$big->mul(2)`);静态类型已知时在编译期 |
|
||||||
直接解析调用。 |
|
||||||
- **混合 C++ / PHP 编程** —— 在性能关键内核中直接调用 C++ 函数(反之亦然)。 |
|
||||||
- **编译期函数与关键词** —— `any()`、`refval()`、`objval()`、`expected()`、 |
|
||||||
`unexpected()`,以及 `toInt()`、`toString()`、`toArray()` 等。 |
|
||||||
- **编译期安全检查** —— `#[Immutable]` 只读契约和 `#[ArrayDef]` 数组结构元数据, |
|
||||||
在编译期检查,零运行时开销。 |
|
||||||
- **编译期代码生成** —— `#[Getter]`、`#[Setter]`、`#[With]`、`#[Constructor]`、 |
|
||||||
`#[Printer]` 和 `#[Arrayable]` 根据属性声明生成类型安全的方法。 |
|
||||||
- **现代 PHP 支持** —— PHP 8.4 property hooks、非对称可见性、PHP 8.5 |
|
||||||
`clone()`-with 以及 `(void)` 丢弃表达式。 |
|
||||||
- **跨平台与 WASM** —— 面向 x64 和 ARM64 的 Linux、Windows、macOS 目标, |
|
||||||
以及 WASI 0.2 和浏览器(Jco)输出。 |
|
||||||
- **Python 桥接** —— 为 Python 模块生成 IDE helper,并将 Python 脚本转换为 TypePHP。 |
|
||||||
|
|
||||||
## 为什么选择 TypePHP? |
|
||||||
|
|
||||||
| | TypePHP AOT | 字节码缓存(OPcache) | JIT(PHP 8+) | |
|
||||||
|---|---|---|---| |
|
||||||
| 编译目标 | 原生机器码 | 字节码 | 机器码(trace) | |
|
||||||
| 启动 / 预热 | 无(已编译完成) | 每进程预热 | JIT 预热 | |
|
||||||
| 类型驱动优化 | 编译期、全程序 | 无 | 有限,基于 trace | |
|
||||||
| 生成原生可执行文件 | 支持 | 不支持 | 不支持 | |
|
||||||
| 源码保护 | 编译为机器码 | 字节码(可还原) | 字节码(可还原) | |
|
||||||
| 性能确定性 | 是 | 否 | 否 | |
|
||||||
|
|
||||||
**相较原生 PHP 的优势:** |
|
||||||
|
|
||||||
- **接近原生的性能。** 数值密集和容器密集的热点路径会编译为与 C++ 程序相同的机器码。 |
|
||||||
见下方[基准测试](#基准测试)。 |
|
||||||
- **源码保护。** 源码被编译掉——交付物是原生二进制,而不是可读的 PHP 文件。 |
|
||||||
- **原生进程入口。** 二进制模式直接启动原生可执行文件,不需要 PHP CLI 或独立的 |
|
||||||
解释器进程。可执行文件仍会嵌入或链接 PHPX、`libphp` 及项目配置的原生库,部署包 |
|
||||||
中必须提供这些运行时依赖。 |
|
||||||
- **渐进式类型,真正带来收益。** 只在性能关键处添加 `use native_types`、`std::` |
|
||||||
容器和类型声明,其余保持普通 PHP。 |
|
||||||
- **Zend 生态互通。** 扩展模式以标准 PHP 扩展形式加载,项目可以调用受支持的 |
|
||||||
内置函数,并显式声明依赖的其他 Zend 扩展。 |
|
||||||
|
|
||||||
## 前置要求 |
|
||||||
|
|
||||||
- **PHP 8.4 – 8.5** CLI、开发头文件及 `php-config` |
|
||||||
- 在类 Unix 系统构建二进制/共享库时,需要与 PHP 匹配的 **embed 库** |
|
||||||
(`libphp.so` 或 `libphp.dylib`) |
|
||||||
- **GCC 9+**(或 Clang),支持 **C++17** |
|
||||||
- **CMake 3.24+** |
|
||||||
- **Composer 2** |
|
||||||
- 高精度数学库:**GMP**、**MPFR**(libmpdec 已随 PHPX 内置) |
|
||||||
|
|
||||||
```shell |
|
||||||
# Ubuntu/Debian |
|
||||||
sudo apt install build-essential cmake pkg-config libgmp-dev libmpfr-dev |
|
||||||
|
|
||||||
# RHEL/CentOS/Fedora |
|
||||||
sudo dnf install gcc gcc-c++ cmake pkgconf-pkg-config gmp-devel mpfr-devel |
|
||||||
|
|
||||||
# Arch Linux |
|
||||||
sudo pacman -S base-devel cmake pkgconf gmp mpfr |
|
||||||
``` |
|
||||||
|
|
||||||
> GMP 用于 `bigInt`,MPFR 用于 `bigFloat`。`decimal` 底层是 libmpdec, |
|
||||||
> 已随 PHPX 内置,无需单独安装。 |
|
||||||
|
|
||||||
Linux x64 是主要开发及全量测试 CI 平台。编译器也提供 Windows、macOS、ARM64 和 |
|
||||||
WASI 后端;具体主机能否构建某个目标,仍取决于 PHP embed、工具链和第三方库是否 |
|
||||||
可用。 |
|
||||||
|
|
||||||
原生 Release Assets 默认使用 PHP 8.5 ZTS 的最新版本构建,提供 Linux x64、Linux |
|
||||||
ARM64、macOS ARM64 和 Windows x64 四个平台包;不提供原生 NTS 或 32 位 x86 包。 |
|
||||||
Linux 与 macOS 包包含编译器和 production Composer 依赖,Windows 包则包含完整且 |
|
||||||
匹配的 PHP/PHPX 运行时与 SDK。 |
|
||||||
|
|
||||||
## 安装 |
|
||||||
|
|
||||||
### 通过 Composer |
|
||||||
|
|
||||||
```bash |
|
||||||
composer require --dev swoole/typephp |
|
||||||
``` |
|
||||||
|
|
||||||
然后编译你的项目: |
|
||||||
|
|
||||||
```bash |
|
||||||
vendor/bin/tpc.php project.yml |
|
||||||
``` |
|
||||||
|
|
||||||
在 TypePHP 源码仓库中开发时,改用本地入口: |
|
||||||
|
|
||||||
```bash |
|
||||||
bin/tpc.php project.yml |
|
||||||
``` |
|
||||||
|
|
||||||
### 从源码安装 |
|
||||||
|
|
||||||
```bash |
|
||||||
git clone https://github.com/swoole/typephp.git |
|
||||||
cd typephp |
|
||||||
composer install |
|
||||||
php bin/tpc.php --help |
|
||||||
``` |
|
||||||
|
|
||||||
可以使用 `PHPX_HOME` 指向独立的 PHPX 源码或安装目录。`PHP_HOME` 可以指向 PHP |
|
||||||
embed 安装前缀;在类 Unix 系统中,该目录应包含 `bin/php-config`、PHP 头文件和 |
|
||||||
`lib/libphp.so`。 |
|
||||||
|
|
||||||
### 构建 `libphp.so` |
|
||||||
|
|
||||||
二进制和共享库构建需要 PHP 的 `embed` SAPI。如果 Linux 上缺少 `libphp.so`, |
|
||||||
`tpc.php` 可以交互式下载 PHP 源码并自动构建。PHP 扩展构建从宿主 SAPI 解析 Zend |
|
||||||
符号,不能再加载第二份 `libphp`。详见[自动构建 libphp.so](docs/zh-cn/LIBPHP_INSTALLER.md)。 |
|
||||||
|
|
||||||
## 快速开始 |
|
||||||
|
|
||||||
创建 `hello.php`: |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
echo "Hello World!\n"; |
|
||||||
var_dump(PHP_VERSION); |
|
||||||
var_dump(php_uname()); |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
编译并运行: |
|
||||||
|
|
||||||
```bash |
|
||||||
bin/tpc.php hello.php |
|
||||||
./hello |
|
||||||
``` |
|
||||||
|
|
||||||
输出示例(具体 PHP 版本和平台字符串取决于实际链接的运行时): |
|
||||||
|
|
||||||
``` |
|
||||||
Hello World! |
|
||||||
string(5) "8.x.x" |
|
||||||
string(16) "Linux ..." |
|
||||||
``` |
|
||||||
|
|
||||||
> 二进制模式需要全局 `main()` 函数。它可以声明为无参数,或 |
|
||||||
> `main(int $argc, array $argv)` 以接收命令行参数,且必须返回 `void`。全局作用域 |
|
||||||
> 不允许可执行语句;可执行代码必须位于函数或方法内。 |
|
||||||
|
|
||||||
## 编译模式 |
|
||||||
|
|
||||||
TypePHP 支持三种构建模式,通过 `-m` / `--mode` 选择: |
|
||||||
|
|
||||||
| 模式 | 参数 | 输出 | 需要 `main()` | 典型用途 | |
|
||||||
|---|---|---|---|---| |
|
||||||
| 二进制 | `-m bin`(默认) | 可执行文件 | 是 | CLI 工具、常驻服务、独立应用 | |
|
||||||
| 扩展 | `-m ext` | PHP `.so` / `.dll` | 否 | 将编译后的函数和类加载到 PHP SAPI | |
|
||||||
| 库 | `-m lib` | 共享库及自动生成的 `.stub.php` | 否 | 在其他项目中复用编译后的 TypePHP API | |
|
||||||
|
|
||||||
```bash |
|
||||||
# 二进制(默认) |
|
||||||
bin/tpc.php app.php -o myapp |
|
||||||
|
|
||||||
# PHP 扩展 |
|
||||||
bin/tpc.php extension/ -m ext -o my_extension |
|
||||||
|
|
||||||
# 共享库,同时生成 mylib.stub.php |
|
||||||
bin/tpc.php lib/ -m lib -o mylib |
|
||||||
``` |
|
||||||
|
|
||||||
详见[编译模式](docs/zh-cn/COMPILATION_MODES.md)。 |
|
||||||
|
|
||||||
## 项目配置 |
|
||||||
|
|
||||||
多文件项目建议使用 `project.yml` 固化可复用的构建配置: |
|
||||||
|
|
||||||
```yaml |
|
||||||
name: myapp |
|
||||||
mode: bin |
|
||||||
php-version: "8.5" |
|
||||||
optimize: 2 |
|
||||||
job: 8 |
|
||||||
build-dir: build |
|
||||||
cxx-std: c++17 |
|
||||||
|
|
||||||
sources: |
|
||||||
- src |
|
||||||
- cpp-src |
|
||||||
- path: src/php85 |
|
||||||
if: PHP_VERSION_ID >= 80500 |
|
||||||
- path: src/windows |
|
||||||
if: PHP_OS_FAMILY == "Windows" |
|
||||||
|
|
||||||
ignore: |
|
||||||
- src/experimental |
|
||||||
|
|
||||||
include-paths: |
|
||||||
- native/include |
|
||||||
defines: |
|
||||||
- FEATURE_FAST_PATH=1 |
|
||||||
link-paths: |
|
||||||
- native/lib |
|
||||||
link-libs: |
|
||||||
- curl |
|
||||||
|
|
||||||
# Zend 扩展依赖,不是原生链接库。 |
|
||||||
# `extension-dependencies` 是等价长名称,两者不能同时使用。 |
|
||||||
ext-deps: |
|
||||||
- pdo_mysql |
|
||||||
- curl |
|
||||||
``` |
|
||||||
|
|
||||||
路径以 YAML 文件所在目录为基准。source 可以是文件或目录;条件 source 支持 |
|
||||||
`PHP_VERSION`、`PHP_VERSION_ID` 和 `PHP_OS_FAMILY`。命令行参数优先于 YAML |
|
||||||
中的同名配置。原生链接依赖应写入 `link-libs`;`ext-deps` 会生成 |
|
||||||
`ZEND_MOD_REQUIRED`,缺少所需 PHP 扩展时由 Zend 拒绝加载模块。 |
|
||||||
|
|
||||||
构建目录保存生成的 C++、依赖对象和预编译头缓存。复用同一个构建目录可以显著加快 |
|
||||||
增量构建;仅在确实需要重编 PHPX 公共对象时使用 `--force`。 |
|
||||||
|
|
||||||
全部项目配置项及命令行优先级详见[编译器命令行](docs/zh-cn/COMPILER_CLI.md)。 |
|
||||||
|
|
||||||
## 兼容性模型 |
|
||||||
|
|
||||||
TypePHP 会在适合 AOT 编译的范围内保持 PHP 语法和运行行为,同时有一些明确限制: |
|
||||||
|
|
||||||
- 全局作用域只允许声明,可执行语句必须位于函数或方法内; |
|
||||||
- 二进制模式对 `main()` 使用严格签名; |
|
||||||
- `use native_types` 会让标量声明使用固定原生存储,之后不能改为不兼容类型; |
|
||||||
- 静态可确定的调用和属性会直接编译,受支持的动态操作则通过 PHPX/Zend runtime |
|
||||||
fallback 执行; |
|
||||||
- `.stub.php` 用于声明 C++ 或外部库 API,函数体必须为空,stub 文件禁止声明 |
|
||||||
`#[Native]` 类; |
|
||||||
- 部分高度动态的引用、声明、闭包和反射模式仍明确不支持。 |
|
||||||
|
|
||||||
兼容性边界属于公共契约,同时有正向和负向测试保护。请以 |
|
||||||
[不兼容 PHP 特性清单](docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md)为当前准确列表,不要把 |
|
||||||
README 未提及的行为默认理解为已支持。 |
|
||||||
|
|
||||||
## 编译期 Attribute 与代码生成 |
|
||||||
|
|
||||||
TypePHP 在 class lowering 阶段消费内置的代码生成 Attribute。生成的方法保留属性 |
|
||||||
声明的类型,并与显式声明的方法一样参与名称冲突、继承关系和 final 方法检查。 |
|
||||||
|
|
||||||
| Attribute | 目标 | 生成的 API | |
|
||||||
|---|---|---| |
|
||||||
| `#[Getter]` | 实例属性,包括构造器提升属性 | `public function getName(): T` | |
|
||||||
| `#[Setter]` | 可变实例属性,包括构造器提升属性 | `public function setName(T $name): void` | |
|
||||||
| `#[With]` | 可变实例属性,包括构造器提升属性 | `public function withName(T $name): static`;克隆对象、修改副本并返回副本 | |
|
||||||
| `#[Constructor]` | 普通实例属性声明 | 将属性加入自动生成的 public `__construct()` | |
|
||||||
| `#[Printer]` | 具名类 | `public function __toString(): string` | |
|
||||||
| `#[Arrayable]` | 具名类 | `public function toArray(): array` | |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
|
|
||||||
#[Printer(fields: ['id', 'name'])] |
|
||||||
#[Arrayable(fields: ['id', 'name'])] |
|
||||||
final class User |
|
||||||
{ |
|
||||||
#[Constructor, Getter, With] |
|
||||||
public int $id; |
|
||||||
|
|
||||||
#[Constructor, Getter, Setter] |
|
||||||
public string $name = 'guest'; |
|
||||||
} |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
$user = new User(7); |
|
||||||
$user->setName('Alice'); |
|
||||||
|
|
||||||
$copy = $user->withId(8); |
|
||||||
echo $user->getId(); // 7 |
|
||||||
echo $copy->getId(); // 8 |
|
||||||
echo $user; // User(id=7, name=Alice) |
|
||||||
echo $user->toArray()['name']; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
未指定 `fields` 时,`#[Printer]` 和 `#[Arrayable]` 使用当前类自身的 public 实例 |
|
||||||
属性。位置参数写法 `#[Arrayable(['id'])]` 等价于 |
|
||||||
`#[Arrayable(fields: ['id'])]`。 |
|
||||||
|
|
||||||
`#[Getter]`、`#[Setter]` 和 `#[With]` 不能用于 static 属性或带 property hook 的 |
|
||||||
属性;`#[Setter]` 和 `#[With]` 还会拒绝 readonly 属性。类中已经显式声明 |
|
||||||
`__construct()` 时不能使用 `#[Constructor]`,必填的构造属性必须位于带默认值的 |
|
||||||
属性之前。生成的方法名若与已有方法冲突,或覆盖继承而来的 final 方法,编译期会 |
|
||||||
直接报错。 |
|
||||||
|
|
||||||
## 使用示例 |
|
||||||
|
|
||||||
### 1. 原生类型 —— 编译期数值加速 |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
use native_types; |
|
||||||
|
|
||||||
function fib(int $n): int |
|
||||||
{ |
|
||||||
if ($n == 1 || $n == 2) { |
|
||||||
return 1; |
|
||||||
} |
|
||||||
return fib($n - 1) + fib($n - 2); |
|
||||||
} |
|
||||||
|
|
||||||
function main(int $argc, array $argv): void |
|
||||||
{ |
|
||||||
$n = (int)$argv[1]; |
|
||||||
$begin = microtime(true); |
|
||||||
echo fib($n) . "\n"; |
|
||||||
echo "Time: " . (microtime(true) - $begin) . "\n"; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
```bash |
|
||||||
bin/tpc.php fib.php -O3 -o fib |
|
||||||
./fib 30 |
|
||||||
``` |
|
||||||
|
|
||||||
使用 `use native_types` 后,`int` 变量变为 C++ `int64_t`,算术运算直接编译为 |
|
||||||
CPU 指令,而不是 ZendVM 调用。 |
|
||||||
|
|
||||||
### 2. 高精度数值 |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
declare(strict_types=1); |
|
||||||
use native_types; |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
// 54 位整数 —— 自动识别并存储为 bigInt |
|
||||||
$a = std::bigInt("123456789012345678901234567890123456789012345678901234"); |
|
||||||
$b = std::bigInt("987654321098765432109876543210987654321098765432109876"); |
|
||||||
|
|
||||||
echo $a->add($b)->toString() . "\n"; // 精确计算,不会溢出 |
|
||||||
|
|
||||||
// 精确的十进制运算 —— 无二进制浮点误差 |
|
||||||
$c = std::decimal("0.1")->add(std::decimal("0.2")); |
|
||||||
echo $c->toString() . "\n"; // "0.3" |
|
||||||
|
|
||||||
// 256 位浮点数 |
|
||||||
$pi = std::bigFloat("3.14159265358979323846264338327950288419716939937510"); |
|
||||||
echo $pi->mul(2)->toString() . "\n"; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
详见[高精度类型](docs/zh-cn/HIGH_PRECISION_TYPES.md)和[原生类型](docs/zh-cn/NATIVE_TYPES.md)。 |
|
||||||
|
|
||||||
### 3. 强类型容器 |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
use native_types; |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
$vector = std::vector(Type::Int); |
|
||||||
|
|
||||||
$vector[] = 1; |
|
||||||
$vector[] = 2; |
|
||||||
$vector[] = 3; |
|
||||||
|
|
||||||
$sum = 0; |
|
||||||
foreach ($vector as $value) { |
|
||||||
$sum += $value; |
|
||||||
} |
|
||||||
|
|
||||||
echo $sum . "\n"; // 6 |
|
||||||
echo $vector[1] . "\n"; // 2 |
|
||||||
|
|
||||||
// 固定 key/value 类型的映射 |
|
||||||
$map = std::ordered_map(Type::String, Type::Int); |
|
||||||
$map["a"] = 1; |
|
||||||
$map["b"] = 2; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
详见 [Std 容器](docs/zh-cn/STD_CONTAINERS.md)。 |
|
||||||
|
|
||||||
### 4. 通用方法 |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
$s = "hello world"; |
|
||||||
echo $s->length() . "\n"; // strlen() |
|
||||||
echo $s->upper() . "\n"; // strtoupper() |
|
||||||
echo $s->substr(0, 5) . "\n"; // substr() |
|
||||||
|
|
||||||
$arr = [1, 3, 5, 7, 9]; |
|
||||||
echo $arr->count() . "\n"; // count() |
|
||||||
var_dump($arr->contains(3)); // in_array() |
|
||||||
|
|
||||||
$big = std::bigInt("12345678901234567890"); |
|
||||||
echo $big->mul(2)->toString() . "\n"; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
原生类型上的方法调用在编译期被解析为直接的 C/C++ 函数调用——没有虚表查找、 |
|
||||||
没有反射、没有运行时派发。详见[通用方法](docs/zh-cn/UNIVERSAL_METHODS.md)。 |
|
||||||
|
|
||||||
### 5. 混合 C++ / PHP |
|
||||||
|
|
||||||
用 C++ 编写性能关键内核,并在 PHP 中调用: |
|
||||||
|
|
||||||
```cpp |
|
||||||
// math.cpp |
|
||||||
#include <phpx.h> |
|
||||||
|
|
||||||
using namespace php; |
|
||||||
|
|
||||||
Int php_fast_sum(Int a, Int b) { |
|
||||||
return a + b; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
// math.stub.php —— 声明 C++ 函数签名 |
|
||||||
function fast_sum(int $a, int $b): int {} |
|
||||||
``` |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
echo fast_sum(3, 4) . "\n"; // 7 |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
需要将 `math.cpp`、`math.stub.php` 和调用它的 PHP 源码加入同一个项目配置。 |
|
||||||
C++ 符号的 `php_` 前缀属于 TypePHP callable ABI;stub 函数只提供类型元数据, |
|
||||||
不能包含实际实现。 |
|
||||||
|
|
||||||
详见[混合 C++/PHP](docs/zh-cn/MIXED_CPP_PHP.md)。 |
|
||||||
|
|
||||||
## 基准测试 |
|
||||||
|
|
||||||
### PHP 语言基准(来自 php-src) |
|
||||||
|
|
||||||
TypePHP 使用 `-O3` 运行 PHP 源码树自带的官方 `bench.php` 与 |
|
||||||
`micro_bench.php` 语言性能测试: |
|
||||||
|
|
||||||
| 基准 | 解释执行 PHP | TypePHP AOT(`-O3`) | 加速比 | |
|
||||||
|---|---|---|---| |
|
||||||
| `bench.php`(总计) | 5.034 秒 | **0.603 秒** | 约 8× | |
|
||||||
| `micro_bench.php`(总计) | 13.045 秒 | **2.021 秒** | 约 6.5× | |
|
||||||
|
|
||||||
两项基准覆盖 PHP 语言核心性能——函数调用、对象属性访问、数组/哈希访问、 |
|
||||||
字符串处理、控制流等。测试代码见 [`benchmark/bench.php`](benchmark/bench.php) 和 |
|
||||||
[`benchmark/micro_bench.php`](benchmark/micro_bench.php)。其他专项性能回归测试 |
|
||||||
统一放置在 [`benchmark/`](benchmark/) 目录中。 |
|
||||||
|
|
||||||
这些数字是项目测量快照,不是性能保证。PHP 版本、编译器、CPU、优化参数和已启用 |
|
||||||
扩展都会影响结果;在用于部署决策前,应在同一机器上使用相同 workload 自行对比。 |
|
||||||
|
|
||||||
### std::array 对比 PHP 数组 |
|
||||||
|
|
||||||
一个 10000×100000 的元素累加循环,对比 PHP 数组、TypePHP `std::array` |
|
||||||
与原生 C++: |
|
||||||
|
|
||||||
| 实现 | 耗时 | |
|
||||||
|---|---| |
|
||||||
| PHP 数组(JIT) | 67.6 秒 | |
|
||||||
| `std::array`(TypePHP AOT) | **6.4 秒** | |
|
||||||
| C++ `std::vector` | 6.2 秒 | |
|
||||||
|
|
||||||
在该 workload 中,`std::array` 比 PHP 数组快约 **10 倍**,并接近手写 C++ 结果。 |
|
||||||
完整基准测试见 [Std 容器](docs/zh-cn/STD_CONTAINERS.md)。 |
|
||||||
|
|
||||||
## 命令行 |
|
||||||
|
|
||||||
```bash |
|
||||||
bin/tpc.php <file|dir|project.yml> [options] [-- program-args...] |
|
||||||
``` |
|
||||||
|
|
||||||
常用示例: |
|
||||||
|
|
||||||
```bash |
|
||||||
# 编译单个文件 |
|
||||||
bin/tpc.php app.php |
|
||||||
|
|
||||||
# 优化并运行,`--` 后的参数传给生成的程序 |
|
||||||
bin/tpc.php app.php -O3 -r -- --flag value |
|
||||||
|
|
||||||
# 编译 project.yml 定义的项目 |
|
||||||
bin/tpc.php project.yml -O2 -j 8 |
|
||||||
|
|
||||||
# 生成 PHP 扩展 |
|
||||||
bin/tpc.php extension/ -m ext -o my_extension |
|
||||||
|
|
||||||
# 只生成 C++(跳过编译与链接) |
|
||||||
bin/tpc.php app.php --dry --build-dir /tmp/typephp-build |
|
||||||
|
|
||||||
# 编译为 WASI 0.2 |
|
||||||
bin/tpc.php --wasm app.php |
|
||||||
|
|
||||||
# 编译为浏览器目标(需要 jco) |
|
||||||
bin/tpc.php --wasm=browser app.php |
|
||||||
``` |
|
||||||
|
|
||||||
主要选项: |
|
||||||
|
|
||||||
| 选项 | 说明 | |
|
||||||
|---|---| |
|
||||||
| `-O <0-3>` | 优化级别(默认 `0`) | |
|
||||||
| `-d`, `--debug` | 调试构建,带符号和源码跟踪 | |
|
||||||
| `-o`, `--output <file>` | 输出文件名 | |
|
||||||
| `-m`, `--mode <bin\|lib\|ext>` | 构建模式(默认 `bin`) | |
|
||||||
| `-r`, `--run` | 构建成功后运行 | |
|
||||||
| `-j`, `--job <num>` | 并行编译任务数(默认 `4`) | |
|
||||||
| `-f`, `--force` | 不使用缓存,重新编译可复用 PHPX 对象 | |
|
||||||
| `--build-dir <dir>` | 生成 C++ 与中间产物的目录 | |
|
||||||
| `--dry` | 只生成 C++,跳过编译与链接 | |
|
||||||
| `--php-version <8.4\|8.5>` | 接受的 PHP 语法版本 | |
|
||||||
| `--cxx-std <ver>` | C++ 标准(如 `c++17`、`c++20`) | |
|
||||||
| `--march <arch>` | 目标指令集(如 `native`) | |
|
||||||
| `--target-platform <triple>` | 交叉编译目标 triple | |
|
||||||
| `--lto` | 启用链接时优化 | |
|
||||||
| `--sanitize <type>` | 启用 sanitizer(如 `address`) | |
|
||||||
| `--profile` | 启用 Linux gperftools 性能分析 | |
|
||||||
| `--format` | 使用 clang-format 格式化生成的 C++ | |
|
||||||
| `--no-literal-strings` | 禁用字面量字符串表优化 | |
|
||||||
| `--no-progress`, `--no-color` | 适合 CI 的输出控制 | |
|
||||||
| `-I`, `-D`, `-L`, `-l` | 可重复指定的原生 include、define、库路径和链接库参数 | |
|
||||||
|
|
||||||
运行 `bin/tpc.php --help` 查看权威的最新参数列表。详见 |
|
||||||
[编译器命令行](docs/zh-cn/COMPILER_CLI.md),包括 Bash 补全: |
|
||||||
|
|
||||||
```bash |
|
||||||
source <(./tpc --generate-completion=bash) |
|
||||||
``` |
|
||||||
|
|
||||||
## 常见问题 |
|
||||||
|
|
||||||
- **缺少 `libphp.so` / `libphp.dylib`:** 安装或编译与当前 PHP 匹配的 embed SAPI,设置 |
|
||||||
`PHP_HOME`,或使用 `bin/tpc.php` 在 Linux 上提供的交互式安装流程。 |
|
||||||
- **找不到 PHPX:** 将 `PHPX_HOME` 指向包含 `include/` 和 |
|
||||||
`lib/libphpx.so`(或对应平台文件)的 PHPX 安装目录,并在编译项目前先构建 PHPX。 |
|
||||||
- **启动崩溃或出现 ABI 错误:** PHP 头文件、`php-config`、`libphp` 和扩展 ABI |
|
||||||
必须使用一致的 PHP 版本及 ZTS/NTS 模式,不能混用不同 PHP 构建产生的产物。 |
|
||||||
- **增量构建异常缓慢:** 固定使用同一个 `--build-dir`,以复用对象和 PCH 缓存。 |
|
||||||
当外层测试工具已经并行运行多个测试时,不要再设置过大的 `tpc -j`,避免并发数 |
|
||||||
相乘后造成 CPU 和内存争用。 |
|
||||||
- **使用 `bin/tpc.php` 可以编译,但自举 `tpc` 失败:** 必须用自举编译器复现。 |
|
||||||
自举执行可能暴露 PHP-hosted 编译器不会经过的动态调用或 ABI 路径。 |
|
||||||
|
|
||||||
## Python 桥接 |
|
||||||
|
|
||||||
TypePHP 内置一个 Python 工具子模块,复用 `tpc` 入口: |
|
||||||
|
|
||||||
```shell |
|
||||||
# 为 Python 模块生成 IDE helper |
|
||||||
./tpc --gen-python-helper math |
|
||||||
./tpc --gen-python-helper numpy --output-dir .ide-helper |
|
||||||
|
|
||||||
# 将 Python 脚本转换为 TypePHP |
|
||||||
./tpc --convert-python-to-php script.py > script.php |
|
||||||
``` |
|
||||||
|
|
||||||
详见 [Python 工具子模块](docs/zh-cn/python/tools.md)。 |
|
||||||
|
|
||||||
## 开发与测试 |
|
||||||
|
|
||||||
安装开发依赖并运行编译器单元测试: |
|
||||||
|
|
||||||
```bash |
|
||||||
composer install |
|
||||||
PHPX_HOME=/path/to/phpx vendor/bin/phpunit |
|
||||||
``` |
|
||||||
|
|
||||||
PHPT 是端到端测试。必须先构建自举编译器,并显式传给测试工具;将 Zend PHP |
|
||||||
可执行文件作为 `--compiler` 并不能验证实际交付的编译器: |
|
||||||
|
|
||||||
```bash |
|
||||||
PHPX_HOME=/path/to/phpx php bin/tpc.php project.yml --job 2 --no-progress |
|
||||||
php run-tests.php -q -j8 --compiler ./tpc tests/compiler |
|
||||||
``` |
|
||||||
|
|
||||||
静态分析与从测试源码生成的覆盖矩阵是两项独立检查: |
|
||||||
|
|
||||||
```bash |
|
||||||
composer analyse |
|
||||||
php bin/analyze-test-coverage.php |
|
||||||
php bin/analyze-test-coverage.php \ |
|
||||||
--format=markdown --output=build/test-coverage.md --strict |
|
||||||
``` |
|
||||||
|
|
||||||
覆盖工具分别报告 PHP 版本 × 特性 × 正向编译 × 运行语义 × 负向诊断,并列出实际 |
|
||||||
出现的 php-parser AST 节点。它不会给出分母不明确的单一百分比。详见 |
|
||||||
[测试覆盖分析工具](docs/zh-cn/TEST_COVERAGE_ANALYZER.md)。 |
|
||||||
|
|
||||||
GitHub Actions 会在 PHP 8.4 和 8.5 上分别运行 PHPUnit 与自举 PHPT。修改编译器 |
|
||||||
内部规则或代码生成时应增加聚焦的 PHPUnit;运行输出或诊断可观察时还应增加 PHPT。 |
|
||||||
|
|
||||||
## 文档 |
|
||||||
|
|
||||||
- [快速入门](docs/zh-cn/QUICKSTART.md) —— 最小编译流程 |
|
||||||
- [编译模式](docs/zh-cn/COMPILATION_MODES.md) —— `bin`、`ext`、`lib` |
|
||||||
- [编译器命令行](docs/zh-cn/COMPILER_CLI.md) —— CLI 参数与项目配置 |
|
||||||
- [不兼容 PHP 特性清单](docs/zh-cn/INCOMPATIBLE_PHP_FEATURES.md) —— 当前限制 |
|
||||||
- [原生类型](docs/zh-cn/NATIVE_TYPES.md) —— 原生标量类型 |
|
||||||
- [高精度类型](docs/zh-cn/HIGH_PRECISION_TYPES.md) —— BigInt / Decimal / BigFloat |
|
||||||
- [Std 容器](docs/zh-cn/STD_CONTAINERS.md) —— 强类型容器 |
|
||||||
- [通用方法](docs/zh-cn/UNIVERSAL_METHODS.md) —— 编译期方法解析 |
|
||||||
- [编译期函数](docs/zh-cn/COMPILE_TIME_FUNCTIONS.md) —— `any()`、`refval()`、`objval()` 等 |
|
||||||
- [混合 C++/PHP](docs/zh-cn/MIXED_CPP_PHP.md) —— C++/PHP 互操作 |
|
||||||
- [`#[Immutable]`](docs/zh-cn/IMMUTABLE.md) —— 编译期只读契约 |
|
||||||
- [`#[ArrayDef]`](docs/zh-cn/ARRAY_DEF.md) —— 强类型数组属性契约 |
|
||||||
- [Property hooks](docs/zh-cn/PROPERTY_HOOKS.md) —— PHP 8.4 hook 降级和运行时元数据 |
|
||||||
- [对象存储模型](docs/zh-cn/OBJECT_STORAGE_AND_PASSING_MODELS.md) —— Zend object、Box 与 Native class 边界 |
|
||||||
- [Generator](docs/zh-cn/YIELD_GENERATOR.md) —— 生成器降级与生命周期 |
|
||||||
- [测试覆盖分析工具](docs/zh-cn/TEST_COVERAGE_ANALYZER.md) —— AST 与特性证据矩阵 |
|
||||||
- [WASI 构建](docs/zh-cn/WASI_BUILD.md) —— WASI 目标 |
|
||||||
|
|
||||||
## 授权协议 |
|
||||||
|
|
||||||
TypePHP 采用 [GNU General Public License v3.0](LICENSE) 授权。 |
|
||||||
|
|
||||||
## 社区 |
|
||||||
|
|
||||||
- 代码仓库:<https://github.com/swoole/typephp> |
|
||||||
- 版权所有 © 2026 上海识沃网络科技有限公司(Swoole) |
|
||||||
@ -1,751 +1,84 @@ |
|||||||
[English](README.md) | [简体中文](README-CN.md) |
# 依赖 |
||||||
|
- 编译器需要 PHP 8.4 以上版本;生成的扩展仍可面向 PHP 8.2~8.5 |
||||||
|
- 需要 GCC-9 以上版本,支持 C++17 标准 |
||||||
|
- 需要 CMake-3.24 以上版本 |
||||||
|
- 需要高精度数学库:`GMP`、`MPFR`、`libmpdec` |
||||||
|
|
||||||
<div align="center"> |
# Composer 安装 |
||||||
|
|
||||||
# TypePHP |
在项目中安装 TypePHP: |
||||||
|
|
||||||
**A native AOT compiler for PHP** |
|
||||||
|
|
||||||
Compile PHP source code into native machine code ahead of time — producing |
|
||||||
native executables, PHP extensions, and shared libraries — while keeping |
|
||||||
the PHP syntax you already know. |
|
||||||
|
|
||||||
[](https://github.com/swoole/typephp/actions/workflows/linux-x64.yml) |
|
||||||
[](https://github.com/swoole/typephp/actions/workflows/linux-arm64.yml) |
|
||||||
[](https://github.com/swoole/typephp/actions/workflows/macos-arm64.yml) |
|
||||||
[](https://github.com/swoole/typephp/actions/workflows/windows-build.yml) |
|
||||||
[](https://www.php.net/) |
|
||||||
[](LICENSE) |
|
||||||
|
|
||||||
</div> |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## What is TypePHP? |
|
||||||
|
|
||||||
TypePHP is an Ahead-Of-Time (AOT) compiler that translates PHP source code into |
|
||||||
C++ and then into native machine code. Unlike a bytecode cache or a VM, it does |
|
||||||
not interpret opcodes at runtime: it generates optimized native binaries that |
|
||||||
run directly on the CPU. |
|
||||||
|
|
||||||
It keeps familiar PHP syntax and adds compile-time type information, so the |
|
||||||
compiler can emit fast, statically-typed C++ for hot paths. Dynamic PHP values, |
|
||||||
internal functions, reflection, and object metadata continue to interoperate |
|
||||||
with the Zend runtime through PHPX; user functions are not executed as Zend |
|
||||||
opcodes after they have been compiled. |
|
||||||
|
|
||||||
TypePHP is **written entirely in PHP** and is **fully self-hosting**: the `tpc` |
|
||||||
compiler binary is built by compiling the compiler's own PHP source code with |
|
||||||
TypePHP. The bootstrap chain is pure PHP — no C or C++ glue in the compiler |
|
||||||
itself. |
|
||||||
|
|
||||||
TypePHP is under active development. It intentionally supports a defined, |
|
||||||
testable subset of PHP rather than claiming drop-in compatibility with every |
|
||||||
dynamic PHP program. Read [Compatibility model](#compatibility-model) and the |
|
||||||
[incompatible-feature list](docs/en/INCOMPATIBLE_PHP_FEATURES.md) before adopting |
|
||||||
it for an existing application. |
|
||||||
|
|
||||||
## How it works |
|
||||||
|
|
||||||
```text |
|
||||||
PHP source + .stub.php declarations + optional C/C++ sources |
|
||||||
│ |
|
||||||
▼ |
|
||||||
parse, validate, and collect declarations |
|
||||||
│ |
|
||||||
▼ |
|
||||||
lower function bodies and constants to C++17 |
|
||||||
│ |
|
||||||
▼ |
|
||||||
native compiler + reusable object/PCH caches |
|
||||||
│ |
|
||||||
▼ |
|
||||||
executable | PHP extension | shared library | WASI component |
|
||||||
``` |
|
||||||
|
|
||||||
The prepare phase builds the complete symbol model without allocating runtime |
|
||||||
cache IDs. Constants and declaration defaults retain their AST until the |
|
||||||
convert phase, where they are lowered after all project symbols are known. |
|
||||||
This two-phase design keeps multi-file and self-hosted builds deterministic. |
|
||||||
|
|
||||||
## Features |
|
||||||
|
|
||||||
- **Self-hosting, written in PHP** — the TypePHP compiler is implemented |
|
||||||
entirely in PHP and bootstraps itself: `tpc` compiles the compiler's own |
|
||||||
source into a native binary. |
|
||||||
- **True AOT compilation** — PHP is lowered to C++17, then to native machine |
|
||||||
code. No interpreter, no opcode cache, no JIT warm-up. |
|
||||||
- **Three native build modes** — build a native `bin` executable, a loadable |
|
||||||
PHP `ext` extension, or a reusable `lib` shared library from the same codebase. |
|
||||||
- **Native type system** — `int`, `float`, and `bool` map directly to C++ |
|
||||||
scalar types (`int64_t`, `double`, `bool`) for orders-of-magnitude speedups |
|
||||||
on numeric code. |
|
||||||
- **High-precision numerics** — `bigInt` (GMP), `decimal` (libmpdec), and |
|
||||||
`bigFloat` (MPFR), with typed operators and method APIs. |
|
||||||
- **Strongly-typed containers** — `std::array`, `std::vector`, `std::map`, and |
|
||||||
`std::ordered_map` with compile-time element types; up to **10×** faster than |
|
||||||
PHP arrays and on par with C++ `std::vector`. |
|
||||||
- **Universal methods** — call methods directly on primitives |
|
||||||
(`$s->upper()`, `$arr->contains()`, `$big->mul(2)`); statically-known calls |
|
||||||
are resolved directly at compile time. |
|
||||||
- **Mixed C++ / PHP** — call C++ functions from PHP (and vice versa) for |
|
||||||
performance-critical kernels. |
|
||||||
- **Compile-time functions & keywords** — `any()`, `refval()`, `objval()`, |
|
||||||
`expected()`, `unexpected()`, plus `toInt()`, `toString()`, `toArray()` and |
|
||||||
friends. |
|
||||||
- **Compile-time safety** — `#[Immutable]` read-only contracts and `#[ArrayDef]` |
|
||||||
array-shape metadata, checked at compile time with zero runtime cost. |
|
||||||
- **Compile-time code generation** — `#[Getter]`, `#[Setter]`, `#[With]`, |
|
||||||
`#[Constructor]`, `#[Printer]`, and `#[Arrayable]` generate type-safe methods |
|
||||||
from property declarations. |
|
||||||
- **Modern PHP support** — PHP 8.4 property hooks, asymmetric visibility, |
|
||||||
PHP 8.5 `clone()`-with, and `(void)` discard expressions. |
|
||||||
- **Cross-platform & WASM** — Linux, Windows, and macOS targets for x64 and |
|
||||||
ARM64, plus WASI 0.2 and browser (Jco) output. |
|
||||||
- **Python bridge** — generate IDE helpers for Python modules and convert |
|
||||||
Python scripts to TypePHP. |
|
||||||
|
|
||||||
## Why TypePHP? |
|
||||||
|
|
||||||
| | TypePHP AOT | Opcode cache (OPcache) | JIT (PHP 8+) | |
|
||||||
|---|---|---|---| |
|
||||||
| Compilation target | Native machine code | Bytecode | Machine code (trace) | |
|
||||||
| Startup / warm-up | None (already compiled) | Per-process warm-up | JIT warm-up | |
|
||||||
| Type-driven optimization | Compile-time, full-program | None | Limited, trace-based | |
|
||||||
| Native executable output | Yes | No | No | |
|
||||||
| Source code protection | Compiled to machine code | Bytecode (reversible) | Bytecode (reversible) | |
|
||||||
| Deterministic performance | Yes | No | No | |
|
||||||
|
|
||||||
**Strengths over plain PHP:** |
|
||||||
|
|
||||||
- **Near-native performance.** Numeric and container-heavy hot paths compile |
|
||||||
down to the same machine code a C++ program would produce. See the |
|
||||||
[benchmark](#benchmark) below. |
|
||||||
- **Source protection.** Your source is compiled away — shipped artifacts are |
|
||||||
native binaries, not readable PHP files. |
|
||||||
- **Native process entry.** Binary mode starts directly from a native |
|
||||||
executable and does not require the PHP CLI or a separate interpreter |
|
||||||
process. The executable still embeds/links PHPX, `libphp`, and any configured |
|
||||||
native libraries, which must be available in the deployment package. |
|
||||||
- **Gradual typing that actually pays off.** Add `use native_types`, `std::` |
|
||||||
containers, and type declarations only where performance matters; the rest |
|
||||||
stays ordinary PHP. |
|
||||||
- **Zend ecosystem interop.** Extension mode loads as a standard PHP extension, |
|
||||||
and projects can call supported internal functions and require other Zend |
|
||||||
extensions explicitly. |
|
||||||
|
|
||||||
## Requirements |
|
||||||
|
|
||||||
- **PHP 8.4 – 8.5** CLI, development headers, and `php-config` |
|
||||||
- The matching **PHP embed library** (`libphp.so` or `libphp.dylib`) for binary/shared-library |
|
||||||
builds on Unix-like systems |
|
||||||
- **GCC 9+** (or Clang) with **C++17** |
|
||||||
- **CMake 3.24+** |
|
||||||
- **Composer 2** |
|
||||||
- High-precision math libraries: **GMP**, **MPFR** (libmpdec is bundled with PHPX) |
|
||||||
|
|
||||||
```shell |
|
||||||
# Ubuntu/Debian |
|
||||||
sudo apt install build-essential cmake pkg-config libgmp-dev libmpfr-dev |
|
||||||
|
|
||||||
# RHEL/CentOS/Fedora |
|
||||||
sudo dnf install gcc gcc-c++ cmake pkgconf-pkg-config gmp-devel mpfr-devel |
|
||||||
|
|
||||||
# Arch Linux |
|
||||||
sudo pacman -S base-devel cmake pkgconf gmp mpfr |
|
||||||
``` |
|
||||||
|
|
||||||
> GMP powers `bigInt` and MPFR powers `bigFloat`. The `decimal` type is backed |
|
||||||
> by libmpdec, which is bundled with PHPX — no separate install required. |
|
||||||
|
|
||||||
Linux x64 is the primary development and full-test CI platform. The compiler |
|
||||||
also has Windows, macOS, ARM64, and WASI backends; availability of PHP embed, |
|
||||||
toolchain, and third-party libraries still determines which target can be |
|
||||||
built on a given host. |
|
||||||
|
|
||||||
Native release assets are built with the latest PHP 8.5 ZTS release. TypePHP |
|
||||||
publishes Linux x64, Linux ARM64, macOS ARM64, and Windows x64 packages. Native |
|
||||||
NTS and 32-bit x86 packages are not provided. Linux and macOS archives contain |
|
||||||
the compiler and production Composer dependencies, while the Windows archive |
|
||||||
contains the complete matching PHP/PHPX runtime and SDK. |
|
||||||
|
|
||||||
## Installation |
|
||||||
|
|
||||||
### Via Composer |
|
||||||
|
|
||||||
```bash |
```bash |
||||||
composer require --dev swoole/typephp |
composer require --dev swoole/typephp |
||||||
``` |
``` |
||||||
|
|
||||||
Then compile your project: |
安装后可直接编译项目: |
||||||
|
|
||||||
```bash |
```bash |
||||||
vendor/bin/tpc.php project.yml |
vendor/bin/tpc.php project.yml |
||||||
``` |
``` |
||||||
|
|
||||||
When working inside the TypePHP source repository, use the local entry point |
在 TypePHP 源码仓库中则使用: |
||||||
instead: |
|
||||||
|
|
||||||
```bash |
```bash |
||||||
bin/tpc.php project.yml |
bin/tpc.php project.yml |
||||||
``` |
``` |
||||||
|
|
||||||
### From source |
Linux 环境缺少 `libphp.so` 时,`tpc.php` 可以交互式下载 PHP 源码并自动构建,详见 [自动构建 libphp.so](docs/LIBPHP_INSTALLER.md)。 |
||||||
|
|
||||||
```bash |
|
||||||
git clone https://github.com/swoole/typephp.git |
|
||||||
cd typephp |
|
||||||
composer install |
|
||||||
php bin/tpc.php --help |
|
||||||
``` |
|
||||||
|
|
||||||
`PHPX_HOME` may point to a separate PHPX checkout or installation. `PHP_HOME` |
|
||||||
may point to the PHP embed prefix; it must contain `bin/php-config`, PHP headers, |
|
||||||
and `lib/libphp.so` on Unix-like systems. |
|
||||||
|
|
||||||
### Building `libphp.so` |
|
||||||
|
|
||||||
Binary and shared-library builds require PHP's `embed` SAPI. If `libphp.so` is |
|
||||||
missing on Linux, `tpc.php` can interactively download the PHP source and build |
|
||||||
it for you. A PHP extension build resolves Zend symbols from the host SAPI and |
|
||||||
must not load a second `libphp`. See |
|
||||||
[Automatic libphp.so build](docs/en/LIBPHP_INSTALLER.md). |
|
||||||
|
|
||||||
## Quick Start |
|
||||||
|
|
||||||
Create `hello.php`: |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
echo "Hello World!\n"; |
|
||||||
var_dump(PHP_VERSION); |
|
||||||
var_dump(php_uname()); |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Compile and run it: |
|
||||||
|
|
||||||
```bash |
|
||||||
bin/tpc.php hello.php |
|
||||||
./hello |
|
||||||
``` |
|
||||||
|
|
||||||
Example output (the exact PHP version and platform strings depend on the linked |
|
||||||
runtime): |
|
||||||
|
|
||||||
``` |
|
||||||
Hello World! |
|
||||||
string(5) "8.x.x" |
|
||||||
string(16) "Linux ..." |
|
||||||
``` |
|
||||||
|
|
||||||
> Binary mode requires a global `main()` function. It may be declared with no |
|
||||||
> parameters, or as `main(int $argc, array $argv)` to receive command-line |
|
||||||
> arguments, and must return `void`. Top-level executable statements are not |
|
||||||
> allowed; executable code belongs in a function or method. |
|
||||||
|
|
||||||
## Compilation Modes |
|
||||||
|
|
||||||
TypePHP supports three build modes, selected with `-m` / `--mode`: |
|
||||||
|
|
||||||
| Mode | Flag | Output | Needs `main()` | Typical use | |
|
||||||
|---|---|---|---|---| |
|
||||||
| Binary | `-m bin` (default) | Executable | Yes | CLI tools, long-running services, standalone apps | |
|
||||||
| Extension | `-m ext` | PHP `.so` / `.dll` | No | Loading compiled functions/classes into a PHP SAPI | |
|
||||||
| Library | `-m lib` | Shared library plus generated `.stub.php` | No | Reusing a compiled TypePHP API from another project | |
|
||||||
|
|
||||||
```bash |
|
||||||
# Binary (default) |
|
||||||
bin/tpc.php app.php -o myapp |
|
||||||
|
|
||||||
# PHP extension |
|
||||||
bin/tpc.php extension/ -m ext -o my_extension |
|
||||||
|
|
||||||
# Shared library; also generates mylib.stub.php |
|
||||||
bin/tpc.php lib/ -m lib -o mylib |
|
||||||
``` |
|
||||||
|
|
||||||
See [Compilation modes](docs/en/COMPILATION_MODES.md) for details. |
|
||||||
|
|
||||||
## Project configuration |
|
||||||
|
|
||||||
For multi-file projects, keep repeatable build settings in `project.yml`: |
|
||||||
|
|
||||||
```yaml |
|
||||||
name: myapp |
|
||||||
mode: bin |
|
||||||
php-version: "8.5" |
|
||||||
optimize: 2 |
|
||||||
job: 8 |
|
||||||
build-dir: build |
|
||||||
cxx-std: c++17 |
|
||||||
|
|
||||||
sources: |
|
||||||
- src |
|
||||||
- cpp-src |
|
||||||
- path: src/php85 |
|
||||||
if: PHP_VERSION_ID >= 80500 |
|
||||||
- path: src/windows |
|
||||||
if: PHP_OS_FAMILY == "Windows" |
|
||||||
|
|
||||||
ignore: |
|
||||||
- src/experimental |
|
||||||
|
|
||||||
include-paths: |
|
||||||
- native/include |
|
||||||
defines: |
|
||||||
- FEATURE_FAST_PATH=1 |
|
||||||
link-paths: |
|
||||||
- native/lib |
|
||||||
link-libs: |
|
||||||
- curl |
|
||||||
|
|
||||||
# Zend extension requirements, not native linker libraries. |
|
||||||
# `extension-dependencies` is the equivalent long name; do not use both. |
|
||||||
ext-deps: |
|
||||||
- pdo_mysql |
|
||||||
- curl |
|
||||||
``` |
|
||||||
|
|
||||||
Paths are resolved relative to the YAML file. A source entry may be a file or |
|
||||||
directory; conditional entries support `PHP_VERSION`, `PHP_VERSION_ID`, and |
|
||||||
`PHP_OS_FAMILY`. CLI arguments override their YAML counterparts. Native linker |
|
||||||
dependencies belong in `link-libs`; `ext-deps` writes `ZEND_MOD_REQUIRED` |
|
||||||
entries so Zend can reject loading when a required PHP extension is missing. |
|
||||||
|
|
||||||
The build directory contains generated C++, dependency objects, and the |
|
||||||
precompiled-header cache. Reusing it makes incremental builds much faster; |
|
||||||
use `--force` only when the reusable PHPX objects must be rebuilt. |
|
||||||
|
|
||||||
See [Compiler CLI](docs/en/COMPILER_CLI.md) for all project keys and command-line |
|
||||||
precedence rules. |
|
||||||
|
|
||||||
## Compatibility model |
|
||||||
|
|
||||||
TypePHP follows PHP syntax and runtime behavior where they are compatible with |
|
||||||
ahead-of-time compilation, but it also makes several deliberate restrictions: |
|
||||||
|
|
||||||
- global scope is declaration-only; executable statements must be inside a |
|
||||||
function or method; |
|
||||||
- binary mode has a strict `main()` signature; |
|
||||||
- `use native_types` opts scalar declarations into fixed native storage, so a |
|
||||||
value cannot later change to an incompatible type; |
|
||||||
- statically-known calls and properties are compiled directly, while supported |
|
||||||
dynamic operations use PHPX/Zend runtime fallbacks; |
|
||||||
- `.stub.php` files declare C++ or imported-library APIs and must contain empty |
|
||||||
bodies; `#[Native]` classes are not permitted in stub files; |
|
||||||
- some highly dynamic reference, declaration, closure, and reflection patterns |
|
||||||
remain intentionally unsupported. |
|
||||||
|
|
||||||
The compatibility boundary is part of the public contract and has both |
|
||||||
positive and negative tests. Consult |
|
||||||
[Incompatible PHP features](docs/en/INCOMPATIBLE_PHP_FEATURES.md) for the current, |
|
||||||
specific list instead of assuming that absence from this README means support. |
|
||||||
|
|
||||||
## Compile-time attributes and code generation |
|
||||||
|
|
||||||
TypePHP consumes its built-in code-generation attributes while lowering the |
|
||||||
class. The generated methods retain the declared property types and take part |
|
||||||
in the same conflict, inheritance, and final-method checks as explicitly |
|
||||||
declared methods. |
|
||||||
|
|
||||||
| Attribute | Target | Generated API | |
|
||||||
|---|---|---| |
|
||||||
| `#[Getter]` | Instance property, including a promoted property | `public function getName(): T` | |
|
||||||
| `#[Setter]` | Mutable instance property, including a promoted property | `public function setName(T $name): void` | |
|
||||||
| `#[With]` | Mutable instance property, including a promoted property | `public function withName(T $name): static`; clones the object, updates the clone, and returns it | |
|
||||||
| `#[Constructor]` | Declared instance property | Adds the property to a generated public `__construct()` | |
|
||||||
| `#[Printer]` | Named class | `public function __toString(): string` | |
|
||||||
| `#[Arrayable]` | Named class | `public function toArray(): array` | |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
|
|
||||||
#[Printer(fields: ['id', 'name'])] |
|
||||||
#[Arrayable(fields: ['id', 'name'])] |
|
||||||
final class User |
|
||||||
{ |
|
||||||
#[Constructor, Getter, With] |
|
||||||
public int $id; |
|
||||||
|
|
||||||
#[Constructor, Getter, Setter] |
|
||||||
public string $name = 'guest'; |
|
||||||
} |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
$user = new User(7); |
|
||||||
$user->setName('Alice'); |
|
||||||
|
|
||||||
$copy = $user->withId(8); |
|
||||||
echo $user->getId(); // 7 |
|
||||||
echo $copy->getId(); // 8 |
|
||||||
echo $user; // User(id=7, name=Alice) |
|
||||||
echo $user->toArray()['name']; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
Without `fields`, `#[Printer]` and `#[Arrayable]` use the class's own public |
|
||||||
instance properties. The positional form, such as `#[Arrayable(['id'])]`, is |
|
||||||
equivalent to `#[Arrayable(fields: ['id'])]`. |
|
||||||
|
|
||||||
`#[Getter]`, `#[Setter]`, and `#[With]` cannot target static properties or |
|
||||||
properties with hooks. `#[Setter]` and `#[With]` additionally reject readonly |
|
||||||
properties. `#[Constructor]` cannot be used when the class already declares |
|
||||||
`__construct()`, and required constructor properties must precede properties |
|
||||||
with defaults. A generated method name that conflicts with a declared or |
|
||||||
inherited final method is a compile-time error. |
|
||||||
|
|
||||||
## Examples |
|
||||||
|
|
||||||
### 1. Native types — compile-time numeric speedup |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
use native_types; |
|
||||||
|
|
||||||
function fib(int $n): int |
|
||||||
{ |
|
||||||
if ($n == 1 || $n == 2) { |
|
||||||
return 1; |
|
||||||
} |
|
||||||
return fib($n - 1) + fib($n - 2); |
|
||||||
} |
|
||||||
|
|
||||||
function main(int $argc, array $argv): void |
|
||||||
{ |
|
||||||
$n = (int)$argv[1]; |
|
||||||
$begin = microtime(true); |
|
||||||
echo fib($n) . "\n"; |
|
||||||
echo "Time: " . (microtime(true) - $begin) . "\n"; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
```bash |
|
||||||
bin/tpc.php fib.php -O3 -o fib |
|
||||||
./fib 30 |
|
||||||
``` |
|
||||||
|
|
||||||
With `use native_types`, `int` variables become C++ `int64_t` and arithmetic |
|
||||||
compiles to plain CPU instructions instead of ZendVM calls. |
|
||||||
|
|
||||||
### 2. High-precision numerics |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
declare(strict_types=1); |
|
||||||
use native_types; |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
// 54-digit integer — automatically detected and stored as bigInt |
|
||||||
$a = std::bigInt("123456789012345678901234567890123456789012345678901234"); |
|
||||||
$b = std::bigInt("987654321098765432109876543210987654321098765432109876"); |
|
||||||
|
|
||||||
echo $a->add($b)->toString() . "\n"; // exact, no overflow |
|
||||||
|
|
||||||
// Exact decimal arithmetic — no binary floating-point error |
|
||||||
$c = std::decimal("0.1")->add(std::decimal("0.2")); |
|
||||||
echo $c->toString() . "\n"; // "0.3" |
|
||||||
|
|
||||||
// 256-bit floating point |
|
||||||
$pi = std::bigFloat("3.14159265358979323846264338327950288419716939937510"); |
|
||||||
echo $pi->mul(2)->toString() . "\n"; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
See [High-precision types](docs/en/HIGH_PRECISION_TYPES.md) and |
|
||||||
[Native types](docs/en/NATIVE_TYPES.md). |
|
||||||
|
|
||||||
### 3. Strongly-typed containers |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
use native_types; |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
$vector = std::vector(Type::Int); |
|
||||||
|
|
||||||
$vector[] = 1; |
|
||||||
$vector[] = 2; |
|
||||||
$vector[] = 3; |
|
||||||
|
|
||||||
$sum = 0; |
|
||||||
foreach ($vector as $value) { |
|
||||||
$sum += $value; |
|
||||||
} |
|
||||||
|
|
||||||
echo $sum . "\n"; // 6 |
```shell |
||||||
echo $vector[1] . "\n"; // 2 |
# Ubuntu/Debian |
||||||
|
sudo apt install libgmp-dev libmpfr-dev libmpdec-dev |
||||||
// key-value map with fixed key/value types |
|
||||||
$map = std::ordered_map(Type::String, Type::Int); |
|
||||||
$map["a"] = 1; |
|
||||||
$map["b"] = 2; |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
See [Std containers](docs/en/STD_CONTAINERS.md). |
|
||||||
|
|
||||||
### 4. Universal methods |
|
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
$s = "hello world"; |
|
||||||
echo $s->length() . "\n"; // strlen() |
|
||||||
echo $s->upper() . "\n"; // strtoupper() |
|
||||||
echo $s->substr(0, 5) . "\n"; // substr() |
|
||||||
|
|
||||||
$arr = [1, 3, 5, 7, 9]; |
# RHEL/CentOS/Fedora |
||||||
echo $arr->count() . "\n"; // count() |
sudo dnf install gmp-devel mpfr-devel libmpdec-devel |
||||||
var_dump($arr->contains(3)); // in_array() |
|
||||||
|
|
||||||
$big = std::bigInt("12345678901234567890"); |
# Arch Linux |
||||||
echo $big->mul(2)->toString() . "\n"; |
sudo pacman -S gmp mpfr mpdecimal |
||||||
} |
|
||||||
``` |
``` |
||||||
|
|
||||||
Method calls on primitives are resolved at compile time into direct C/C++ |
> GMP 用于 `BigInt` 任意精度整数,MPFR 用于 `BigFloat` 高精度浮点数,libmpdec 用于 `Decimal` 十进制高精度小数。 |
||||||
function calls — no vtable lookup, no reflection, no runtime dispatch. See |
|
||||||
[Universal methods](docs/en/UNIVERSAL_METHODS.md). |
|
||||||
|
|
||||||
### 5. Mixed C++ / PHP |
|
||||||
|
|
||||||
Write performance-critical kernels in C++ and call them from PHP: |
> 预览版目前仅支持 `Linux` 系统,建议使用 `Ubuntu 22.04` |
||||||
|
|
||||||
```cpp |
## PHP |
||||||
// math.cpp |
必须包含 embed 模块 |
||||||
#include <phpx.h> |
|
||||||
|
|
||||||
using namespace php; |
## PHPX |
||||||
|
可使用 `composer install` 安装依赖。 |
||||||
|
进入 `vendor/swoole/phpx` 目录,编译 `phpx` |
||||||
|
|
||||||
Int php_fast_sum(Int a, Int b) { |
```shell |
||||||
return a + b; |
cd vendor/swoole/phpx |
||||||
} |
cmake . |
||||||
``` |
make -j32 |
||||||
|
|
||||||
```php |
|
||||||
<?php |
|
||||||
// math.stub.php — declares the C++ function signature |
|
||||||
function fast_sum(int $a, int $b): int {} |
|
||||||
``` |
``` |
||||||
|
|
||||||
```php |
## 动态链接库 |
||||||
<?php |
```shell |
||||||
function main(): void |
sudo ldconfig -p | grep php |
||||||
{ |
|
||||||
echo fast_sum(3, 4) . "\n"; // 7 |
|
||||||
} |
|
||||||
``` |
``` |
||||||
|
必须包含 `libphp.so` 和 `libphpx.so` |
||||||
|
|
||||||
Add `math.cpp`, `math.stub.php`, and the calling PHP source to the same project |
若编译完成,但找不到动态链接库,需要修改 |
||||||
configuration. The `php_` C++ symbol prefix is the TypePHP callable ABI; stub |
```shell |
||||||
functions provide type metadata only and must not contain an implementation. |
vim /etc/ld.so.conf.d/swoole.conf |
||||||
|
|
||||||
See [Mixed C++/PHP](docs/en/MIXED_CPP_PHP.md). |
|
||||||
|
|
||||||
## Benchmark |
|
||||||
|
|
||||||
### PHP language benchmarks (from php-src) |
|
||||||
|
|
||||||
TypePHP runs the official `bench.php` and `micro_bench.php` language |
|
||||||
benchmarks that ship with the PHP source tree, compiled with `-O3`: |
|
||||||
|
|
||||||
| Benchmark | Interpreted PHP | TypePHP AOT (`-O3`) | Speedup | |
|
||||||
|---|---|---|---| |
|
||||||
| `bench.php` (total) | 5.034 s | **0.603 s** | ~8× | |
|
||||||
| `micro_bench.php` (total) | 13.045 s | **2.021 s** | ~6.5× | |
|
||||||
|
|
||||||
Both benchmarks measure core PHP language performance — function calls, object |
|
||||||
property access, array/hash access, string handling, control flow, and more. |
|
||||||
The checked-in workloads are [`benchmark/bench.php`](benchmark/bench.php) and |
|
||||||
[`benchmark/micro_bench.php`](benchmark/micro_bench.php). Additional focused |
|
||||||
performance regressions live in the same [`benchmark/`](benchmark/) directory. |
|
||||||
|
|
||||||
These numbers are a project measurement snapshot, not a performance guarantee. |
|
||||||
PHP version, compiler, CPU, optimization flags, and enabled extensions can all |
|
||||||
change the result; compare on the same machine with the same workload before |
|
||||||
making deployment decisions. |
|
||||||
|
|
||||||
### std::array vs PHP array |
|
||||||
|
|
||||||
A 10000×100000 element update loop, comparing PHP arrays against TypePHP's |
|
||||||
`std::array` and native C++: |
|
||||||
|
|
||||||
| Implementation | Time | |
|
||||||
|---|---| |
|
||||||
| PHP array (JIT) | 67.6 s | |
|
||||||
| `std::array` (TypePHP AOT) | **6.4 s** | |
|
||||||
| C++ `std::vector` | 6.2 s | |
|
||||||
|
|
||||||
`std::array` is roughly **10× faster** than PHP arrays and performs |
|
||||||
close to the hand-written C++ result in this workload. See the benchmark in |
|
||||||
[Std containers](docs/en/STD_CONTAINERS.md). |
|
||||||
|
|
||||||
## Command Line |
|
||||||
|
|
||||||
```bash |
|
||||||
bin/tpc.php <file|dir|project.yml> [options] [-- program-args...] |
|
||||||
``` |
``` |
||||||
|
|
||||||
Common usage: |
添加路径 |
||||||
|
|
||||||
```bash |
|
||||||
# Compile a single file |
|
||||||
bin/tpc.php app.php |
|
||||||
|
|
||||||
# Optimize and run, passing args to the program after `--` |
|
||||||
bin/tpc.php app.php -O3 -r -- --flag value |
|
||||||
|
|
||||||
# Compile a project defined in project.yml |
|
||||||
bin/tpc.php project.yml -O2 -j 8 |
|
||||||
|
|
||||||
# Build a PHP extension |
|
||||||
bin/tpc.php extension/ -m ext -o my_extension |
|
||||||
|
|
||||||
# Only generate C++ (skip compile & link) |
|
||||||
bin/tpc.php app.php --dry --build-dir /tmp/typephp-build |
|
||||||
|
|
||||||
# Compile to WASI 0.2 |
|
||||||
bin/tpc.php --wasm app.php |
|
||||||
|
|
||||||
# Compile for the browser (requires jco) |
|
||||||
bin/tpc.php --wasm=browser app.php |
|
||||||
``` |
``` |
||||||
|
/home/swoole/workspace/projects/phpx/lib |
||||||
Key options: |
/opt/php-8.4/lib/ |
||||||
|
|
||||||
| Option | Description | |
|
||||||
|---|---| |
|
||||||
| `-O <0-3>` | Optimization level (default `0`) | |
|
||||||
| `-d`, `--debug` | Debug build with symbols and source tracking | |
|
||||||
| `-o`, `--output <file>` | Output file name | |
|
||||||
| `-m`, `--mode <bin\|lib\|ext>` | Build mode (default `bin`) | |
|
||||||
| `-r`, `--run` | Run after a successful build | |
|
||||||
| `-j`, `--job <num>` | Parallel compile jobs (default `4`) | |
|
||||||
| `-f`, `--force` | Rebuild reusable PHPX objects instead of using the cache | |
|
||||||
| `--build-dir <dir>` | Directory for generated C++ and intermediates | |
|
||||||
| `--dry` | Generate C++ only, skip compile and link | |
|
||||||
| `--php-version <8.4\|8.5>` | PHP syntax version to accept | |
|
||||||
| `--cxx-std <ver>` | C++ standard (e.g. `c++17`, `c++20`) | |
|
||||||
| `--march <arch>` | Target instruction set (e.g. `native`) | |
|
||||||
| `--target-platform <triple>` | Cross-compilation target triple | |
|
||||||
| `--lto` | Enable link-time optimization | |
|
||||||
| `--sanitize <type>` | Enable a sanitizer (e.g. `address`) | |
|
||||||
| `--profile` | Enable Linux gperftools profiling | |
|
||||||
| `--format` | Format generated C++ with clang-format | |
|
||||||
| `--no-literal-strings` | Disable the literal-string table optimization | |
|
||||||
| `--no-progress`, `--no-color` | CI-friendly output controls | |
|
||||||
| `-I`, `-D`, `-L`, `-l` | Repeatable native include, define, library path, and library options | |
|
||||||
|
|
||||||
Run `bin/tpc.php --help` for the authoritative, up-to-date list. See |
|
||||||
[Compiler CLI](docs/en/COMPILER_CLI.md) for details, including Bash completion: |
|
||||||
|
|
||||||
```bash |
|
||||||
source <(./tpc --generate-completion=bash) |
|
||||||
``` |
``` |
||||||
|
|
||||||
## Troubleshooting |
## Release packaging |
||||||
|
|
||||||
- **`libphp.so` / `libphp.dylib` is missing:** install/build the matching PHP embed SAPI, set |
|
||||||
`PHP_HOME`, or let `bin/tpc.php` offer the interactive Linux installer. |
|
||||||
- **PHPX cannot be found:** set `PHPX_HOME` to a PHPX installation containing |
|
||||||
`include/` and `lib/libphpx.so` (or the platform equivalent), then build PHPX |
|
||||||
before compiling the project. |
|
||||||
- **Startup crashes or ABI errors:** the PHP headers, `php-config`, `libphp`, |
|
||||||
and loaded extension ABI must agree on the PHP version and ZTS/NTS mode. Do |
|
||||||
not mix artifacts from different PHP builds. |
|
||||||
- **Incremental builds are unexpectedly slow:** keep a stable `--build-dir` so |
|
||||||
object and PCH caches can be reused. When an external test runner already |
|
||||||
runs several tests concurrently, avoid multiplying that concurrency by an |
|
||||||
unnecessarily large `tpc -j` value. |
|
||||||
- **A project compiles with `bin/tpc.php` but fails with `tpc`:** reproduce with |
|
||||||
the self-hosted compiler. Bootstrap execution can expose dynamic-call or ABI |
|
||||||
paths that the PHP-hosted compiler does not exercise. |
|
||||||
|
|
||||||
## Python bridge |
|
||||||
|
|
||||||
TypePHP ships a Python tool submodule that shares the `tpc` entry point: |
Use the same PHP entry point on Windows, Linux, and macOS: |
||||||
|
|
||||||
```shell |
```shell |
||||||
# Generate IDE helpers for Python modules |
php package.php |
||||||
./tpc --gen-python-helper math |
|
||||||
./tpc --gen-python-helper numpy --output-dir .ide-helper |
|
||||||
|
|
||||||
# Convert a Python script to TypePHP |
|
||||||
./tpc --convert-python-to-php script.py > script.php |
|
||||||
``` |
|
||||||
|
|
||||||
See [Python tool submodule](docs/en/python/tools.md). |
|
||||||
|
|
||||||
## Development and testing |
|
||||||
|
|
||||||
Install development dependencies and run the compiler unit suite: |
|
||||||
|
|
||||||
```bash |
|
||||||
composer install |
|
||||||
PHPX_HOME=/path/to/phpx vendor/bin/phpunit |
|
||||||
``` |
|
||||||
|
|
||||||
PHPT is the end-to-end suite. Build the self-hosted compiler first and pass it |
|
||||||
explicitly to the test runner; using the Zend PHP executable as `--compiler` |
|
||||||
does not test the deployed compiler: |
|
||||||
|
|
||||||
```bash |
|
||||||
PHPX_HOME=/path/to/phpx php bin/tpc.php project.yml --job 2 --no-progress |
|
||||||
php run-tests.php -q -j8 --compiler ./tpc tests/compiler |
|
||||||
``` |
``` |
||||||
|
|
||||||
Static analysis and the source-derived coverage matrix are separate checks: |
Windows packaging requires `PHP_HOME` and `PHPX_HOME`; Linux packaging requires |
||||||
|
UPX; macOS uses `strip` when available. TypePHP rejects 32-bit targets and |
||||||
```bash |
supports common 64-bit CPU architectures, including x86-64 and ARM64. |
||||||
composer analyse |
|
||||||
php bin/analyze-test-coverage.php |
|
||||||
php bin/analyze-test-coverage.php \ |
|
||||||
--format=markdown --output=build/test-coverage.md --strict |
|
||||||
``` |
|
||||||
|
|
||||||
The coverage tool reports PHP version × feature × positive compilation × |
|
||||||
runtime semantics × negative diagnostics, plus concrete PHP-parser AST nodes. |
|
||||||
It intentionally does not publish a single percentage without an explicit |
|
||||||
denominator. See [Test coverage analyzer](docs/en/TEST_COVERAGE_ANALYZER.md). |
|
||||||
|
|
||||||
GitHub Actions runs PHPUnit and self-hosted PHPT on PHP 8.4 and 8.5. Changes to |
|
||||||
compiler behavior should add a focused PHPUnit test for internal/code-generation |
|
||||||
rules and a PHPT whenever runtime output or diagnostics are observable. |
|
||||||
|
|
||||||
## Documentation |
|
||||||
|
|
||||||
- [Quick Start](docs/en/QUICKSTART.md) — minimal compilation flow |
|
||||||
- [Compilation modes](docs/en/COMPILATION_MODES.md) — `bin`, `ext`, `lib` |
|
||||||
- [Compiler CLI](docs/en/COMPILER_CLI.md) — CLI arguments and project config |
|
||||||
- [Incompatible PHP features](docs/en/INCOMPATIBLE_PHP_FEATURES.md) — current limits |
|
||||||
- [Native types](docs/en/NATIVE_TYPES.md) — native scalar types |
|
||||||
- [High-precision types](docs/en/HIGH_PRECISION_TYPES.md) — BigInt / Decimal / BigFloat |
|
||||||
- [Std containers](docs/en/STD_CONTAINERS.md) — strongly-typed containers |
|
||||||
- [Universal methods](docs/en/UNIVERSAL_METHODS.md) — compile-time method resolution |
|
||||||
- [Compile-time functions](docs/en/COMPILE_TIME_FUNCTIONS.md) — `any()`, `refval()`, `objval()`, … |
|
||||||
- [Mixed C++/PHP](docs/en/MIXED_CPP_PHP.md) — C++/PHP interop |
|
||||||
- [`#[Immutable]`](docs/en/IMMUTABLE.md) — compile-time read-only contracts |
|
||||||
- [`#[ArrayDef]`](docs/en/ARRAY_DEF.md) — typed array-property contracts |
|
||||||
- [Property hooks](docs/en/PROPERTY_HOOKS.md) — PHP 8.4 hook lowering and runtime metadata |
|
||||||
- [Object storage models](docs/en/OBJECT_STORAGE_AND_PASSING_MODELS.md) — Zend object, Box, and Native class boundaries |
|
||||||
- [Generators](docs/en/YIELD_GENERATOR.md) — generator lowering and lifecycle |
|
||||||
- [Test coverage analyzer](docs/en/TEST_COVERAGE_ANALYZER.md) — AST and feature evidence matrix |
|
||||||
- [WASI build](docs/en/WASI_BUILD.md) — WASI targets |
|
||||||
|
|
||||||
## License |
|
||||||
|
|
||||||
TypePHP is licensed under the [GNU General Public License v3.0](LICENSE). |
|
||||||
|
|
||||||
## Community |
|
||||||
|
|
||||||
- Repository: <https://github.com/swoole/typephp> |
|
||||||
- Copyright © 2026 上海识沃网络科技有限公司 (Swoole) |
|
||||||
|
|||||||
@ -0,0 +1,52 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> |
||||||
|
|
||||||
|
<!-- 程序集标识 --> |
||||||
|
<assemblyIdentity |
||||||
|
type="win32" |
||||||
|
name="AotCompiler.App" |
||||||
|
version="1.0.0.0" /> |
||||||
|
|
||||||
|
<!-- 信任信息 / UAC 权限级别 --> |
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> |
||||||
|
<security> |
||||||
|
<requestedPrivileges> |
||||||
|
<!-- |
||||||
|
asInvoker: 以当前用户权限运行(推荐默认值) |
||||||
|
highestAvailable: 请求当前用户能获得的最高权限 |
||||||
|
requireAdministrator: 要求管理员权限 |
||||||
|
--> |
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" /> |
||||||
|
</requestedPrivileges> |
||||||
|
</security> |
||||||
|
</trustInfo> |
||||||
|
|
||||||
|
<!-- 应用程序兼容性 --> |
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> |
||||||
|
<application> |
||||||
|
<!-- Windows 10 / 11 --> |
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> |
||||||
|
<!-- Windows 8.1 --> |
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /> |
||||||
|
<!-- Windows 8 --> |
||||||
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /> |
||||||
|
<!-- Windows 7 --> |
||||||
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" /> |
||||||
|
</application> |
||||||
|
</compatibility> |
||||||
|
|
||||||
|
<!-- DPI 感知(Windows 10 版本 1607+) --> |
||||||
|
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"> |
||||||
|
<asmv3:windowsSettings> |
||||||
|
<!-- |
||||||
|
PerMonitorV2: 逐显示器 DPI 感知 v2(推荐,支持混合模式 DPI 缩放) |
||||||
|
PerMonitor: 逐显示器 DPI 感知 v1 |
||||||
|
System: 系统 DPI 感知 |
||||||
|
None / 不设置: DPI 不感知(系统会自动缩放) |
||||||
|
--> |
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> |
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> |
||||||
|
</asmv3:windowsSettings> |
||||||
|
</asmv3:application> |
||||||
|
|
||||||
|
</assembly> |
||||||
@ -0,0 +1,495 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
""" |
||||||
|
使用 libclang 解析 C++ 代码并添加头文件路径 |
||||||
|
""" |
||||||
|
|
||||||
|
import clang.cindex |
||||||
|
from clang.cindex import Index, CursorKind, TypeKind, StorageClass |
||||||
|
import json |
||||||
|
import sys |
||||||
|
import os |
||||||
|
from pathlib import Path |
||||||
|
import subprocess |
||||||
|
import argparse |
||||||
|
|
||||||
|
|
||||||
|
class PHPConfigHelper: |
||||||
|
"""PHP 配置辅助类""" |
||||||
|
|
||||||
|
def __init__(self, php_config_path='php-config'): |
||||||
|
self.php_config = php_config_path |
||||||
|
self._check_availability() |
||||||
|
|
||||||
|
def _check_availability(self): |
||||||
|
"""检查 php-config 是否可用""" |
||||||
|
try: |
||||||
|
result = subprocess.run( |
||||||
|
[self.php_config, '--version'], |
||||||
|
capture_output=True, |
||||||
|
text=True, |
||||||
|
check=True |
||||||
|
) |
||||||
|
print(f"✓ 找到 PHP {result.stdout.strip()}") |
||||||
|
except (FileNotFoundError, subprocess.CalledProcessError) as e: |
||||||
|
print(f"警告: php-config 不可用: {e}") |
||||||
|
# 尝试使用常见路径 |
||||||
|
common_paths = [ |
||||||
|
'/usr/bin/php-config', |
||||||
|
'/usr/local/bin/php-config', |
||||||
|
'/opt/php/bin/php-config' |
||||||
|
] |
||||||
|
for path in common_paths: |
||||||
|
if os.path.exists(path): |
||||||
|
self.php_config = path |
||||||
|
try: |
||||||
|
result = subprocess.run( |
||||||
|
[self.php_config, '--version'], |
||||||
|
capture_output=True, |
||||||
|
text=True, |
||||||
|
check=True |
||||||
|
) |
||||||
|
print(f"✓ 找到 PHP {result.stdout.strip()} 在 {path}") |
||||||
|
return |
||||||
|
except (FileNotFoundError, subprocess.CalledProcessError): |
||||||
|
continue |
||||||
|
print("警告: php-config 在任何常见路径都不可用,使用默认路径") |
||||||
|
|
||||||
|
def get_includes(self): |
||||||
|
""" |
||||||
|
获取 include 路径列表 |
||||||
|
|
||||||
|
Returns: |
||||||
|
list: 头文件路径列表(不带 -I 前缀) |
||||||
|
""" |
||||||
|
try: |
||||||
|
result = subprocess.run( |
||||||
|
[self.php_config, '--includes'], |
||||||
|
capture_output=True, |
||||||
|
text=True, |
||||||
|
check=True |
||||||
|
) |
||||||
|
|
||||||
|
# 解析输出: "-I/path1 -I/path2" -> ['/path1', '/path2'] |
||||||
|
includes = [] |
||||||
|
for flag in result.stdout.strip().split(): |
||||||
|
if flag.startswith('-I'): |
||||||
|
includes.append(flag[2:]) |
||||||
|
|
||||||
|
return includes |
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError): |
||||||
|
print("警告: 无法获取 PHP includes,使用默认路径") |
||||||
|
return ['/usr/include/php', '/usr/include/php/20210902'] # 默认路径 |
||||||
|
|
||||||
|
def get_include_dir(self): |
||||||
|
"""获取主 include 目录""" |
||||||
|
try: |
||||||
|
result = subprocess.run( |
||||||
|
[self.php_config, '--include-dir'], |
||||||
|
capture_output=True, |
||||||
|
text=True, |
||||||
|
check=True |
||||||
|
) |
||||||
|
return result.stdout.strip() |
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError): |
||||||
|
return '/usr/include/php' |
||||||
|
|
||||||
|
def get_extension_dir(self): |
||||||
|
"""获取扩展目录""" |
||||||
|
try: |
||||||
|
result = subprocess.run( |
||||||
|
[self.php_config, '--extension-dir'], |
||||||
|
capture_output=True, |
||||||
|
text=True, |
||||||
|
check=True |
||||||
|
) |
||||||
|
return result.stdout.strip() |
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError): |
||||||
|
return '/usr/lib/php' |
||||||
|
|
||||||
|
def get_version(self): |
||||||
|
"""获取 PHP 版本""" |
||||||
|
try: |
||||||
|
result = subprocess.run( |
||||||
|
[self.php_config, '--version'], |
||||||
|
capture_output=True, |
||||||
|
text=True, |
||||||
|
check=True |
||||||
|
) |
||||||
|
return result.stdout.strip() |
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError): |
||||||
|
return 'unknown' |
||||||
|
|
||||||
|
def get_php_binary(self): |
||||||
|
"""获取 PHP 二进制路径""" |
||||||
|
try: |
||||||
|
result = subprocess.run( |
||||||
|
[self.php_config, '--php-binary'], |
||||||
|
capture_output=True, |
||||||
|
text=True, |
||||||
|
check=True |
||||||
|
) |
||||||
|
return result.stdout.strip() |
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError): |
||||||
|
return 'php' |
||||||
|
|
||||||
|
def get_configure_options(self): |
||||||
|
"""获取配置选项""" |
||||||
|
try: |
||||||
|
result = subprocess.run( |
||||||
|
[self.php_config, '--configure-options'], |
||||||
|
capture_output=True, |
||||||
|
text=True, |
||||||
|
check=True |
||||||
|
) |
||||||
|
return result.stdout.strip() |
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError): |
||||||
|
return '' |
||||||
|
|
||||||
|
def get_all_info(self): |
||||||
|
"""获取所有配置信息""" |
||||||
|
return { |
||||||
|
'version': self.get_version(), |
||||||
|
'includes': self.get_includes(), |
||||||
|
'include_dir': self.get_include_dir(), |
||||||
|
'extension_dir': self.get_extension_dir(), |
||||||
|
'php_binary': self.get_php_binary(), |
||||||
|
'configure_options': self.get_configure_options(), |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
class ClangParser: |
||||||
|
def __init__(self, libclang_path=None): |
||||||
|
""" |
||||||
|
初始化 Clang 解析器 |
||||||
|
|
||||||
|
Args: |
||||||
|
libclang_path: libclang 库的路径(可选) |
||||||
|
""" |
||||||
|
if libclang_path: |
||||||
|
try: |
||||||
|
clang.cindex.Config.set_library_file(libclang_path) |
||||||
|
except Exception as e: |
||||||
|
print(f"警告: 无法设置 libclang 路径 {libclang_path}: {e}") |
||||||
|
print("尝试使用默认路径...") |
||||||
|
|
||||||
|
try: |
||||||
|
self.index = Index.create() |
||||||
|
except Exception as e: |
||||||
|
print(f"错误: 无法创建 Clang 索引: {e}") |
||||||
|
print("请确保已安装 python3-clang 和 clang 库") |
||||||
|
raise |
||||||
|
|
||||||
|
def parse_file(self, filename, include_paths=None, defines=None, |
||||||
|
compiler_args=None, language='c++'): |
||||||
|
""" |
||||||
|
解析 C++ 文件 |
||||||
|
|
||||||
|
Args: |
||||||
|
filename: 要解析的文件路径 |
||||||
|
include_paths: 头文件搜索路径列表 |
||||||
|
defines: 宏定义列表 ['MACRO=value', 'DEBUG'] |
||||||
|
compiler_args: 额外的编译器参数 |
||||||
|
language: 语言类型 ('c', 'c++', 'objective-c') |
||||||
|
|
||||||
|
Returns: |
||||||
|
TranslationUnit 对象 |
||||||
|
""" |
||||||
|
if not os.path.exists(filename): |
||||||
|
raise FileNotFoundError(f"文件不存在: {filename}") |
||||||
|
|
||||||
|
args = [] |
||||||
|
|
||||||
|
# 1. 设置语言标准 |
||||||
|
if language == 'c++': |
||||||
|
args.extend([ |
||||||
|
'-x', 'c++', |
||||||
|
'-std=c++14', # 更标准的 C++ 版本 |
||||||
|
]) |
||||||
|
elif language == 'c': |
||||||
|
args.extend(['-x', 'c', '-std=c11']) |
||||||
|
|
||||||
|
# 2. 添加头文件搜索路径 |
||||||
|
if include_paths: |
||||||
|
for path in include_paths: |
||||||
|
if os.path.exists(path): # 检查路径是否存在 |
||||||
|
args.append(f'-I{path}') |
||||||
|
else: |
||||||
|
print(f"警告: 包含路径不存在: {path}") |
||||||
|
|
||||||
|
# 3. 添加宏定义 |
||||||
|
if defines: |
||||||
|
for define in defines: |
||||||
|
args.append(f'-D{define}') |
||||||
|
|
||||||
|
# 4. 添加额外的编译器参数 |
||||||
|
if compiler_args: |
||||||
|
args.extend(compiler_args) |
||||||
|
|
||||||
|
# 5. 常用的编译选项 |
||||||
|
args.extend([ |
||||||
|
'-Wno-pragma-once-outside-header', # 忽略警告 |
||||||
|
'-ferror-limit=0', # 不限制错误数量 |
||||||
|
'-fno-delayed-template-parsing', # 避免某些 C++ 模板解析问题 |
||||||
|
'-w', # 禁用所有警告以减少输出 |
||||||
|
]) |
||||||
|
|
||||||
|
print(f"编译参数: {' '.join(args)}") |
||||||
|
|
||||||
|
# 解析文件 |
||||||
|
try: |
||||||
|
tu = self.index.parse( |
||||||
|
filename, |
||||||
|
args=args, |
||||||
|
options=clang.cindex.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD |
||||||
|
) |
||||||
|
except Exception as e: |
||||||
|
print(f"解析文件时出错: {e}") |
||||||
|
print("尝试使用最小参数集...") |
||||||
|
# 尝试使用最小参数集 |
||||||
|
minimal_args = ['-x', 'c++', '-std=c++14', '-w'] |
||||||
|
if include_paths: |
||||||
|
for path in include_paths: |
||||||
|
if os.path.exists(path): |
||||||
|
minimal_args.append(f'-I{path}') |
||||||
|
try: |
||||||
|
tu = self.index.parse( |
||||||
|
filename, |
||||||
|
args=minimal_args, |
||||||
|
options=clang.cindex.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD |
||||||
|
) |
||||||
|
print("使用最小参数集成功解析") |
||||||
|
except Exception as e2: |
||||||
|
print(f"使用最小参数集也失败: {e2}") |
||||||
|
print("尝试解析不包含头文件的简化版本...") |
||||||
|
# 创建一个临时文件,移除头文件包含行 |
||||||
|
temp_filename = filename + ".tmp" |
||||||
|
with open(filename, 'r') as original: |
||||||
|
lines = original.readlines() |
||||||
|
|
||||||
|
# 移除 #include 行 |
||||||
|
filtered_lines = [line for line in lines if not line.strip().startswith('#include')] |
||||||
|
|
||||||
|
with open(temp_filename, 'w') as temp: |
||||||
|
temp.writelines(filtered_lines) |
||||||
|
|
||||||
|
try: |
||||||
|
tu = self.index.parse( |
||||||
|
temp_filename, |
||||||
|
args=minimal_args, |
||||||
|
options=clang.cindex.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD |
||||||
|
) |
||||||
|
print("解析简化版本成功") |
||||||
|
# 清理临时文件 |
||||||
|
os.remove(temp_filename) |
||||||
|
except Exception as e3: |
||||||
|
print(f"简化版本也失败: {e3}") |
||||||
|
# 清理临时文件 |
||||||
|
if os.path.exists(temp_filename): |
||||||
|
os.remove(temp_filename) |
||||||
|
raise |
||||||
|
|
||||||
|
# 检查诊断信息 |
||||||
|
if tu.diagnostics: |
||||||
|
print(f"\n诊断信息 ({len(tu.diagnostics)} 个):") |
||||||
|
error_count = 0 |
||||||
|
warning_count = 0 |
||||||
|
|
||||||
|
for diag in tu.diagnostics: |
||||||
|
if diag.severity >= 3: # 错误级别 |
||||||
|
error_count += 1 |
||||||
|
else: # 警告级别 |
||||||
|
warning_count += 1 |
||||||
|
|
||||||
|
print(f"错误: {error_count}, 警告: {warning_count}") |
||||||
|
|
||||||
|
# 只显示前几个诊断信息,避免输出过多 |
||||||
|
for i, diag in enumerate(tu.diagnostics): |
||||||
|
if i >= 5: # 只显示前5个 |
||||||
|
print("... 还有更多诊断信息") |
||||||
|
break |
||||||
|
print(f" [{diag.severity}] {diag.spelling}") |
||||||
|
if diag.location.file: |
||||||
|
print(f" at {diag.location.file.name}:{diag.location.line}") |
||||||
|
|
||||||
|
return tu |
||||||
|
|
||||||
|
def extract_functions(self, tu, name_prefixes=None): |
||||||
|
""" |
||||||
|
提取函数定义 |
||||||
|
|
||||||
|
Args: |
||||||
|
tu: TranslationUnit 对象 |
||||||
|
name_prefixes: 函数名前缀过滤列表 |
||||||
|
|
||||||
|
Returns: |
||||||
|
函数信息列表 |
||||||
|
""" |
||||||
|
functions = [] |
||||||
|
|
||||||
|
def visit_node(node, depth=0): |
||||||
|
# 只处理函数声明/定义 |
||||||
|
if node.kind == CursorKind.FUNCTION_DECL: |
||||||
|
try: |
||||||
|
func_info = self.parse_function(node) |
||||||
|
|
||||||
|
# 过滤函数名 |
||||||
|
if name_prefixes: |
||||||
|
if any(func_info['name'].startswith(prefix) |
||||||
|
for prefix in name_prefixes): |
||||||
|
functions.append(func_info) |
||||||
|
else: |
||||||
|
functions.append(func_info) |
||||||
|
except Exception as e: |
||||||
|
print(f"解析函数时出错: {e}") |
||||||
|
|
||||||
|
# 递归访问子节点 |
||||||
|
for child in node.get_children(): |
||||||
|
visit_node(child, depth + 1) |
||||||
|
|
||||||
|
visit_node(tu.cursor) |
||||||
|
return functions |
||||||
|
|
||||||
|
def parse_function(self, cursor): |
||||||
|
""" |
||||||
|
解析函数详细信息 |
||||||
|
""" |
||||||
|
# 检查方法是否存在 |
||||||
|
def safe_call(method, default_value=None): |
||||||
|
try: |
||||||
|
return method() |
||||||
|
except AttributeError: |
||||||
|
return default_value |
||||||
|
|
||||||
|
# 基本信息 |
||||||
|
func_info = { |
||||||
|
'name': cursor.spelling, |
||||||
|
'displayName': cursor.displayname, |
||||||
|
'mangledName': cursor.mangled_name, |
||||||
|
'returnType': cursor.result_type.spelling, |
||||||
|
'isStatic': cursor.storage_class == StorageClass.STATIC, |
||||||
|
'isInline': safe_call(lambda: cursor.is_inline_function(), False), |
||||||
|
'isVirtual': safe_call(lambda: cursor.is_virtual_method(), False), |
||||||
|
'isConst': safe_call(lambda: cursor.is_const_method(), False), |
||||||
|
'location': { |
||||||
|
'file': str(cursor.location.file) if cursor.location.file else None, |
||||||
|
'line': cursor.location.line, |
||||||
|
'column': cursor.location.column, |
||||||
|
}, |
||||||
|
'parameters': [], |
||||||
|
'namespaces': self.get_namespaces(cursor), |
||||||
|
} |
||||||
|
|
||||||
|
# 解析参数 |
||||||
|
for arg in cursor.get_arguments(): |
||||||
|
param_info = { |
||||||
|
'name': arg.spelling or f'arg{len(func_info["parameters"])}', |
||||||
|
'type': arg.type.spelling, |
||||||
|
'canonicalType': arg.type.get_canonical().spelling, |
||||||
|
} |
||||||
|
|
||||||
|
# 检查是否有默认值 |
||||||
|
try: |
||||||
|
for token in arg.get_tokens(): |
||||||
|
if token.spelling == '=': |
||||||
|
# 有默认值 |
||||||
|
param_info['hasDefault'] = True |
||||||
|
break |
||||||
|
except: |
||||||
|
# 如果无法获取 tokens,跳过默认值检查 |
||||||
|
pass |
||||||
|
|
||||||
|
func_info['parameters'].append(param_info) |
||||||
|
|
||||||
|
return func_info |
||||||
|
|
||||||
|
def get_namespaces(self, cursor): |
||||||
|
""" |
||||||
|
获取函数所在的命名空间 |
||||||
|
""" |
||||||
|
namespaces = [] |
||||||
|
parent = cursor.semantic_parent |
||||||
|
|
||||||
|
while parent and parent.kind != CursorKind.TRANSLATION_UNIT: |
||||||
|
if parent.kind == CursorKind.NAMESPACE: |
||||||
|
namespaces.insert(0, parent.spelling) |
||||||
|
parent = parent.semantic_parent |
||||||
|
|
||||||
|
return namespaces |
||||||
|
|
||||||
|
|
||||||
|
def main(): |
||||||
|
parser = argparse.ArgumentParser(description='使用 libclang 解析 C++ 代码并提取函数信息') |
||||||
|
parser.add_argument('filename', help='要解析的 C++ 文件路径') |
||||||
|
parser.add_argument('--libclang-path', help='libclang 库路径') |
||||||
|
parser.add_argument('--include-paths', nargs='*', help='额外的包含路径') |
||||||
|
parser.add_argument('--function-prefixes', nargs='*', help='函数名前缀过滤器') |
||||||
|
|
||||||
|
args = parser.parse_args() |
||||||
|
|
||||||
|
if not os.path.exists(args.filename): |
||||||
|
print(f"错误: 文件不存在: {args.filename}") |
||||||
|
sys.exit(1) |
||||||
|
|
||||||
|
try: |
||||||
|
# 创建解析器 |
||||||
|
parser_obj = ClangParser(libclang_path=args.libclang_path) |
||||||
|
|
||||||
|
# 配置头文件路径 |
||||||
|
include_paths = args.include_paths or [ |
||||||
|
"/usr/include/linux", |
||||||
|
"/home/swoole/workspace/projects/phpx/include" |
||||||
|
] |
||||||
|
|
||||||
|
# 尝试获取 PHP 配置的头文件路径 |
||||||
|
try: |
||||||
|
php_config = PHPConfigHelper() |
||||||
|
php_includes = php_config.get_includes() |
||||||
|
include_paths.extend(php_includes) |
||||||
|
except Exception as e: |
||||||
|
print(f"警告: 无法获取 PHP 配置: {e}") |
||||||
|
print("继续使用默认路径...") |
||||||
|
|
||||||
|
# 配置宏定义 |
||||||
|
defines = [ |
||||||
|
'HAVE_CONFIG_H', |
||||||
|
'ZEND_ENABLE_STATIC_TSRMLS_CACHE=1', |
||||||
|
] |
||||||
|
|
||||||
|
# 额外的编译器参数 |
||||||
|
compiler_args = [ |
||||||
|
'-fparse-all-comments', # 解析所有注释 |
||||||
|
'-Wno-unknown-pragmas', |
||||||
|
] |
||||||
|
|
||||||
|
# 解析文件 |
||||||
|
tu = parser_obj.parse_file( |
||||||
|
args.filename, |
||||||
|
include_paths=include_paths, |
||||||
|
defines=defines, |
||||||
|
compiler_args=compiler_args, |
||||||
|
language='c++' |
||||||
|
) |
||||||
|
|
||||||
|
# 提取函数 |
||||||
|
name_prefixes = args.function_prefixes or None |
||||||
|
functions = parser_obj.extract_functions(tu, name_prefixes=name_prefixes) |
||||||
|
|
||||||
|
# 输出结果 |
||||||
|
output = { |
||||||
|
'file': args.filename, |
||||||
|
'functions': functions, |
||||||
|
'total': len(functions), |
||||||
|
} |
||||||
|
|
||||||
|
print(json.dumps(output, indent=2, ensure_ascii=False)) |
||||||
|
|
||||||
|
except clang.cindex.TranslationUnitLoadError as e: |
||||||
|
print(f"翻译单元加载错误: {e}") |
||||||
|
print("这通常意味着 C++ 代码包含语法错误或缺少必要的头文件") |
||||||
|
sys.exit(1) |
||||||
|
except Exception as e: |
||||||
|
print(f"错误: {e}") |
||||||
|
sys.exit(1) |
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
main() |
||||||
@ -1,17 +0,0 @@ |
|||||||
# TypePHP benchmarks |
|
||||||
|
|
||||||
This directory contains repeatable performance workloads used to guide and |
|
||||||
verify compiler/runtime optimizations. Benchmark results depend on the CPU, |
|
||||||
PHP build, compiler, and system load, so compare PHP and TypePHP on the same |
|
||||||
machine instead of committing absolute timing expectations. |
|
||||||
|
|
||||||
- `bench.php` and `micro_bench.php` are the original general workloads moved |
|
||||||
from `examples/`. |
|
||||||
- `property-access/` builds and compares dynamic/static property access under |
|
||||||
Zend PHP and TypePHP. |
|
||||||
|
|
||||||
Run the property benchmark from the repository root: |
|
||||||
|
|
||||||
```bash |
|
||||||
php benchmark/property-access/run.php |
|
||||||
``` |
|
||||||
@ -1,3 +0,0 @@ |
|||||||
/build/ |
|
||||||
/property_access |
|
||||||
/*.rsp |
|
||||||
@ -1,15 +0,0 @@ |
|||||||
# Dynamic property benchmark |
|
||||||
|
|
||||||
This benchmark compares the same dynamic and static property operations under |
|
||||||
Zend PHP and a TypePHP `-O2` binary. Each metric is the best of seven rounds |
|
||||||
after three warm-up rounds and is reported in nanoseconds per property access. |
|
||||||
|
|
||||||
Run it from the repository root: |
|
||||||
|
|
||||||
```bash |
|
||||||
php benchmark/property-access/run.php |
|
||||||
``` |
|
||||||
|
|
||||||
To reuse an existing binary, add `--skip-build`. For local regression checks, |
|
||||||
`--max-ratio=1.5` exits unsuccessfully when a dynamic read or write takes more |
|
||||||
than 1.5 times the corresponding Zend PHP result. |
|
||||||
@ -1,154 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
declare(strict_types=1); |
|
||||||
|
|
||||||
final class DynamicPropertyEntity |
|
||||||
{ |
|
||||||
public int $first = 0; |
|
||||||
public int $second = 0; |
|
||||||
public int $third = 0; |
|
||||||
public int $fourth = 0; |
|
||||||
public int $fifth = 0; |
|
||||||
|
|
||||||
public function hydrate(array $data): void |
|
||||||
{ |
|
||||||
foreach ($data as $property => $value) { |
|
||||||
$this->$property = $value; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public function sum(array $properties): int |
|
||||||
{ |
|
||||||
$sum = 0; |
|
||||||
foreach ($properties as $property) { |
|
||||||
$sum += $this->$property; |
|
||||||
} |
|
||||||
return $sum; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
final class StaticPropertyEntity |
|
||||||
{ |
|
||||||
public int $first = 0; |
|
||||||
public int $second = 0; |
|
||||||
public int $third = 0; |
|
||||||
public int $fourth = 0; |
|
||||||
public int $fifth = 0; |
|
||||||
|
|
||||||
public function hydrate(array $data): void |
|
||||||
{ |
|
||||||
$this->first = $data['first']; |
|
||||||
$this->second = $data['second']; |
|
||||||
$this->third = $data['third']; |
|
||||||
$this->fourth = $data['fourth']; |
|
||||||
$this->fifth = $data['fifth']; |
|
||||||
} |
|
||||||
|
|
||||||
public function sum(): int |
|
||||||
{ |
|
||||||
return $this->first + $this->second + $this->third + $this->fourth + $this->fifth; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function runDynamicWrite(DynamicPropertyEntity $entity, array $data, int $iterations): int |
|
||||||
{ |
|
||||||
for ($i = 0; $i < $iterations; $i++) { |
|
||||||
$entity->hydrate($data); |
|
||||||
} |
|
||||||
return $entity->first; |
|
||||||
} |
|
||||||
|
|
||||||
function runStaticWrite(StaticPropertyEntity $entity, array $data, int $iterations): int |
|
||||||
{ |
|
||||||
for ($i = 0; $i < $iterations; $i++) { |
|
||||||
$entity->hydrate($data); |
|
||||||
} |
|
||||||
return $entity->first; |
|
||||||
} |
|
||||||
|
|
||||||
function runDynamicRead(DynamicPropertyEntity $entity, array $properties, int $iterations): int |
|
||||||
{ |
|
||||||
$sum = 0; |
|
||||||
for ($i = 0; $i < $iterations; $i++) { |
|
||||||
$sum += $entity->sum($properties); |
|
||||||
} |
|
||||||
return $sum; |
|
||||||
} |
|
||||||
|
|
||||||
function runStaticRead(StaticPropertyEntity $entity, int $iterations): int |
|
||||||
{ |
|
||||||
$sum = 0; |
|
||||||
for ($i = 0; $i < $iterations; $i++) { |
|
||||||
$sum += $entity->sum(); |
|
||||||
} |
|
||||||
return $sum; |
|
||||||
} |
|
||||||
|
|
||||||
function measure(callable $callback, int $operations): float |
|
||||||
{ |
|
||||||
global $benchmarkSink; |
|
||||||
for ($warmup = 0; $warmup < 3; $warmup++) { |
|
||||||
$benchmarkSink += $callback(); |
|
||||||
} |
|
||||||
|
|
||||||
$best = 1.0e30; |
|
||||||
for ($round = 0; $round < 7; $round++) { |
|
||||||
$start = hrtime(true); |
|
||||||
$result = $callback(); |
|
||||||
$elapsed = hrtime(true) - $start; |
|
||||||
$benchmarkSink += $result; |
|
||||||
if ($elapsed < $best) { |
|
||||||
$best = $elapsed; |
|
||||||
} |
|
||||||
} |
|
||||||
return $best / $operations; |
|
||||||
} |
|
||||||
|
|
||||||
function main(): void |
|
||||||
{ |
|
||||||
global $benchmarkSink; |
|
||||||
$benchmarkSink = 0; |
|
||||||
$iterations = 200000; |
|
||||||
$data = [ |
|
||||||
'first' => 1, |
|
||||||
'second' => 2, |
|
||||||
'third' => 3, |
|
||||||
'fourth' => 4, |
|
||||||
'fifth' => 5, |
|
||||||
]; |
|
||||||
$properties = ['first', 'second', 'third', 'fourth', 'fifth']; |
|
||||||
$dynamic = new DynamicPropertyEntity(); |
|
||||||
$static = new StaticPropertyEntity(); |
|
||||||
$operations = $iterations * 5; |
|
||||||
|
|
||||||
$dynamicWrite = measure( |
|
||||||
function () use ($dynamic, $data, $iterations): int { |
|
||||||
return runDynamicWrite($dynamic, $data, $iterations); |
|
||||||
}, |
|
||||||
$operations, |
|
||||||
); |
|
||||||
$staticWrite = measure( |
|
||||||
function () use ($static, $data, $iterations): int { |
|
||||||
return runStaticWrite($static, $data, $iterations); |
|
||||||
}, |
|
||||||
$operations, |
|
||||||
); |
|
||||||
$dynamicRead = measure( |
|
||||||
function () use ($dynamic, $properties, $iterations): int { |
|
||||||
return runDynamicRead($dynamic, $properties, $iterations); |
|
||||||
}, |
|
||||||
$operations, |
|
||||||
); |
|
||||||
$staticRead = measure( |
|
||||||
function () use ($static, $iterations): int { |
|
||||||
return runStaticRead($static, $iterations); |
|
||||||
}, |
|
||||||
$operations, |
|
||||||
); |
|
||||||
|
|
||||||
printf("dynamic_write_ns=%.3f\n", $dynamicWrite); |
|
||||||
printf("static_write_ns=%.3f\n", $staticWrite); |
|
||||||
printf("dynamic_read_ns=%.3f\n", $dynamicRead); |
|
||||||
printf("static_read_ns=%.3f\n", $staticRead); |
|
||||||
echo 'checksum=', $benchmarkSink + $dynamic->sum($properties) + $static->sum(), "\n"; |
|
||||||
} |
|
||||||
@ -1,7 +0,0 @@ |
|||||||
name: property_access_benchmark |
|
||||||
mode: bin |
|
||||||
optimize: 2 |
|
||||||
build-dir: build |
|
||||||
output: property_access |
|
||||||
sources: |
|
||||||
- benchmark.php |
|
||||||
@ -1,101 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
declare(strict_types=1); |
|
||||||
|
|
||||||
$root = dirname(__DIR__, 2); |
|
||||||
$source = __DIR__ . '/benchmark.php'; |
|
||||||
$project = __DIR__ . '/project.yml'; |
|
||||||
$binary = __DIR__ . '/property_access'; |
|
||||||
$skipBuild = in_array('--skip-build', $argv, true); |
|
||||||
$maximumRatio = null; |
|
||||||
foreach ($argv as $argument) { |
|
||||||
if (str_starts_with($argument, '--max-ratio=')) { |
|
||||||
$maximumRatio = (float) substr($argument, strlen('--max-ratio=')); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** @param list<string> $command */ |
|
||||||
function runCommand(array $command, string $cwd, bool $capture): string |
|
||||||
{ |
|
||||||
$stdout = $capture ? ['pipe', 'w'] : STDOUT; |
|
||||||
$stderr = $capture ? ['pipe', 'w'] : STDERR; |
|
||||||
$process = proc_open($command, [STDIN, $stdout, $stderr], $pipes, $cwd, null, ['bypass_shell' => true]); |
|
||||||
if (!is_resource($process)) { |
|
||||||
throw new RuntimeException('Failed to start: ' . implode(' ', $command)); |
|
||||||
} |
|
||||||
|
|
||||||
$output = ''; |
|
||||||
$error = ''; |
|
||||||
if ($capture) { |
|
||||||
$output = stream_get_contents($pipes[1]); |
|
||||||
$error = stream_get_contents($pipes[2]); |
|
||||||
fclose($pipes[1]); |
|
||||||
fclose($pipes[2]); |
|
||||||
} |
|
||||||
$status = proc_close($process); |
|
||||||
if ($status !== 0) { |
|
||||||
throw new RuntimeException( |
|
||||||
'Command failed (' . $status . '): ' . implode(' ', $command) . "\n" . $output . $error, |
|
||||||
); |
|
||||||
} |
|
||||||
return $output; |
|
||||||
} |
|
||||||
|
|
||||||
/** @return array<string, float> */ |
|
||||||
function parseResults(string $output): array |
|
||||||
{ |
|
||||||
$results = []; |
|
||||||
foreach (explode("\n", trim($output)) as $line) { |
|
||||||
if (!str_contains($line, '=')) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
[$name, $value] = explode('=', $line, 2); |
|
||||||
if ($name !== 'checksum') { |
|
||||||
$results[$name] = (float) $value; |
|
||||||
} |
|
||||||
} |
|
||||||
return $results; |
|
||||||
} |
|
||||||
|
|
||||||
if (!$skipBuild) { |
|
||||||
runCommand([ |
|
||||||
PHP_BINARY, |
|
||||||
$root . '/bin/tpc.php', |
|
||||||
$project, |
|
||||||
'-j', |
|
||||||
'8', |
|
||||||
'--no-color', |
|
||||||
'--no-progress', |
|
||||||
], $root, false); |
|
||||||
} |
|
||||||
if (!is_file($binary)) { |
|
||||||
throw new RuntimeException('Benchmark binary does not exist: ' . $binary); |
|
||||||
} |
|
||||||
|
|
||||||
$php = parseResults(runCommand([ |
|
||||||
PHP_BINARY, |
|
||||||
'-d', |
|
||||||
'opcache.enable_cli=0', |
|
||||||
'-r', |
|
||||||
'require ' . var_export($source, true) . '; main();', |
|
||||||
], $root, true)); |
|
||||||
$typephp = parseResults(runCommand([$binary], $root, true)); |
|
||||||
|
|
||||||
echo "Metric PHP ns/op TypePHP ns/op TypePHP/PHP\n"; |
|
||||||
echo "------------------------------------------------------------\n"; |
|
||||||
$failed = false; |
|
||||||
foreach (['dynamic_write_ns', 'dynamic_read_ns', 'static_write_ns', 'static_read_ns'] as $metric) { |
|
||||||
if (!isset($php[$metric], $typephp[$metric])) { |
|
||||||
throw new RuntimeException('Missing benchmark metric: ' . $metric); |
|
||||||
} |
|
||||||
$ratio = $typephp[$metric] / $php[$metric]; |
|
||||||
printf("%-22s %10.2f %14.2f %12.2fx\n", $metric, $php[$metric], $typephp[$metric], $ratio); |
|
||||||
if ($maximumRatio !== null && str_starts_with($metric, 'dynamic_') && $ratio > $maximumRatio) { |
|
||||||
$failed = true; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if ($failed) { |
|
||||||
fwrite(STDERR, "Dynamic property ratio exceeded --max-ratio={$maximumRatio}\n"); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
@ -1,146 +0,0 @@ |
|||||||
#!/usr/bin/env php |
|
||||||
<?php |
|
||||||
/** |
|
||||||
* This file is part of Swoole-Compiler(AOT). |
|
||||||
* |
|
||||||
* @link https://www.swoole.com/ |
|
||||||
* @contact service@swoole.com |
|
||||||
*/ |
|
||||||
|
|
||||||
declare(strict_types=1); |
|
||||||
|
|
||||||
use TypePhp\Testing\TestCoverageAnalyzer; |
|
||||||
|
|
||||||
require __DIR__ . '/bootstrap.php'; |
|
||||||
|
|
||||||
$format = 'summary'; |
|
||||||
$output = null; |
|
||||||
$includePhpUnit = true; |
|
||||||
$strict = false; |
|
||||||
$phpVersions = ['8.4', '8.5']; |
|
||||||
$paths = []; |
|
||||||
|
|
||||||
foreach (array_slice($argv, 1) as $argument) { |
|
||||||
if ($argument === '--help' || $argument === '-h') { |
|
||||||
printUsage($argv[0]); |
|
||||||
exit(0); |
|
||||||
} |
|
||||||
if ($argument === '--no-phpunit') { |
|
||||||
$includePhpUnit = false; |
|
||||||
continue; |
|
||||||
} |
|
||||||
if ($argument === '--strict') { |
|
||||||
$strict = true; |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (str_starts_with($argument, '--format=')) { |
|
||||||
$format = substr($argument, strlen('--format=')); |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (str_starts_with($argument, '--output=')) { |
|
||||||
$output = substr($argument, strlen('--output=')); |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (str_starts_with($argument, '--php-versions=')) { |
|
||||||
$phpVersions = array_values(array_filter(array_map('trim', explode(',', substr($argument, strlen('--php-versions=')))))); |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (str_starts_with($argument, '-')) { |
|
||||||
fwrite(STDERR, 'Unknown option: ' . $argument . PHP_EOL); |
|
||||||
exit(2); |
|
||||||
} |
|
||||||
$paths[] = $argument; |
|
||||||
} |
|
||||||
|
|
||||||
if (!in_array($format, ['summary', 'json', 'markdown'], true)) { |
|
||||||
fwrite(STDERR, 'Invalid format. Expected summary, json or markdown.' . PHP_EOL); |
|
||||||
exit(2); |
|
||||||
} |
|
||||||
if ($phpVersions === []) { |
|
||||||
fwrite(STDERR, 'At least one target PHP version is required.' . PHP_EOL); |
|
||||||
exit(2); |
|
||||||
} |
|
||||||
foreach ($phpVersions as $version) { |
|
||||||
if (!preg_match('/^\d+\.\d+$/', $version)) { |
|
||||||
fwrite(STDERR, 'Invalid PHP version: ' . $version . PHP_EOL); |
|
||||||
exit(2); |
|
||||||
} |
|
||||||
} |
|
||||||
if ($paths === []) { |
|
||||||
$paths = ['tests/compiler']; |
|
||||||
} |
|
||||||
|
|
||||||
try { |
|
||||||
$analyzer = new TestCoverageAnalyzer(TYPEPHP_ROOT_PATH, $phpVersions); |
|
||||||
$report = $analyzer->analyze( |
|
||||||
$paths, |
|
||||||
$includePhpUnit ? TYPEPHP_ROOT_PATH . '/phpunit/src' : null, |
|
||||||
$includePhpUnit ? TYPEPHP_ROOT_PATH . '/phpunit/code' : null, |
|
||||||
); |
|
||||||
} catch (Throwable $error) { |
|
||||||
fwrite(STDERR, 'Coverage analysis failed: ' . $error->getMessage() . PHP_EOL); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
$rendered = match ($format) { |
|
||||||
'summary' => $analyzer->renderSummary($report), |
|
||||||
'markdown' => $analyzer->renderMarkdown($report), |
|
||||||
'json' => json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . PHP_EOL, |
|
||||||
}; |
|
||||||
|
|
||||||
if ($output === null) { |
|
||||||
echo $rendered; |
|
||||||
} else { |
|
||||||
$outputPath = isAbsolutePath($output) ? $output : TYPEPHP_ROOT_PATH . DIRECTORY_SEPARATOR . $output; |
|
||||||
$directory = dirname($outputPath); |
|
||||||
if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) { |
|
||||||
fwrite(STDERR, 'Unable to create output directory: ' . $directory . PHP_EOL); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
if (file_put_contents($outputPath, $rendered) === false) { |
|
||||||
fwrite(STDERR, 'Unable to write report: ' . $outputPath . PHP_EOL); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
echo 'Wrote ', $format, ' coverage report: ', relativePath(TYPEPHP_ROOT_PATH, $outputPath), PHP_EOL; |
|
||||||
} |
|
||||||
|
|
||||||
if ($strict && ($report['parse_errors'] !== [] || $report['unresolved_phpunit_fixtures'] !== [])) { |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
function printUsage(string $script): void |
|
||||||
{ |
|
||||||
echo <<<USAGE |
|
||||||
Usage: |
|
||||||
php {$script} [options] [PHPT path ...] |
|
||||||
|
|
||||||
Options: |
|
||||||
--format=summary|json|markdown Output format (default: summary) |
|
||||||
--output=<file> Write the report to a file |
|
||||||
--php-versions=8.4,8.5 Target PHP version columns |
|
||||||
--no-phpunit Do not scan PHPUnit compiler fixtures |
|
||||||
--strict Fail on parse issues or unresolved fixture links |
|
||||||
-h, --help Show this help |
|
||||||
|
|
||||||
Examples: |
|
||||||
php {$script} |
|
||||||
php {$script} --format=markdown --output=build/test-coverage.md |
|
||||||
php {$script} --format=json tests/compiler/type_decl tests/compiler/basic |
|
||||||
|
|
||||||
The tool reports separate, explicitly denominated AST-node, positive compile, |
|
||||||
runtime semantic and negative diagnostic coverage. It never emits a combined |
|
||||||
overall percentage. |
|
||||||
|
|
||||||
USAGE; |
|
||||||
} |
|
||||||
|
|
||||||
function isAbsolutePath(string $path): bool |
|
||||||
{ |
|
||||||
return $path !== '' && ($path[0] === '/' || preg_match('/^[A-Za-z]:[\\\\\/]/', $path) === 1); |
|
||||||
} |
|
||||||
|
|
||||||
function relativePath(string $root, string $path): string |
|
||||||
{ |
|
||||||
$prefix = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
|
||||||
return str_starts_with($path, $prefix) ? substr($path, strlen($prefix)) : $path; |
|
||||||
} |
|
||||||
@ -1,17 +1,6 @@ |
|||||||
<?php |
<?php |
||||||
/** |
define("ROOT_PATH", dirname(__DIR__)); |
||||||
* This file is part of TypePHP(AOT). |
define('DEBUG', true); |
||||||
* |
|
||||||
* @link https://www.swoole.com/aot/ |
|
||||||
* @contact service@swoole.com |
|
||||||
*/ |
|
||||||
|
|
||||||
define('TYPEPHP_ROOT_PATH', dirname(__DIR__)); |
require ROOT_PATH . '/vendor/autoload.php'; |
||||||
define('TYPEPHP_DEBUG', true); |
require ROOT_PATH . '/src/functions.php'; |
||||||
|
|
||||||
// Composer bin proxies provide the consuming project's autoloader. A source |
|
||||||
// checkout and a packaged compiler keep their own autoloader below TYPEPHP_ROOT_PATH. |
|
||||||
$autoloadPath = $GLOBALS['_composer_autoload_path'] ?? TYPEPHP_ROOT_PATH . '/vendor/autoload.php'; |
|
||||||
unset($GLOBALS['_composer_autoload_path']); |
|
||||||
require $autoloadPath; |
|
||||||
unset($autoloadPath); |
|
||||||
@ -0,0 +1,42 @@ |
|||||||
|
<?php |
||||||
|
$dir = '/home/htf/workspace/c/php-8.0.12/ext/standard/'; |
||||||
|
|
||||||
|
$outDir = __DIR__ . '/../config/'; |
||||||
|
|
||||||
|
$funcs = []; |
||||||
|
$files = glob($dir . '*.c'); |
||||||
|
$index = 0; |
||||||
|
foreach ($files as $file) { |
||||||
|
$content = file_get_contents($file); |
||||||
|
preg_match_all('/PHP_FUNCTION\(([a-z0-9_]+)\)/i', $content, $match); |
||||||
|
if (empty($match[1])) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
foreach ($match[1] as $fn) { |
||||||
|
$funcs[] = $fn; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
shuffle($funcs); |
||||||
|
|
||||||
|
$_funcs = []; |
||||||
|
foreach ($funcs as $fn) { |
||||||
|
$_funcs['fn_' . random_int(100000, 999999) . str_pad($index++, 3, '0') . random_int(100, 999)] = $fn; |
||||||
|
} |
||||||
|
|
||||||
|
function dumpVar($file, $var) |
||||||
|
{ |
||||||
|
file_put_contents($file, "<?php\nreturn " . var_export($var, 1) . ";\n");
|
||||||
|
} |
||||||
|
|
||||||
|
dumpVar($outDir . 'functions.php', $_funcs); |
||||||
|
|
||||||
|
$constants = get_defined_constants(); |
||||||
|
$ignore_constants = array_flip(require __DIR__ . '/ignore_constants.php'); |
||||||
|
foreach ($constants as $k => $v) { |
||||||
|
if (isset($ignore_constants[$k])) { |
||||||
|
unset($constants[$k]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dumpVar($outDir . 'constants.php', $constants); |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
<?php |
||||||
|
return [ |
||||||
|
"PHP_SAPI", |
||||||
|
"PHP_OS", |
||||||
|
"PHP_OS_FAMILY", |
||||||
|
"PHP_ZTS", |
||||||
|
"PHP_MAXPATHLEN", |
||||||
|
"PHP_EOL", |
||||||
|
"PATH_SEPARATOR", |
||||||
|
"DIRECTORY_SEPARATOR", |
||||||
|
"PHP_BINDIR", |
||||||
|
"DEFAULT_INCLUDE_PATH", |
||||||
|
"PHP_LIBDIR", |
||||||
|
"PHP_VERSION", |
||||||
|
"PHP_VERSION_ID", |
||||||
|
'PEAR_INSTALL_DIR', |
||||||
|
'PEAR_EXTENSION_DIR', |
||||||
|
'PHP_EXTENSION_DIR', |
||||||
|
'PHP_PREFIX', |
||||||
|
'PHP_MANDIR', |
||||||
|
'PHP_DATADIR', |
||||||
|
'PHP_SYSCONFDIR', |
||||||
|
'PHP_LOCALSTATEDIR', |
||||||
|
'PHP_CONFIG_FILE_PATH', |
||||||
|
'PHP_CONFIG_FILE_SCAN_DIR', |
||||||
|
'PHP_BINARY', |
||||||
|
'LIBXML_VERSION', |
||||||
|
'LIBXML_DOTTED_VERSION', |
||||||
|
'LIBXML_LOADED_VERSION', |
||||||
|
'OPENSSL_VERSION_TEXT', |
||||||
|
'OPENSSL_VERSION_NUMBER', |
||||||
|
'OPENSSL_DEFAULT_STREAM_CIPHERS', |
||||||
|
'PCRE_VERSION', |
||||||
|
'PCRE_VERSION_MAJOR', |
||||||
|
'PCRE_VERSION_MINOR', |
||||||
|
'PCRE_JIT_SUPPORT', |
||||||
|
'STDIN', |
||||||
|
'STDOUT', |
||||||
|
'STDERR', |
||||||
|
]; |
||||||
@ -1,9 +1,9 @@ |
|||||||
#!/usr/bin/env php |
#!/usr/bin/env php |
||||||
<?php |
<?php |
||||||
require __DIR__ . '/bootstrap.php'; |
require __DIR__ . '/bootstrap.php'; |
||||||
require TYPEPHP_ROOT_PATH . '/src/polyfills.php'; |
require __DIR__ . '/../src/polyfills.php'; |
||||||
require TYPEPHP_ROOT_PATH . '/src/gen_stub.php'; |
require __DIR__ . '/../src/gen_stub.php'; |
||||||
require TYPEPHP_ROOT_PATH . '/src/compiler.php'; |
require __DIR__ . '/../src/compiler.php'; |
||||||
|
|
||||||
const TYPEPHP_PHP_SCRIPT_ENTRY = true; |
const TYPEPHP_PHP_SCRIPT_ENTRY = true; |
||||||
main($argc, $argv); |
main($argc, $argv); |
||||||
|
|||||||
@ -1,178 +0,0 @@ |
|||||||
#!/usr/bin/env bash |
|
||||||
|
|
||||||
set -euo pipefail |
|
||||||
|
|
||||||
readonly MIN_AGE_MINUTES=60 |
|
||||||
|
|
||||||
dry_run=false |
|
||||||
tmp_root=/tmp |
|
||||||
|
|
||||||
usage() { |
|
||||||
cat <<'EOF' |
|
||||||
Usage: ./cleanup-typephp-tmp.sh [options] |
|
||||||
|
|
||||||
Remove inactive TypePHP temporary files and directories from /tmp. |
|
||||||
An entry is skipped when it or any of its descendants was modified or |
|
||||||
metadata-changed during the last 60 minutes. |
|
||||||
Recognized prefixes: typephp-, typephp_, utils_test_, and phpx-windows. |
|
||||||
|
|
||||||
Options: |
|
||||||
-n, --dry-run Show what would be removed without deleting anything |
|
||||||
--tmp-dir DIR Use another temporary directory (primarily for testing) |
|
||||||
-h, --help Show this help |
|
||||||
EOF |
|
||||||
} |
|
||||||
|
|
||||||
while (($# > 0)); do |
|
||||||
case "$1" in |
|
||||||
-n | --dry-run) |
|
||||||
dry_run=true |
|
||||||
;; |
|
||||||
--tmp-dir) |
|
||||||
if (($# < 2)); then |
|
||||||
echo "Error: --tmp-dir requires a directory." >&2 |
|
||||||
exit 2 |
|
||||||
fi |
|
||||||
tmp_root=$2 |
|
||||||
shift |
|
||||||
;; |
|
||||||
-h | --help) |
|
||||||
usage |
|
||||||
exit 0 |
|
||||||
;; |
|
||||||
*) |
|
||||||
echo "Error: unknown option: $1" >&2 |
|
||||||
usage >&2 |
|
||||||
exit 2 |
|
||||||
;; |
|
||||||
esac |
|
||||||
shift |
|
||||||
done |
|
||||||
|
|
||||||
if [[ ! -d "$tmp_root" ]]; then |
|
||||||
echo "Error: temporary directory does not exist: $tmp_root" >&2 |
|
||||||
exit 1 |
|
||||||
fi |
|
||||||
|
|
||||||
tmp_root=$(realpath -e -- "$tmp_root") |
|
||||||
if [[ -z "$tmp_root" || "$tmp_root" == / ]]; then |
|
||||||
echo "Error: refusing to use an unsafe temporary directory." >&2 |
|
||||||
exit 1 |
|
||||||
fi |
|
||||||
|
|
||||||
readonly tmp_root |
|
||||||
readonly owner_uid=${SUDO_UID:-$(id -u)} |
|
||||||
|
|
||||||
format_size() { |
|
||||||
local kib=$1 |
|
||||||
awk -v kib="$kib" 'BEGIN { |
|
||||||
if (kib >= 1048576) { |
|
||||||
printf "%.2f GiB", kib / 1048576 |
|
||||||
} else if (kib >= 1024) { |
|
||||||
printf "%.2f MiB", kib / 1024 |
|
||||||
} else { |
|
||||||
printf "%d KiB", kib |
|
||||||
} |
|
||||||
}' |
|
||||||
} |
|
||||||
|
|
||||||
entry_size_kib() { |
|
||||||
local output |
|
||||||
output=$(du -sk -- "$1" 2>/dev/null) || { |
|
||||||
printf '0' |
|
||||||
return |
|
||||||
} |
|
||||||
printf '%s' "${output%%$'\t'*}" |
|
||||||
} |
|
||||||
|
|
||||||
has_recent_entry() { |
|
||||||
local candidate=$1 |
|
||||||
local recent |
|
||||||
|
|
||||||
# Check the complete tree. Looking only at the top-level directory mtime |
|
||||||
# would miss writes to an existing file in a nested build directory. |
|
||||||
if ! recent=$(find -P "$candidate" -xdev \ |
|
||||||
\( -mmin "-${MIN_AGE_MINUTES}" -o -cmin "-${MIN_AGE_MINUTES}" \) \ |
|
||||||
-printf '1' -quit 2>/dev/null); then |
|
||||||
return 0 |
|
||||||
fi |
|
||||||
|
|
||||||
[[ -n "$recent" ]] |
|
||||||
} |
|
||||||
|
|
||||||
matched_count=0 |
|
||||||
removed_count=0 |
|
||||||
skipped_recent_count=0 |
|
||||||
skipped_error_count=0 |
|
||||||
total_kib=0 |
|
||||||
|
|
||||||
while IFS= read -r -d '' candidate; do |
|
||||||
((matched_count += 1)) |
|
||||||
|
|
||||||
# Keep the target constrained to one direct child of the selected root. |
|
||||||
if [[ "$candidate" != "$tmp_root"/* || "${candidate%/*}" != "$tmp_root" ]]; then |
|
||||||
echo "[skip unsafe] $candidate" >&2 |
|
||||||
((skipped_error_count += 1)) |
|
||||||
continue |
|
||||||
fi |
|
||||||
|
|
||||||
if has_recent_entry "$candidate"; then |
|
||||||
echo "[skip recent] $candidate" |
|
||||||
((skipped_recent_count += 1)) |
|
||||||
continue |
|
||||||
fi |
|
||||||
|
|
||||||
size_kib=$(entry_size_kib "$candidate") |
|
||||||
size=$(format_size "$size_kib") |
|
||||||
|
|
||||||
# The size scan can take noticeable time for a large build tree. Recheck |
|
||||||
# freshness immediately before acting in case a compiler started using it. |
|
||||||
if has_recent_entry "$candidate"; then |
|
||||||
echo "[skip recent] $candidate" |
|
||||||
((skipped_recent_count += 1)) |
|
||||||
continue |
|
||||||
fi |
|
||||||
|
|
||||||
if $dry_run; then |
|
||||||
echo "[would remove] $size $candidate" |
|
||||||
((removed_count += 1)) |
|
||||||
((total_kib += size_kib)) |
|
||||||
continue |
|
||||||
fi |
|
||||||
|
|
||||||
if [[ -d "$candidate" && ! -L "$candidate" ]]; then |
|
||||||
if rm -rf --one-file-system -- "$candidate"; then |
|
||||||
echo "[removed] $size $candidate" |
|
||||||
((removed_count += 1)) |
|
||||||
((total_kib += size_kib)) |
|
||||||
else |
|
||||||
echo "[skip error] failed to remove: $candidate" >&2 |
|
||||||
((skipped_error_count += 1)) |
|
||||||
fi |
|
||||||
elif rm -f -- "$candidate"; then |
|
||||||
echo "[removed] $size $candidate" |
|
||||||
((removed_count += 1)) |
|
||||||
((total_kib += size_kib)) |
|
||||||
else |
|
||||||
echo "[skip error] failed to remove: $candidate" >&2 |
|
||||||
((skipped_error_count += 1)) |
|
||||||
fi |
|
||||||
done < <( |
|
||||||
find -P "$tmp_root" -mindepth 1 -maxdepth 1 -uid "$owner_uid" \ |
|
||||||
\( -name 'typephp-*' -o -name 'typephp_*' \ |
|
||||||
-o -name 'utils_test_*' -o -name 'phpx-windows*' \) -print0 |
|
||||||
) |
|
||||||
|
|
||||||
if $dry_run; then |
|
||||||
action='would remove' |
|
||||||
else |
|
||||||
action='removed' |
|
||||||
fi |
|
||||||
|
|
||||||
printf 'Summary: matched %d, %s %d (%s), skipped recent %d, errors %d.\n' \ |
|
||||||
"$matched_count" "$action" "$removed_count" "$(format_size "$total_kib")" \ |
|
||||||
"$skipped_recent_count" "$skipped_error_count" |
|
||||||
|
|
||||||
if ((skipped_error_count > 0)); then |
|
||||||
exit 1 |
|
||||||
fi |
|
||||||
@ -1,151 +0,0 @@ |
|||||||
# Bash completion for the TypePHP compiler. |
|
||||||
# Generated by: tpc --generate-completion=bash |
|
||||||
|
|
||||||
_typephp_tpc_complete_files() |
|
||||||
{ |
|
||||||
local candidate |
|
||||||
COMPREPLY=() |
|
||||||
while IFS= read -r candidate; do |
|
||||||
if [[ -d "$candidate" || "$candidate" == *.php || "$candidate" == *.yml || "$candidate" == *.yaml || "$candidate" == *.prof ]]; then |
|
||||||
COMPREPLY+=("$candidate") |
|
||||||
fi |
|
||||||
done < <(compgen -f -- "$1") |
|
||||||
} |
|
||||||
|
|
||||||
_typephp_tpc_complete_python_files() |
|
||||||
{ |
|
||||||
local candidate |
|
||||||
COMPREPLY=() |
|
||||||
while IFS= read -r candidate; do |
|
||||||
if [[ -d "$candidate" || "$candidate" == *.py ]]; then |
|
||||||
COMPREPLY+=("$candidate") |
|
||||||
fi |
|
||||||
done < <(compgen -f -- "$1") |
|
||||||
} |
|
||||||
|
|
||||||
_typephp_tpc_complete_paths() |
|
||||||
{ |
|
||||||
local mode="$1" candidate |
|
||||||
COMPREPLY=() |
|
||||||
shift |
|
||||||
while IFS= read -r candidate; do |
|
||||||
COMPREPLY+=("$candidate") |
|
||||||
done < <(compgen "$mode" -- "$1") |
|
||||||
} |
|
||||||
|
|
||||||
_typephp_tpc() |
|
||||||
{ |
|
||||||
local current previous value candidate |
|
||||||
current="${COMP_WORDS[COMP_CWORD]}" |
|
||||||
previous="" |
|
||||||
if (( COMP_CWORD > 0 )); then |
|
||||||
previous="${COMP_WORDS[COMP_CWORD - 1]}" |
|
||||||
fi |
|
||||||
|
|
||||||
local index |
|
||||||
for ((index = 1; index < COMP_CWORD; index++)); do |
|
||||||
if [[ "${COMP_WORDS[index]}" == -- ]]; then |
|
||||||
compopt -o default |
|
||||||
return |
|
||||||
fi |
|
||||||
done |
|
||||||
|
|
||||||
case "$previous" in |
|
||||||
-O) |
|
||||||
COMPREPLY=( $(compgen -W '0 1 2 3' -- "$current") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
--optimize) |
|
||||||
COMPREPLY=( $(compgen -W '0 1 2 3' -- "$current") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
-m) |
|
||||||
COMPREPLY=( $(compgen -W 'bin lib ext' -- "$current") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
--mode) |
|
||||||
COMPREPLY=( $(compgen -W 'bin lib ext' -- "$current") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
--php-version) |
|
||||||
COMPREPLY=( $(compgen -W '8.4 8.5' -- "$current") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
--cxx-std) |
|
||||||
COMPREPLY=( $(compgen -W 'c++17 c++20 c++23' -- "$current") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
--sanitize) |
|
||||||
COMPREPLY=( $(compgen -W 'address undefined' -- "$current") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
--build-dir|--output-dir|-I|--include-path|-L|--link-path) |
|
||||||
compopt -o filenames |
|
||||||
_typephp_tpc_complete_paths -d "$current" |
|
||||||
return |
|
||||||
;; |
|
||||||
--convert-python-to-php) |
|
||||||
compopt -o filenames |
|
||||||
_typephp_tpc_complete_python_files "$current" |
|
||||||
return |
|
||||||
;; |
|
||||||
-o|--output) |
|
||||||
compopt -o filenames |
|
||||||
_typephp_tpc_complete_paths -f "$current" |
|
||||||
return |
|
||||||
;; |
|
||||||
esac |
|
||||||
|
|
||||||
case "$current" in |
|
||||||
-O[0-3]) |
|
||||||
COMPREPLY=("$current") |
|
||||||
return |
|
||||||
;; |
|
||||||
-O*) |
|
||||||
value="${current#-O}" |
|
||||||
COMPREPLY=( $(compgen -W '0 1 2 3' -P '-O' -- "$value") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
--wasm=*) |
|
||||||
value="${current#--wasm=}" |
|
||||||
COMPREPLY=( $(compgen -W 'component browser' -P '--wasm=' -- "$value") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
--generate-completion=*) |
|
||||||
value="${current#--generate-completion=}" |
|
||||||
COMPREPLY=( $(compgen -W 'bash' -P '--generate-completion=' -- "$value") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
--build-dir=*) |
|
||||||
value="${current#--build-dir=}" |
|
||||||
compopt -o filenames |
|
||||||
COMPREPLY=() |
|
||||||
while IFS= read -r candidate; do |
|
||||||
COMPREPLY+=("--build-dir=${candidate}") |
|
||||||
done < <(compgen -d -- "$value") |
|
||||||
return |
|
||||||
;; |
|
||||||
--output-dir=*) |
|
||||||
value="${current#--output-dir=}" |
|
||||||
compopt -o filenames |
|
||||||
COMPREPLY=() |
|
||||||
while IFS= read -r candidate; do |
|
||||||
COMPREPLY+=("--output-dir=${candidate}") |
|
||||||
done < <(compgen -d -- "$value") |
|
||||||
return |
|
||||||
;; |
|
||||||
-* ) |
|
||||||
COMPREPLY=( $(compgen -W '-O --optimize -o --output -h --help -v --version --profile --no-literal-strings --php-version -f --force -m --mode -r --run --debug -j --job --no-console --sanitize --cxx-std --march --target-platform --no-color --build-dir --dry -I --include-path -D --define --no-progress --lto --format -l --link-lib -L --link-path --wasm --wasm= --gen-python-helper --convert-python-to-php --output-dir --output-dir= --build-dir= --generate-completion=' -- "$current") ) |
|
||||||
return |
|
||||||
;; |
|
||||||
esac |
|
||||||
|
|
||||||
compopt -o filenames |
|
||||||
_typephp_tpc_complete_files "$current" |
|
||||||
} |
|
||||||
|
|
||||||
complete -F _typephp_tpc tpc |
|
||||||
complete -F _typephp_tpc ./tpc |
|
||||||
complete -F _typephp_tpc tpc.php |
|
||||||
complete -F _typephp_tpc bin/tpc.php |
|
||||||
complete -F _typephp_tpc ./bin/tpc.php |
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue