Compare commits
193 Commits
speed_buil
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
c9d9f58de0 | 35 minutes ago |
|
|
8918fae3ba | 37 minutes ago |
|
|
ec251655ba | 53 minutes ago |
|
|
0f335a8135 | 1 hour ago |
|
|
d321666721 | 1 hour ago |
|
|
98321138ce | 2 hours ago |
|
|
f2b0e9add9 | 2 hours ago |
|
|
4a9b918e7c | 2 hours ago |
|
|
1b92ae6e7b | 2 hours ago |
|
|
6e593b3d46 | 3 hours ago |
|
|
af2b03c111 | 5 hours ago |
|
|
8ddd04e1b1 | 21 hours ago |
|
|
ee1d6e7b54 | 22 hours ago |
|
|
fef462af33 | 23 hours ago |
|
|
875f123403 | 23 hours ago |
|
|
ed27572ca1 | 1 day ago |
|
|
3d2933ec7f | 1 day ago |
|
|
87c328b3db | 1 day ago |
|
|
063476360b | 1 day ago |
|
|
022ca55ff0 | 1 day ago |
|
|
ced2ebb1ba | 1 day ago |
|
|
7973f99f47 | 1 day ago |
|
|
74274f8328 | 2 days ago |
|
|
6693d95cd6 | 2 days ago |
|
|
8f32497de7 | 2 days ago |
|
|
1b235d0769 | 2 days ago |
|
|
8c6ea1d14e | 2 days ago |
|
|
0ac6dfe499 | 2 days ago |
|
|
a6cc11c6d7 | 2 days ago |
|
|
2d14c9e432 | 2 days ago |
|
|
7f587749f1 | 2 days ago |
|
|
821f639e8f | 2 days ago |
|
|
983333dc2e | 2 days ago |
|
|
978e553480 | 2 days ago |
|
|
e4817819dc | 2 days ago |
|
|
8a30c5e58d | 2 days ago |
|
|
0061d1595a | 2 days ago |
|
|
2c74be470e | 2 days ago |
|
|
2986f3db70 | 2 days ago |
|
|
e4bb7b2605 | 2 days ago |
|
|
305d0d09d9 | 2 days ago |
|
|
a5700d24c9 | 2 days ago |
|
|
642e9a7e4b | 2 days ago |
|
|
422bdc4ce6 | 2 days ago |
|
|
208541d00b | 2 days ago |
|
|
19a570ff3f | 2 days ago |
|
|
178636a577 | 2 days ago |
|
|
1118c1e8ec | 2 days ago |
|
|
02fc0fb319 | 2 days ago |
|
|
3def25cc3c | 2 days ago |
|
|
44095252d2 | 3 days ago |
|
|
9f1cf07511 | 3 days ago |
|
|
36fd0228f0 | 3 days ago |
|
|
4c59d9a21b | 3 days ago |
|
|
b2246cd456 | 3 days ago |
|
|
795a8e0a1d | 3 days ago |
|
|
9fc5755544 | 3 days ago |
|
|
6dcafb652b | 3 days ago |
|
|
23a2bf44d7 | 3 days ago |
|
|
6d45c64b4d | 3 days ago |
|
|
c5f478766e | 3 days ago |
|
|
f64a1d5cb7 | 3 days ago |
|
|
00420215b3 | 3 days ago |
|
|
2471ed1db7 | 3 days ago |
|
|
0a97daa663 | 5 days ago |
|
|
4e00799c4a | 5 days ago |
|
|
41e11a66e6 | 5 days ago |
|
|
0f6cc17450 | 5 days ago |
|
|
101b9f6261 | 5 days ago |
|
|
65a44c7f65 | 6 days ago |
|
|
e722501b3d | 6 days ago |
|
|
d80f4a0d20 | 6 days ago |
|
|
4713a58b06 | 6 days ago |
|
|
cb1e12f11f | 6 days ago |
|
|
7418b6247e | 6 days ago |
|
|
65d3710a61 | 6 days ago |
|
|
70d13a3601 | 6 days ago |
|
|
4b8d0eb685 | 6 days ago |
|
|
a08f73c995 | 1 week ago |
|
|
729000fa0c | 1 week ago |
|
|
0d0e62fbc7 | 1 week ago |
|
|
ab1854beb0 | 1 week ago |
|
|
d33073c021 | 1 week ago |
|
|
68bdedaa31 | 1 week ago |
|
|
d1704b42ef | 1 week ago |
|
|
f1ef66622c | 1 week ago |
|
|
244fa4d8b2 | 1 week ago |
|
|
7fece68f12 | 1 week ago |
|
|
464de23051 | 1 week ago |
|
|
2ffd4ded19 | 1 week ago |
|
|
2e6ddf98f1 | 1 week ago |
|
|
c9666664aa | 1 week ago |
|
|
587818d5f9 | 1 week ago |
|
|
7d1a90a340 | 1 week ago |
|
|
13344ace72 | 1 week ago |
|
|
7cf265cbc7 | 1 week ago |
|
|
de8fdaf464 | 1 week ago |
|
|
d5f0a113e6 | 1 week ago |
|
|
981b8f54d7 | 2 weeks ago |
|
|
af0b6f9582 | 2 weeks ago |
|
|
2f229ee0ac | 2 weeks ago |
|
|
ea08344c92 | 2 weeks ago |
|
|
1456305ade | 2 weeks ago |
|
|
5162261c75 | 2 weeks ago |
|
|
a536172f82 | 2 weeks ago |
|
|
aaf1ec11b3 | 2 weeks ago |
|
|
e4f6475452 | 2 weeks ago |
|
|
96960e9da1 | 2 weeks ago |
|
|
bca77588b5 | 2 weeks ago |
|
|
f79d40f8ee | 2 weeks ago |
|
|
496e6a4d69 | 2 weeks ago |
|
|
a510530d9b | 2 weeks ago |
|
|
6c9adbccad | 2 weeks ago |
|
|
343084c4ba | 2 weeks ago |
|
|
af7ae9df82 | 2 weeks ago |
|
|
42e96f1103 | 2 weeks ago |
|
|
23e88efb6a | 2 weeks ago |
|
|
463b427dc6 | 2 weeks ago |
|
|
e1018ca87e | 2 weeks ago |
|
|
bd6335ec3b | 2 weeks ago |
|
|
a79610fb2c | 2 weeks ago |
|
|
5fb3fdf490 | 2 weeks ago |
|
|
2a1ca3b1a0 | 2 weeks ago |
|
|
01ea7c40be | 2 weeks ago |
|
|
8aa8cdb065 | 2 weeks ago |
|
|
6e5fadcbc6 | 2 weeks ago |
|
|
18dd9a8a32 | 2 weeks ago |
|
|
e07b97d6cf | 2 weeks ago |
|
|
8a3b5ae9a4 | 2 weeks ago |
|
|
8e48b9d7fe | 2 weeks ago |
|
|
f5ca997e65 | 2 weeks ago |
|
|
c47ef90593 | 2 weeks ago |
|
|
b77fee697e | 2 weeks ago |
|
|
7190bea4a5 | 2 weeks ago |
|
|
6fb8187b07 | 2 weeks ago |
|
|
997bbffb65 | 2 weeks ago |
|
|
8118fbd3dc | 2 weeks ago |
|
|
e5f61eabfe | 2 weeks ago |
|
|
a3ea91918d | 2 weeks ago |
|
|
3b51eaa6d0 | 2 weeks ago |
|
|
0ba5079336 | 2 weeks ago |
|
|
7d7fa75d6f | 2 weeks ago |
|
|
64afbc9656 | 2 weeks ago |
|
|
6c97b4f301 | 2 weeks ago |
|
|
f61a542255 | 2 weeks ago |
|
|
3b6a48ef74 | 2 weeks ago |
|
|
22160661a9 | 3 weeks ago |
|
|
069d3e61bb | 3 weeks ago |
|
|
2be13eefcd | 3 weeks ago |
|
|
cb03570212 | 3 weeks ago |
|
|
70927ee68a | 3 weeks ago |
|
|
a4f1188a7e | 3 weeks ago |
|
|
1072e9d1c7 | 3 weeks ago |
|
|
1847b4c926 | 3 weeks ago |
|
|
f630fcf661 | 3 weeks ago |
|
|
525a4e4412 | 3 weeks ago |
|
|
b1b40d6a09 | 3 weeks ago |
|
|
8d4801ebd2 | 3 weeks ago |
|
|
ca41af9bb5 | 3 weeks ago |
|
|
833b74b344 | 3 weeks ago |
|
|
b69582011a | 3 weeks ago |
|
|
c4b8db030d | 3 weeks ago |
|
|
7f46445a38 | 3 weeks ago |
|
|
39354af70b | 3 weeks ago |
|
|
dbd12d249d | 3 weeks ago |
|
|
1baefe166f | 3 weeks ago |
|
|
c4404c15c5 | 3 weeks ago |
|
|
abbaa11c7c | 3 weeks ago |
|
|
3d36928e8b | 3 weeks ago |
|
|
ac16f5fbac | 3 weeks ago |
|
|
9e26887c03 | 3 weeks ago |
|
|
07c7d4b84a | 3 weeks ago |
|
|
9992793a02 | 3 weeks ago |
|
|
8503c7c32c | 3 weeks ago |
|
|
cee9ee6e1b | 3 weeks ago |
|
|
c3614aa6d3 | 3 weeks ago |
|
|
580d877ecd | 3 weeks ago |
|
|
3b3711c139 | 3 weeks ago |
|
|
b71ef5ca1a | 3 weeks ago |
|
|
9d1ba7ee7a | 3 weeks ago |
|
|
8dd33c1400 | 3 weeks ago |
|
|
db4b7f9e28 | 3 weeks ago |
|
|
2310c574ab | 3 weeks ago |
|
|
8a23368cf1 | 3 weeks ago |
|
|
ec298e66fe | 3 weeks ago |
|
|
6d7e68a9ff | 3 weeks ago |
|
|
a7b16dc1be | 3 weeks ago |
|
|
c6c04d7e96 | 3 weeks ago |
|
|
78135d75ef | 3 weeks ago |
|
|
f2c9309857 | 3 weeks ago |
|
|
09b99e357d | 3 weeks ago |
|
|
560ea46692 | 3 weeks ago |
|
|
a92fc0dfd7 | 3 weeks ago |
935 changed files with 61204 additions and 12206 deletions
@ -1,179 +0,0 @@ |
|||||||
{ |
|
||||||
"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": [] |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
@ -1,43 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,19 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,26 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,39 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,47 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,56 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,38 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,46 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,46 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,54 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,37 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,38 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,44 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,47 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,41 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,48 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,37 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,51 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,47 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,58 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,38 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,42 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,46 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,43 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,55 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,36 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,37 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,35 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,32 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,66 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,48 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,55 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,41 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,56 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,53 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,68 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,44 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,50 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,37 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
<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> |
|
||||||
@ -1,38 +0,0 @@ |
|||||||
<!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> |
|
||||||
@ -1,41 +0,0 @@ |
|||||||
<blog> |
|
||||||
|
|
||||||
# 类型检查与兼容 |
|
||||||
|
|
||||||
类型检查负责回答两个问题:**「这个值在此处是什么类型?」** 与 **「把 A 赋给期望 B 的槽位是否合法?」**。TypePHP 的类型逻辑集中在 `src/TypeSystem/` 两个 trait 中,并在遍历/生成阶段被大量调用。 |
|
||||||
|
|
||||||
Sources: [src/TypeSystem/](src/TypeSystem/) |
|
||||||
|
|
||||||
## 两个核心 Trait |
|
||||||
|
|
||||||
| Trait | 职责 | |
|
||||||
|---|---| |
|
||||||
| `NativeTypeCompatibilityTrait` | 判断原生类型之间的赋值/参数兼容(含子类→基类、可空、协变等规则) | |
|
||||||
| `CompositeTypeCheckerTrait` | 处理联合类型、`mixed`、交集等复合类型的成员判定 | |
|
||||||
|
|
||||||
它们被 `CompilerBase` 通过 `use` 引入(与 Parser 各 trait 并列),为 `parseParams`、`genArgumentDeclaration` 等生成逻辑提供判定依据。 |
|
||||||
|
|
||||||
Sources: [src/CompilerBase.php](src/CompilerBase.php#L79-L80) |
|
||||||
|
|
||||||
## 检查发生的时机 |
|
||||||
|
|
||||||
1. **参数解析**:`parseParameterType()` 决定形参的 C++ 类型;不兼容的默认/传入会在此报错。 |
|
||||||
2. **返回值**:函数/方法返回类型在 `parseFunctionDecl()` 校验,未声明返回类型会 `fatalError`(要求显式声明)。 |
|
||||||
3. **属性赋值**:固定值类型属性赋值受 [native-types](native-types) 的静态类型规则约束。 |
|
||||||
4. **常量表达式**:`ConstantExpressionValidationVisitor` 在遍历链上校验编译期常量表达式的合法性。 |
|
||||||
|
|
||||||
Sources: [src/Preprocessor.php](src/Preprocessor.php#L328-L368) · [src/Preprocessor.php](src/Preprocessor.php#L497-L536) · [src/Translator.php](src/Translator.php#L2463-L2466) |
|
||||||
|
|
||||||
## 类型丢失与恢复 |
|
||||||
|
|
||||||
从数组/函数返回值取出对象时会丢失类型上下文,需用编译期函数 `objval($var, 'ClassName')` 显式恢复类信息;`refval()` / `any()` 等见 [编译期属性](compile-time-attributes)。 |
|
||||||
|
|
||||||
Sources: [docs/NATIVE_TYPES.md](docs/NATIVE_TYPES.md#L55-L70) |
|
||||||
|
|
||||||
## 相关阅读 |
|
||||||
|
|
||||||
- 类型如何映射到 C++ → [原生类型映射](native-types) |
|
||||||
- 编译期属性如何参与类型推断 → [编译期属性](compile-time-attributes) |
|
||||||
- 生成时如何消费类型 → [生成器总览](generator) |
|
||||||
|
|
||||||
</blog> |
|
||||||
File diff suppressed because one or more lines are too long
@ -1,274 +0,0 @@ |
|||||||
# 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. |
|
||||||
@ -1,325 +0,0 @@ |
|||||||
# 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). |
|
||||||
@ -0,0 +1,21 @@ |
|||||||
|
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 |
||||||
@ -0,0 +1,155 @@ |
|||||||
|
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 |
||||||
@ -1,90 +0,0 @@ |
|||||||
# 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. |
|
||||||
@ -0,0 +1,11 @@ |
|||||||
|
--- 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;
|
||||||
@ -0,0 +1,44 @@ |
|||||||
|
#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 |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
<?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 {} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
<?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'; |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
name: linux-arm64-smoke |
||||||
|
mode: bin |
||||||
|
build-dir: build |
||||||
|
output: linux_arm64_smoke |
||||||
|
cxx-std: c++17 |
||||||
|
|
||||||
|
sources: |
||||||
|
- main.php |
||||||
|
- cpp-src |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
#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 |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
<?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 {} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
<?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'; |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
name: macos-smoke |
||||||
|
mode: bin |
||||||
|
build-dir: build |
||||||
|
output: macos_smoke |
||||||
|
cxx-std: c++17 |
||||||
|
|
||||||
|
sources: |
||||||
|
- main.php |
||||||
|
- cpp-src |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
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 |
||||||
@ -0,0 +1,360 @@ |
|||||||
|
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 |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
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 |
||||||
@ -0,0 +1,141 @@ |
|||||||
|
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 |
||||||
@ -0,0 +1,449 @@ |
|||||||
|
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 |
||||||
@ -1,76 +0,0 @@ |
|||||||
# 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 的机器执行(用户原编译机)。 |
|
||||||
|
|
||||||
@ -1,154 +0,0 @@ |
|||||||
# 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 找到核心库——这是用户环境的常规布局操作。 |
|
||||||
@ -1,103 +0,0 @@ |
|||||||
# 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. |
|
||||||
@ -0,0 +1,674 @@ |
|||||||
|
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>. |
||||||
@ -0,0 +1,700 @@ |
|||||||
|
[简体中文](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/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/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/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/COMPILER_CLI.md)。 |
||||||
|
|
||||||
|
## 兼容性模型 |
||||||
|
|
||||||
|
TypePHP 会在适合 AOT 编译的范围内保持 PHP 语法和运行行为,同时有一些明确限制: |
||||||
|
|
||||||
|
- 全局作用域只允许声明,可执行语句必须位于函数或方法内; |
||||||
|
- 二进制模式对 `main()` 使用严格签名; |
||||||
|
- `use native_types` 会让标量声明使用固定原生存储,之后不能改为不兼容类型; |
||||||
|
- 静态可确定的调用和属性会直接编译,受支持的动态操作则通过 PHPX/Zend runtime |
||||||
|
fallback 执行; |
||||||
|
- `.stub.php` 用于声明 C++ 或外部库 API,函数体必须为空,stub 文件禁止声明 |
||||||
|
`#[Native]` 类; |
||||||
|
- 部分高度动态的引用、声明、闭包和反射模式仍明确不支持。 |
||||||
|
|
||||||
|
兼容性边界属于公共契约,同时有正向和负向测试保护。请以 |
||||||
|
[不兼容 PHP 特性清单](docs/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/HIGH_PRECISION_TYPES.md)和[原生类型](docs/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/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/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/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 语言核心性能——函数调用、对象属性访问、数组/哈希访问、 |
||||||
|
字符串处理、控制流等。仓库内的测试源码为 |
||||||
|
[`examples/bench.php`](examples/bench.php)和 |
||||||
|
[`examples/micro_bench.php`](examples/micro_bench.php)。 |
||||||
|
|
||||||
|
这些数字是项目测量快照,不是性能保证。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/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/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/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/TEST_COVERAGE_ANALYZER.md)。 |
||||||
|
|
||||||
|
GitHub Actions 会在 PHP 8.4 和 8.5 上分别运行 PHPUnit 与自举 PHPT。修改编译器 |
||||||
|
内部规则或代码生成时应增加聚焦的 PHPUnit;运行输出或诊断可观察时还应增加 PHPT。 |
||||||
|
|
||||||
|
## 文档 |
||||||
|
|
||||||
|
- [快速入门](docs/QUICKSTART.md) —— 最小编译流程 |
||||||
|
- [编译模式](docs/COMPILATION_MODES.md) —— `bin`、`ext`、`lib` |
||||||
|
- [编译器命令行](docs/COMPILER_CLI.md) —— CLI 参数与项目配置 |
||||||
|
- [不兼容 PHP 特性清单](docs/INCOMPATIBLE_PHP_FEATURES.md) —— 当前限制 |
||||||
|
- [原生类型](docs/NATIVE_TYPES.md) —— 原生标量类型 |
||||||
|
- [高精度类型](docs/HIGH_PRECISION_TYPES.md) —— BigInt / Decimal / BigFloat |
||||||
|
- [Std 容器](docs/STD_CONTAINERS.md) —— 强类型容器 |
||||||
|
- [通用方法](docs/UNIVERSAL_METHODS.md) —— 编译期方法解析 |
||||||
|
- [编译期函数](docs/COMPILE_TIME_FUNCTIONS.md) —— `any()`、`refval()`、`objval()` 等 |
||||||
|
- [混合 C++/PHP](docs/MIXED_CPP_PHP.md) —— C++/PHP 互操作 |
||||||
|
- [`#[Immutable]`](docs/IMMUTABLE.md) —— 编译期只读契约 |
||||||
|
- [`#[ArrayDef]`](docs/ARRAY_DEF.md) —— 强类型数组属性契约 |
||||||
|
- [Property hooks](docs/PROPERTY_HOOKS.md) —— PHP 8.4 hook 降级和运行时元数据 |
||||||
|
- [对象存储模型](docs/OBJECT_STORAGE_AND_PASSING_MODELS.md) —— Zend object、Box 与 Native class 边界 |
||||||
|
- [Generator](docs/YIELD_GENERATOR.md) —— 生成器降级与生命周期 |
||||||
|
- [测试覆盖分析工具](docs/TEST_COVERAGE_ANALYZER.md) —— AST 与特性证据矩阵 |
||||||
|
- [WASI 构建](docs/WASI_BUILD.md) —— WASI 目标 |
||||||
|
|
||||||
|
## 授权协议 |
||||||
|
|
||||||
|
TypePHP 采用 [GNU General Public License v3.0](LICENSE) 授权。 |
||||||
|
|
||||||
|
## 社区 |
||||||
|
|
||||||
|
- 代码仓库:<https://github.com/swoole/typephp> |
||||||
|
- 版权所有 © 2026 上海识沃网络科技有限公司(Swoole) |
||||||
@ -1,84 +1,750 @@ |
|||||||
# 依赖 |
[English](README.md) | [简体中文](README-CN.md) |
||||||
- 编译器需要 PHP 8.4 以上版本;生成的扩展仍可面向 PHP 8.2~8.5 |
|
||||||
- 需要 GCC-9 以上版本,支持 C++17 标准 |
|
||||||
- 需要 CMake-3.24 以上版本 |
|
||||||
- 需要高精度数学库:`GMP`、`MPFR`、`libmpdec` |
|
||||||
|
|
||||||
# Composer 安装 |
<div align="center"> |
||||||
|
|
||||||
在项目中安装 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/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 |
||||||
``` |
``` |
||||||
|
|
||||||
在 TypePHP 源码仓库中则使用: |
When working inside the TypePHP source repository, use the local entry point |
||||||
|
instead: |
||||||
|
|
||||||
```bash |
```bash |
||||||
bin/tpc.php project.yml |
bin/tpc.php project.yml |
||||||
``` |
``` |
||||||
|
|
||||||
Linux 环境缺少 `libphp.so` 时,`tpc.php` 可以交互式下载 PHP 源码并自动构建,详见 [自动构建 libphp.so](docs/LIBPHP_INSTALLER.md)。 |
### From source |
||||||
|
|
||||||
```shell |
```bash |
||||||
# Ubuntu/Debian |
git clone https://github.com/swoole/typephp.git |
||||||
sudo apt install libgmp-dev libmpfr-dev libmpdec-dev |
cd typephp |
||||||
|
composer install |
||||||
|
php bin/tpc.php --help |
||||||
|
``` |
||||||
|
|
||||||
# RHEL/CentOS/Fedora |
`PHPX_HOME` may point to a separate PHPX checkout or installation. `PHP_HOME` |
||||||
sudo dnf install gmp-devel mpfr-devel libmpdec-devel |
may point to the PHP embed prefix; it must contain `bin/php-config`, PHP headers, |
||||||
|
and `lib/libphp.so` on Unix-like systems. |
||||||
|
|
||||||
# Arch Linux |
### Building `libphp.so` |
||||||
sudo pacman -S gmp mpfr mpdecimal |
|
||||||
|
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/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()); |
||||||
|
} |
||||||
``` |
``` |
||||||
|
|
||||||
> GMP 用于 `BigInt` 任意精度整数,MPFR 用于 `BigFloat` 高精度浮点数,libmpdec 用于 `Decimal` 十进制高精度小数。 |
Compile and run it: |
||||||
|
|
||||||
> 预览版目前仅支持 `Linux` 系统,建议使用 `Ubuntu 22.04` |
```bash |
||||||
|
bin/tpc.php hello.php |
||||||
|
./hello |
||||||
|
``` |
||||||
|
|
||||||
## PHP |
Example output (the exact PHP version and platform strings depend on the linked |
||||||
必须包含 embed 模块 |
runtime): |
||||||
|
|
||||||
## PHPX |
``` |
||||||
可使用 `composer install` 安装依赖。 |
Hello World! |
||||||
进入 `vendor/swoole/phpx` 目录,编译 `phpx` |
string(5) "8.x.x" |
||||||
|
string(16) "Linux ..." |
||||||
|
``` |
||||||
|
|
||||||
```shell |
> Binary mode requires a global `main()` function. It may be declared with no |
||||||
cd vendor/swoole/phpx |
> parameters, or as `main(int $argc, array $argv)` to receive command-line |
||||||
cmake . |
> arguments, and must return `void`. Top-level executable statements are not |
||||||
make -j32 |
> 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/COMPILATION_MODES.md) for details. |
||||||
```shell |
|
||||||
sudo ldconfig -p | grep php |
## 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 |
||||||
``` |
``` |
||||||
必须包含 `libphp.so` 和 `libphpx.so` |
|
||||||
|
|
||||||
若编译完成,但找不到动态链接库,需要修改 |
Paths are resolved relative to the YAML file. A source entry may be a file or |
||||||
```shell |
directory; conditional entries support `PHP_VERSION`, `PHP_VERSION_ID`, and |
||||||
vim /etc/ld.so.conf.d/swoole.conf |
`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/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/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/HIGH_PRECISION_TYPES.md) and |
||||||
|
[Native types](docs/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 |
||||||
|
echo $vector[1] . "\n"; // 2 |
||||||
|
|
||||||
|
// 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/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]; |
||||||
|
echo $arr->count() . "\n"; // count() |
||||||
|
var_dump($arr->contains(3)); // in_array() |
||||||
|
|
||||||
|
$big = std::bigInt("12345678901234567890"); |
||||||
|
echo $big->mul(2)->toString() . "\n"; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Method calls on primitives are resolved at compile time into direct C/C++ |
||||||
|
function calls — no vtable lookup, no reflection, no runtime dispatch. See |
||||||
|
[Universal methods](docs/UNIVERSAL_METHODS.md). |
||||||
|
|
||||||
|
### 5. Mixed C++ / PHP |
||||||
|
|
||||||
|
Write performance-critical kernels in C++ and call them from 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 — declares the C++ function signature |
||||||
|
function fast_sum(int $a, int $b): int {} |
||||||
|
``` |
||||||
|
|
||||||
|
```php |
||||||
|
<?php |
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
echo fast_sum(3, 4) . "\n"; // 7 |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
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 |
||||||
|
functions provide type metadata only and must not contain an implementation. |
||||||
|
|
||||||
|
See [Mixed C++/PHP](docs/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 [`examples/bench.php`](examples/bench.php) and |
||||||
|
[`examples/micro_bench.php`](examples/micro_bench.php). |
||||||
|
|
||||||
|
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/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 |
|
||||||
/opt/php-8.4/lib/ |
Key options: |
||||||
|
|
||||||
|
| 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/COMPILER_CLI.md) for details, including Bash completion: |
||||||
|
|
||||||
|
```bash |
||||||
|
source <(./tpc --generate-completion=bash) |
||||||
``` |
``` |
||||||
|
|
||||||
## Release packaging |
## Troubleshooting |
||||||
|
|
||||||
|
- **`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 |
||||||
|
|
||||||
Use the same PHP entry point on Windows, Linux, and macOS: |
TypePHP ships a Python tool submodule that shares the `tpc` entry point: |
||||||
|
|
||||||
```shell |
```shell |
||||||
php package.php |
# Generate IDE helpers for Python modules |
||||||
|
./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/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 |
||||||
``` |
``` |
||||||
|
|
||||||
Windows packaging requires `PHP_HOME` and `PHPX_HOME`; Linux packaging requires |
Static analysis and the source-derived coverage matrix are separate checks: |
||||||
UPX; macOS uses `strip` when available. TypePHP rejects 32-bit targets and |
|
||||||
supports common 64-bit CPU architectures, including x86-64 and ARM64. |
```bash |
||||||
|
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/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/QUICKSTART.md) — minimal compilation flow |
||||||
|
- [Compilation modes](docs/COMPILATION_MODES.md) — `bin`, `ext`, `lib` |
||||||
|
- [Compiler CLI](docs/COMPILER_CLI.md) — CLI arguments and project config |
||||||
|
- [Incompatible PHP features](docs/INCOMPATIBLE_PHP_FEATURES.md) — current limits |
||||||
|
- [Native types](docs/NATIVE_TYPES.md) — native scalar types |
||||||
|
- [High-precision types](docs/HIGH_PRECISION_TYPES.md) — BigInt / Decimal / BigFloat |
||||||
|
- [Std containers](docs/STD_CONTAINERS.md) — strongly-typed containers |
||||||
|
- [Universal methods](docs/UNIVERSAL_METHODS.md) — compile-time method resolution |
||||||
|
- [Compile-time functions](docs/COMPILE_TIME_FUNCTIONS.md) — `any()`, `refval()`, `objval()`, … |
||||||
|
- [Mixed C++/PHP](docs/MIXED_CPP_PHP.md) — C++/PHP interop |
||||||
|
- [`#[Immutable]`](docs/IMMUTABLE.md) — compile-time read-only contracts |
||||||
|
- [`#[ArrayDef]`](docs/ARRAY_DEF.md) — typed array-property contracts |
||||||
|
- [Property hooks](docs/PROPERTY_HOOKS.md) — PHP 8.4 hook lowering and runtime metadata |
||||||
|
- [Object storage models](docs/OBJECT_STORAGE_AND_PASSING_MODELS.md) — Zend object, Box, and Native class boundaries |
||||||
|
- [Generators](docs/YIELD_GENERATOR.md) — generator lowering and lifecycle |
||||||
|
- [Test coverage analyzer](docs/TEST_COVERAGE_ANALYZER.md) — AST and feature evidence matrix |
||||||
|
- [WASI build](docs/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) |
||||||
|
|||||||
@ -1,52 +0,0 @@ |
|||||||
<?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> |
|
||||||
@ -1,495 +0,0 @@ |
|||||||
#!/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() |
|
||||||
@ -0,0 +1,146 @@ |
|||||||
|
#!/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,6 +1,17 @@ |
|||||||
<?php |
<?php |
||||||
define("ROOT_PATH", dirname(__DIR__)); |
/** |
||||||
define('DEBUG', true); |
* This file is part of TypePHP(AOT). |
||||||
|
* |
||||||
|
* @link https://www.swoole.com/aot/ |
||||||
|
* @contact service@swoole.com |
||||||
|
*/ |
||||||
|
|
||||||
require ROOT_PATH . '/vendor/autoload.php'; |
define('TYPEPHP_ROOT_PATH', dirname(__DIR__)); |
||||||
require ROOT_PATH . '/src/functions.php'; |
define('TYPEPHP_DEBUG', true); |
||||||
|
|
||||||
|
// 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); |
||||||
|
|||||||
@ -1,9 +1,9 @@ |
|||||||
#!/usr/bin/env php |
#!/usr/bin/env php |
||||||
<?php |
<?php |
||||||
require __DIR__ . '/bootstrap.php'; |
require __DIR__ . '/bootstrap.php'; |
||||||
require __DIR__ . '/../src/polyfills.php'; |
require TYPEPHP_ROOT_PATH . '/src/polyfills.php'; |
||||||
require __DIR__ . '/../src/gen_stub.php'; |
require TYPEPHP_ROOT_PATH . '/src/gen_stub.php'; |
||||||
require __DIR__ . '/../src/compiler.php'; |
require TYPEPHP_ROOT_PATH . '/src/compiler.php'; |
||||||
|
|
||||||
const TYPEPHP_PHP_SCRIPT_ENTRY = true; |
const TYPEPHP_PHP_SCRIPT_ENTRY = true; |
||||||
main($argc, $argv); |
main($argc, $argv); |
||||||
|
|||||||
@ -0,0 +1,178 @@ |
|||||||
|
#!/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 |
||||||
@ -0,0 +1,151 @@ |
|||||||
|
# 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 |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
# `#[ArrayDef]` compile-time array contracts |
||||||
|
|
||||||
|
`#[ArrayDef]` attaches key/value type information to a property declared |
||||||
|
exactly as `array`. It supports both Zend classes and `#[Native]` classes and |
||||||
|
has no runtime metadata or per-read overhead. |
||||||
|
|
||||||
|
```php |
||||||
|
class Index |
||||||
|
{ |
||||||
|
#[ArrayDef(Type::String)] |
||||||
|
public array $names = []; // list<string> |
||||||
|
|
||||||
|
#[ArrayDef(Type::Int, Type::String)] |
||||||
|
public array $labels = []; // map<int, string> |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
One argument defines a list value type. Two arguments define a map key type |
||||||
|
and value type. Map keys are restricted to `Type::Int` or `Type::String`. |
||||||
|
`ClassName::class` is therefore valid only as a list element type or as the |
||||||
|
second (value) argument of a map. |
||||||
|
|
||||||
|
For direct writes whose expression types are known, the compiler either emits |
||||||
|
the normal write unchanged or reports a fatal type error. An `any` key/value is |
||||||
|
checked with PHPX exact-type helpers at runtime. No coercive `intval()` or |
||||||
|
string conversion is performed. |
||||||
|
|
||||||
|
List writes support `[]` and non-negative integer indexes up to PHP's current |
||||||
|
append position. Indexed writes uniformly emit `php::safeArrayIndex(index, |
||||||
|
array)`. The helper uses `zend_hash_next_free_element()` and follows the |
||||||
|
initial-index rule of `zend_hash_next_index_insert()`, which remains correct |
||||||
|
when `unset()` has created holes or removed the highest numeric key. An index |
||||||
|
equal to that value behaves like the next `$array[]` append; earlier indexes |
||||||
|
may update or refill an element. Negative indexes and indexes beyond the append |
||||||
|
position fail at runtime. There is no AST special case for |
||||||
|
`property[count(property)]`. Maps do not support `[]` append writes. |
||||||
|
|
||||||
|
The contract intentionally applies only to direct element assignment lowered |
||||||
|
by TypePHP. Reads and in-place operators are unchanged. Values passed through |
||||||
|
dynamic functions, callbacks, Reflection, `eval()`, or other ZendVM escape |
||||||
|
paths are outside the contract and have undefined behavior from ArrayDef's |
||||||
|
perspective. |
||||||
@ -1,224 +0,0 @@ |
|||||||
# TypePHP 编译实现分析、提速方案与 mago 替代评估 |
|
||||||
|
|
||||||
> 生成日期:2026-08-03 |
|
||||||
> 适用范围:`swoole/typephp` v0.4.3(本仓库 `D:\git\php\aot-compiler`) |
|
||||||
> 说明:所有耗时数字均来自本仓库实际基准测试(`src/` 下 139 个 PHP 文件、约 1.65 MB 源码,PHP 8.4.16 CLI;端到端对比用 302 文件合成项目,Windows + MSVC) |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## 0. 实施状态(2026-08-03 更新) |
|
||||||
|
|
||||||
**S1(合并两次解析)与 S2(writeFile 内容比对 + 生成 .cc 增量缓存)已实施并验证。** |
|
||||||
|
|
||||||
### 已落地的改动 |
|
||||||
|
|
||||||
| 文件 | 改动 | |
|
||||||
|---|---| |
|
||||||
| `src/CompilerBase.php` | 新增 `parseCachedAst()` / `cloneAst()`(AST 内存缓存 + CloningVisitor 深拷贝);`writeFile()` 增加内容比对(内容相同不覆盖,返回 bool 保持 mtime 稳定) | |
|
||||||
| `src/Preprocessor.php` | `prepareFile()` 改用 `parseCachedAst()`,消除与 convert 的重复 parse | |
|
||||||
| `src/Translator.php` | `doConvert()` 改用 `parseCachedAst()`;新增 `hasGeneratedObjectFileCache()` / `getGeneratedObjectCacheKey()` / `getGeneratedHeaderDependencies()` / `isGeneratedSourceFile()`;`compileFile()` 对生成的 .cc 走对象缓存;`save()`/`genExtension()` 仅在实际写入时 format | |
|
||||||
|
|
||||||
### 实测收益(302 文件合成项目,dry 模式,3 次取均值) |
|
||||||
|
|
||||||
| 场景 | 基线 | 改动后 | 提升 | |
|
||||||
|---|---|---|---| |
|
||||||
| 全量构建(冷) | 6011 ms | 5629 ms | **-6.4%** | |
|
||||||
| 增量构建(第 3 次,不清理 build) | — | 5023 ms | **-16.4%**(相对基线冷构建) | |
|
||||||
|
|
||||||
说明:dry 模式不包含 C++ 编译,S2 对象缓存的更大收益(跳过 g++/cl 编译)需在完整 build 场景体现;dry 耗时中固定开销(CLI 启动、YAML、符号表)占比高,稀释了 S1 的解析节省。 |
|
||||||
|
|
||||||
### 验证记录 |
|
||||||
|
|
||||||
- `parseCachedAst`:同一文件 prepare+convert 各调用一次,第二次命中缓存且返回深拷贝(不共享节点、无 resolvedName 残留污染)✓ |
|
||||||
- `writeFile` 内容比对:相同内容跳过写入,mtime 保持不变 ✓ |
|
||||||
- `isGeneratedSourceFile`:buildDir 前缀 + .cc 扩展名判断,与 `getCppFile` 路径一致 ✓ |
|
||||||
- PHPUnit:Entity 目录 85 测试、FileScanner 等 110 测试通过(1 个 Windows 路径分隔符既有失败与本次无关) |
|
||||||
- `typephp-think`(554 文件):补丁后 prepare 通过,convert 阶段因「仓库源码类型系统比 v1095 正式版严格」(`Cannot re-assign`)受阻,属源码版本差异,非本次改动问题 |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## 1. 编译是如何实现的(管线剖析) |
|
||||||
|
|
||||||
### 1.1 总体流程 |
|
||||||
|
|
||||||
入口为 `bin/tpc.php` → `src/compiler.php` 的 `main()` → `src/Translator.php`(继承 `Preprocessor` → `CompilerBase`)。编译是一条四阶段流水线: |
|
||||||
|
|
||||||
``` |
|
||||||
┌────────────────────────────────────────────────────────────────────────┐ |
|
||||||
│ ① prepare() 扫描+解析+建符号表+依赖拓扑排序 │ |
|
||||||
│ src/Build/SourcePipelineTrait.php::prepare() │ |
|
||||||
│ → 每文件: Preprocessor::prepareFile() │ |
|
||||||
│ parser->parse() ←── AST 解析 #1 │ |
|
||||||
│ NodeTraverser(NameResolver + Visitor + 常量校验 + AttributeLower) │ |
|
||||||
│ 收集 symbolDeclInFile / symbolCallInFile / classMap / funcMap │ |
|
||||||
│ → getSortedFiles(): marcj/topsort 按文件间符号依赖拓扑排序 │ |
|
||||||
├────────────────────────────────────────────────────────────────────────┤ |
|
||||||
│ ② convert() 每文件: AST → C++ 源码 │ |
|
||||||
│ src/Build/SourcePipelineTrait.php::convert() │ |
|
||||||
│ → Translator::convertFile() → doConvert() │ |
|
||||||
│ loadFile() 读盘(第二次) │ |
|
||||||
│ parser->parse() ←── AST 解析 #2(同一文件解析两次!) │ |
|
||||||
│ NodeTraverser 再次遍历(与 prepare 相同的 4 个 visitor) │ |
|
||||||
│ 逐节点翻译生成 C++(CompilerBase 4000 行核心逻辑) │ |
|
||||||
│ → 生成 func_decl.h / data_decl.h / extension-<target>.cc │ |
|
||||||
├────────────────────────────────────────────────────────────────────────┤ |
|
||||||
│ ③ compile() C++ → .o(外部编译器) │ |
|
||||||
│ Translator::compile() │ |
|
||||||
│ → phpx misc 文件 + 生成的 .cc │ |
|
||||||
│ → PCH 预编译头(PrecompiledHeaderManager,内容指纹缓存) │ |
|
||||||
│ → Linux/macOS: pcntl fork 并行(默认 4 jobs,大文件优先调度) │ |
|
||||||
│ → Windows: 串行(supportsPcntlParallelCompile() = false) │ |
|
||||||
│ → phpx misc 有 .o 缓存(hasMiscObjectFileCache,命令+ABI 指纹) │ |
|
||||||
├────────────────────────────────────────────────────────────────────────┤ |
|
||||||
│ ④ build() 链接为可执行文件 / 扩展 / 库 │ |
|
||||||
│ Translator::build() → NativeBuilder::link() │ |
|
||||||
└────────────────────────────────────────────────────────────────────────┘ |
|
||||||
``` |
|
||||||
|
|
||||||
### 1.2 关键实现位置 |
|
||||||
|
|
||||||
| 职责 | 文件 | |
|
||||||
|---|---| |
|
||||||
| 四阶段编排 | `src/Build/SourcePipelineTrait.php`(prepare/convert)、`src/Translator.php`(compile/build) | |
|
||||||
| PHP→C++ 逐节点翻译 | `src/CompilerBase.php`(4133 行,核心) | |
|
||||||
| 扫描/符号表/依赖排序 | `src/Preprocessor.php` | |
|
||||||
| 编译器抽象 | `src/Backend/`(CompilerBackend → GccLikeBackend → Gcc/Clang、Msvc) | |
|
||||||
| 平台抽象 | `src/Platform/`(Linux/Macos/Windows) | |
|
||||||
| 并行编译调度 | `src/Build/NativeBuilder.php::dispatchParallel()` + `SourceCompileQueue` | |
|
||||||
| PCH 管理 | `src/Build/PrecompiledHeaderManager.php` | |
|
||||||
| 生成物落盘 | `CompilerBase::writeFile()`(无内容比对,直接覆盖) | |
|
||||||
|
|
||||||
### 1.3 当前已具备的优化 |
|
||||||
|
|
||||||
1. **PCH 预编译头**:`phpx.h` 等 8 个公共头打成 `.gch`,带 24 位 sha256 内容指纹缓存(`PrecompiledHeaderManager`)。 |
|
||||||
2. **phpx misc 对象缓存**:`phpx/src/misc/*.cc` 的 `.o` 用「编译命令 + PHP ABI」指纹缓存,避免每次全量重编 PHPX 运行时。 |
|
||||||
3. **编译并行**:Linux/macOS 用 pcntl fork 并行(`-j`,默认 4),且按「大文件优先占满槽位 + 小文件快速通道」调度。 |
|
||||||
4. **编译期常量展开、字面量字符串表**、`--dry` 只生成 C++ 等。 |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## 2. 性能瓶颈实测 |
|
||||||
|
|
||||||
### 2.1 基准方法 |
|
||||||
|
|
||||||
用 `nikic/php-parser 5.6.1`(与仓库 `composer.json` 一致)对 `src/` 全部 139 个 PHP 文件(约 1.65 MB)做多轮测量: |
|
||||||
|
|
||||||
``` |
|
||||||
parse (nikic/php-parser) ×3 1455.5 ms → 单遍 ≈ 485 ms(3.18 MB/s) |
|
||||||
parse + NameResolver ×3 1797.0 ms → 单遍 ≈ 599 ms |
|
||||||
parse+resolve+serialize (1x) 701.1 ms |
|
||||||
unserialize ×3 779.8 ms → 单遍 ≈ 260 ms |
|
||||||
parse+resolve+prettyPrint ×3 2196.0 ms |
|
||||||
serialized AST 体积 47.6 MB(源码的 28.8 倍) |
|
||||||
``` |
|
||||||
|
|
||||||
### 2.2 结论性瓶颈清单(按影响排序) |
|
||||||
|
|
||||||
| # | 瓶颈 | 证据 | 影响 | |
|
||||||
|---|---|---|---| |
|
||||||
| B1 | **每个 PHP 文件被解析两次** | `prepareFile()`(Preprocessor.php:130)与 `doConvert()`(Translator.php:2325)各自 `parser->parse()` 一次,且各自跑一遍完整 NodeTraverser | PHP 侧最大浪费:139 文件规模下纯解析+resolve ≈ 2×599ms ≈ **1.2s 起步**,大项目线性放大 | |
|
||||||
| B2 | **生成的 .cc 无增量缓存** | `compileFile()` 只对 phpx misc 走 `hasMiscObjectFileCache`;用户代码生成的 `.cc` 每次全量重编 | 改 1 个 PHP 文件也要重编全部 .cc(C++ 编译是耗时的绝对大头) | |
|
||||||
| B3 | **生成文件无内容比对** | `CompilerBase::writeFile()` 直接 `file_put_contents` 覆盖,mtime 必然变化 | 即使内容相同也触发下游重新依赖判断/重编,破坏增量可能性 | |
|
||||||
| B4 | **prepare/convert 阶段完全串行** | 两阶段都是单进程 `foreach` | 多核只用于 compile;PHP 侧解析/翻译占用的核数为 1 | |
|
||||||
| B5 | **Windows 无并行编译** | `Windows::supportsPcntlParallelCompile() = false`,直接走 `compileSourceFile()` 串行 | Windows 大项目编译时间 = 串行全量 | |
|
||||||
| B6 | **PCH/缓存指纹每次全量计算** | `PrecompiledHeaderManager::buildFingerprint()` 每次构建都递归遍历 phpx 头文件并逐个 hash(实测 131 个头文件 ≈ 42ms,PHP 侧还有 misc 缓存同样的遍历) | 每次构建的固定开销,虽不大但在累积 | |
|
||||||
| B7 | 转换阶段反复 `getType()` 字符串比较、NodeFinder 多次全树扫描(如 `findSymbolUsing`、`canOptimizeMultiReturn`) | CompilerBase/Preprocessor 中多次独立 `findInstanceOf` | 每文件多次 O(N) 全树扫描,可合并为一次遍历 | |
|
||||||
|
|
||||||
### 2.3 一个反直觉的事实 |
|
||||||
|
|
||||||
**AST 序列化缓存是划算的**:unserialize 单遍 260ms vs parse+resolve 599ms(快 2.3 倍),即使加上 serialize 成本(首轮 ~700ms/139 文件),二次构建纯 PHP 解析阶段也能从 ~1.2s 降到 ~260ms。但注意序列化体积膨胀 28.8 倍(47.6MB/1.65MB),对磁盘 I/O 有压力——更优做法是**合并两阶段解析**(见方案 S1)。 |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## 3. 提速方案(按性价比排序) |
|
||||||
|
|
||||||
### S1(P0)prepare/convert 合并为单次解析 —— 省一半 PHP 解析时间 |
|
||||||
|
|
||||||
- **问题**:同一文件 `prepare` 和 `convert` 各 parse+traverse 一次,逻辑完全重复(visitor 列表一致)。 |
|
||||||
- **做法**:让 `prepareFile()` 把 parse 好的 `$stmts`(NameResolver 等已跑完)挂到文件缓存(内存 `array<string, array<Node>>`,或按 `filemtime+内容hash` 落盘 `build/cache/ast/`),`doConvert()` 优先取缓存;取不到(如 `--force`、跨进程)才重新 parse。 |
|
||||||
- **收益**:139 文件规模 PHP 解析从 ~1.2s → ~0.6s;大项目直接减半。 |
|
||||||
- **风险**:低。`convert` 依赖的 `resolvedName` 等 attribute 由 NameResolver 在 traverse 时写入,缓存的是 traverse 之后的 AST,语义不变。需注意 `ConstantExpressionValidationVisitor` 的报错回调在 prepare 是 warning、convert 是 fatal,缓存后错误在 prepare 阶段就能暴露,行为只会更早更一致。 |
|
||||||
|
|
||||||
### S2(P0)生成 .cc 的增量对象缓存 —— 编译期最大收益 |
|
||||||
|
|
||||||
- **做法**:把 `hasMiscObjectFileCache()` 的机制推广到所有生成的 `.cc`:缓存 key = 编译命令 + PHP ABI + **生成物内容 hash(或源 PHP hash + 编译器版本)**。内容未变 → 跳过 g++/clang。 |
|
||||||
- **配套**:`writeFile()` 加「内容比对,相同则不落盘」逻辑(解决 B3),保证 mtime 稳定。 |
|
||||||
- **收益**:改 1 个文件后二次构建,C++ 编译从全量变为仅差异文件;这是肉眼可感知的最大提升。 |
|
||||||
- **风险**:中。生成 .cc 的内容由 `genFunctionDeclarations`/`genDataDeclarations` 等公共头影响——若公共头变了,所有 .cc 的 `#include` 语义可能变,但 .cc 内容 hash 不会变。**必须把公共声明头也纳入 key**(hash func_decl.h/data_decl.h/extension 文件内容),或采用「.cc 内容未变 且 公共头未变 且 .o 存在 且 命令指纹未变」四条件跳过。 |
|
||||||
|
|
||||||
### S3(P0)修复 PCH/缓存指纹的计算时机 |
|
||||||
|
|
||||||
- `buildFingerprint()` 每次构建全量扫描+hash 头文件(~42ms + misc 缓存同款遍历),应改为「mtime+size 列表指纹」先行判断,内容 hash 仅在前者变化时执行。 |
|
||||||
|
|
||||||
### S4(P1)prepare/convert 并行化 |
|
||||||
|
|
||||||
- parse+resolve 是纯函数,可用 pcntl fork 分片解析(子进程只做 parse+serialize,父进程归并反序列化),类似已有 `dispatchParallel` 的模式。转换阶段因共享状态(literalStrings、classMap 等)风险高,可先只并行「解析」,翻译仍串行。 |
|
||||||
|
|
||||||
### S5(P1)Windows 并行编译 |
|
||||||
|
|
||||||
- 两条路:(a) 给 `Msvc` 后端加 `/MP`(MSVC 原生多进程编译,单命令即可);(b) Windows 上用 `proc_open` 实现多进程调度替代 pcntl。收益对 Windows 用户显著。 |
|
||||||
|
|
||||||
### S6(P2)工具链层 |
|
||||||
|
|
||||||
- `ccache`/`sccache` 兜底(对 C++ 编译做内容缓存,配合 S2 效果更稳);链接器换 `mold`/`lld`;`-pipe`、`-fno-asynchronous-unwind-tables` 等编译 flag 微调。 |
|
||||||
|
|
||||||
### S7(P2)单次遍历替代多次 NodeFinder |
|
||||||
|
|
||||||
- `findSymbolUsing()` 等每文件多次 `findInstanceOf` 全树扫描,可合并为一次 visit 收集函数调用/类引用/常量引用三类符号,减少 O(N) 重复。 |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## 4. AST 解析能否用 mago 替代?—— 结论:不建议(现阶段不可行) |
|
||||||
|
|
||||||
### 4.1 mago 是什么 |
|
||||||
|
|
||||||
[mago](https://github.com/carthage-software/mago)(carthage-software/mago)是一个 **Rust 编写的 PHP 工具链**:lint、format、静态分析(对标 Clippy/OXC),v1.45.0(2026-07),MIT/Apache-2.0 双许可,开发活跃。其核心是自研高性能 PHP 解析器(crate:`mago-syntax` 词法/语法/遍历、`mago-names` 名称解析),官方基准:7M 行 WordPress 静态分析 1.46s(PHPStan 55.9s)、lint 0.88s、format 0.43s。**已支持 PHP 8.4 property hooks、asymmetric visibility(private(set) 等)。** |
|
||||||
|
|
||||||
### 4.2 为什么不能直接替代 |
|
||||||
|
|
||||||
| 维度 | nikic/php-parser(现状) | mago | 兼容性判断 | |
|
||||||
|---|---|---|---| |
|
||||||
| 语言/进程 | PHP 类库,进程内对象 | Rust 二进制/crate,AST 是 Rust 结构 | 无法在 PHP 进程中直接消费 | |
|
||||||
| AST 模型 | AST(抽象语法树),含 attribute 机制(`resolvedName`/`namespacedName` 等存于节点属性) | CST(具体语法树,含 trivia),`mago ast --json` 是调试输出 | 结构不同,需要完整映射层 | |
|
||||||
| 下游依赖 | TypePHP 全库 296 处 `PhpParser\*` 引用、75 个文件;依赖 `NodeTraverser`/`NodeVisitor`/`NameResolver`/`NodeFinder`/`ConstExprEvaluator`/`PrettyPrinter`/`Modifiers` 等 | 无 PHP API | 替换 = 重写编译器前端 | |
|
||||||
| 名称解析 | `NameResolver` 写入节点 attribute,后续代码生成大量读取 | `mago-names` 输出到 Rust 侧 | 需要桥接层搬运 | |
|
||||||
| 代码生成 | `PrettyPrinter` 用于 stub 生成(`LibraryImportStubGenerator`、`gen_stub.php`) | 无等价物 | 缺失 | |
|
||||||
| 错误语义 | `PhpParser\Error` + 行列号,被 `SyntaxError` 包装 | 错误模型不同 | 需要适配 | |
|
||||||
|
|
||||||
**核心矛盾**:TypePHP 不是「解析完就丢」的 linter,而是「AST 必须留在 PHP 进程内、被 4000 行 CompilerBase 逐节点翻译」的编译器。走 mago 只有两条路,都不可行: |
|
||||||
|
|
||||||
1. **mago CLI → JSON AST → PHP 重建对象**:JSON 序列化体积远大于源码(参考第 2 节:php-parser AST 序列化已膨胀 28.8 倍,mago CST 只多不少),反序列化 + 对象重建的 CPU/内存开销会**吃掉**解析省下的时间,且每文件一次进程启动(exec)成本极高;同时丢失 attribute 机制、注释/行列语义需重新映射。 |
|
||||||
2. **PHP FFI 绑定 mago-syntax crate**:需要为整个 Rust AST 写 FFI 边界 + 转 PHP 对象层,工程量接近重写前端,且 mago 的 CST 与 TypePHP 需要的「带 resolvedName 的 AST」不是一回事,仍要在 PHP 侧自建名称解析与代码生成数据模型。 |
|
||||||
|
|
||||||
### 4.3 mago 在本项目中的合理定位(可选) |
|
||||||
|
|
||||||
- **前置快速语法校验**:编译前用 `mago`(或 `mago lint --quick`)做廉价的全项目语法/明显错误预检,给出比 php-parser 更快的反馈(适合 IDE/CI 场景,不进入编译流水线核心)。 |
|
||||||
- **性能参照物**:mago 证明了「Rust 原生解析」的量级(~百 MB/s vs php-parser 3 MB/s),可作为未来「把前端重写为原生编译器(如 Rust 侧做 parse+name-resolution,产物是自研序列化 AST)」的路线参考——但那是一个全新项目,而非对现有编译器的增量改造。 |
|
||||||
- **若仍想引入原生解析**:更现实的是保留 php-parser 语义、仅加速解析环节(如 `--php-parser` 的 `PhpParser\ParserFactory` 换用 tree-sitter 绑定并在 PHP 侧重建 php-parser 节点),但收益-成本比仍不如 S1/S2。 |
|
||||||
|
|
||||||
### 4.4 结论 |
|
||||||
|
|
||||||
> **不建议用 mago 替换 nikic/php-parser**。mago 是「解析即弃」的工具链,TypePHP 是「解析后深度消费 AST」的编译器,二者 AST 模型与进程模型不兼容,桥接成本会抹平解析速度优势。 |
|
||||||
> 真正的提速路径在 **S1(两阶段合并单次解析)+ S2(生成 .cc 增量缓存 + writeFile 内容比对)+ S4/S5(并行化)**,全部基于现有架构增量改造,风险可控、收益立竿见影。 |
|
||||||
|
|
||||||
--- |
|
||||||
|
|
||||||
## 5. 落地顺序建议 |
|
||||||
|
|
||||||
``` |
|
||||||
第一阶段(P0,改动小收益大,1-2 天) |
|
||||||
1. writeFile 内容比对(S2 前置) |
|
||||||
2. prepare/convert 共享 AST(S1) |
|
||||||
3. 生成 .cc 增量对象缓存(S2,key 含公共头内容 hash) |
|
||||||
第二阶段(P1,结构性收益) |
|
||||||
4. PCH/misc 指纹轻量化(S3) |
|
||||||
5. 解析阶段 pcntl 并行(S4 解析部分) |
|
||||||
6. Windows 并行(/MP 或 proc_open)(S5) |
|
||||||
第三阶段(P2,长期) |
|
||||||
7. 单次遍历合并 NodeFinder(S7) |
|
||||||
8. ccache / mold / lld(S6) |
|
||||||
``` |
|
||||||
|
|
||||||
## 6. 附:基准脚本 |
|
||||||
|
|
||||||
分析所用基准脚本:`.workbuddy/bench_ast.php`(可重复运行:`php .workbuddy/bench_ast.php`),复现本节所有数字。 |
|
||||||
@ -0,0 +1,356 @@ |
|||||||
|
# C++ 命名空间、前缀与符号 ABI 规则 |
||||||
|
|
||||||
|
本文是 TypePHP、PHPX 以及 TypePHP 生成代码的内部 C++ 命名规范。它解决以下问题: |
||||||
|
|
||||||
|
- 区分 TypePHP 运行时逻辑、PHPX ZendAPI 封装、项目私有实现和用户 PHP 符号; |
||||||
|
- 防止框架 helper 与用户定义的 PHP 函数或类方法生成相同的 C++ 符号; |
||||||
|
- 明确哪些名称属于稳定 ABI,哪些名称仅限单个生成项目内部使用; |
||||||
|
- 为新增 helper、缓存、入口函数和生成符号提供统一的命名决策。 |
||||||
|
|
||||||
|
## 1. 总体规则 |
||||||
|
|
||||||
|
| 命名域 | 含义 | 典型形式 | 可见范围 | ABI 属性 | |
||||||
|
| --- | --- | --- | --- | --- | |
||||||
|
| `typephp_` | TypePHP 独有的运行时或编译产物支持逻辑 | `typephp_call_parent_constructor()` | TypePHP/PHPX 运行时 | 内部或显式导出 ABI | |
||||||
|
| `php::` | 对 ZendAPI、zval、HashTable、call frame 等 PHP 运行时能力的 C++ 封装 | `php::deindirect()` | PHPX C++ API | PHPX API | |
||||||
|
| `typephp_<project>` | 单个编译项目的私有 C++ 命名空间 | `namespace typephp_tpc` | 当前生成项目 | 非公共 ABI | |
||||||
|
| `php_` | 用户 PHP 函数和类方法映射后的 C++ callable 符号 | `php_app__user__save()` | 链接器可见 | TypePHP/stub callable ABI | |
||||||
|
|
||||||
|
核心约束: |
||||||
|
|
||||||
|
1. 不得新增全局的框架 `php_*` helper。 |
||||||
|
2. 与 TypePHP 无关、只是包装 ZendAPI 的能力必须放入 `namespace php`。 |
||||||
|
3. TypePHP 独有且需要跨生成文件调用的逻辑使用 `typephp_` 前缀。 |
||||||
|
4. 只服务于一个编译项目的数据和函数放入 `typephp_<project>` 命名空间。 |
||||||
|
5. 全局 `php_*` callable 名称保留给用户 PHP 声明的编译 ABI。 |
||||||
|
|
||||||
|
## 2. `typephp_`:TypePHP 独有逻辑 |
||||||
|
|
||||||
|
`typephp_` 表示该 API 的语义由 TypePHP 定义,不是 ZendAPI 的一般性 C++ 包装。常见场景包括: |
||||||
|
|
||||||
|
- TypePHP 属性读写规则; |
||||||
|
- TypePHP 构造、克隆和父方法调用链; |
||||||
|
- TypePHP 编译期 Attribute 的运行时支持; |
||||||
|
- TypePHP Native Class、Property Hook 等专属运行时逻辑; |
||||||
|
- TypePHP embed runtime 的初始化和关闭入口。 |
||||||
|
|
||||||
|
示例: |
||||||
|
|
||||||
|
```cpp |
||||||
|
typephp_call_parent_constructor(object, constructor, args); |
||||||
|
typephp_call_parent_clone(object, clone_method); |
||||||
|
typephp_install_property_handlers(class_entry, handlers); |
||||||
|
typephp_write_property_scoped(object, member, value, scope); |
||||||
|
TYPEPHP_RUNTIME_INIT(project)(argc, argv); |
||||||
|
``` |
||||||
|
|
||||||
|
### 2.1 使用边界 |
||||||
|
|
||||||
|
- 该前缀是 TypePHP 内部 C/C++ 名称空间,不代表 PHP 用户函数。 |
||||||
|
- 新增 API 时应使用完整、可识别的 snake_case 名称,不能使用含义过宽的名称,例如 `typephp_call()`。 |
||||||
|
- 仅在一个 `.cc` 文件中使用的函数还应增加 `static` 或放入匿名命名空间。 |
||||||
|
- 需要跨动态库边界时,使用对应的导出宏;不需要导出的 helper 不应扩大符号可见性。 |
||||||
|
- 不要仅因为代码位于 `typephp_helper.h` 就使用 `typephp_`;判断依据是语义是否为 TypePHP 独有。 |
||||||
|
|
||||||
|
### 2.2 正反例 |
||||||
|
|
||||||
|
```cpp |
||||||
|
// 正确:TypePHP 独有的构造链语义。 |
||||||
|
typephp_call_parent_constructor(object, constructor, args); |
||||||
|
|
||||||
|
// 错误:只是将 INDIRECT zval 物化为普通值,并非 TypePHP 独有。 |
||||||
|
typephp_deindirect(value); |
||||||
|
|
||||||
|
// 正确:通用 Zend 值包装属于 PHPX。 |
||||||
|
php::deindirect(value); |
||||||
|
``` |
||||||
|
|
||||||
|
## 3. `php::`:ZendAPI 的 C++ 封装 |
||||||
|
|
||||||
|
`namespace php` 由 PHPX 提供,用于把 Zend 的 C API、宏、裸指针和手工资源管理封装为类型安全、RAII 友好的 C++ API。 |
||||||
|
|
||||||
|
这一命名域包含两类能力: |
||||||
|
|
||||||
|
1. PHP 值和运行时对象,如 `php::Var`、`php::Str`、`php::Array`、`php::Object`; |
||||||
|
2. ZendAPI 的安全包装,如符号查询、作用域管理、值转换、对象创建和调用。 |
||||||
|
|
||||||
|
示例: |
||||||
|
|
||||||
|
```cpp |
||||||
|
php::Var value; |
||||||
|
php::Array arguments; |
||||||
|
|
||||||
|
auto plain = php::deindirect(value); |
||||||
|
auto called_ce = php::getCalledCe(this_); |
||||||
|
auto scope = php::getCallableScope(function, this_); |
||||||
|
auto create_object = php::getCreateObjectFn(class_entry); |
||||||
|
auto globals = php::globalsArray(); |
||||||
|
``` |
||||||
|
|
||||||
|
### 3.1 何时使用 `php::` |
||||||
|
|
||||||
|
满足以下条件时应放入 `namespace php`: |
||||||
|
|
||||||
|
- API 对任何 PHPX C++ 调用者都有意义; |
||||||
|
- API 的行为可以完全用 Zend/PHP 运行时语义解释; |
||||||
|
- API 不依赖 TypePHP AST、编译期 Attribute 或 TypePHP 特有语言规则; |
||||||
|
- API 的主要作用是隐藏 Zend 宏、裸 `zval *`、引用计数或异常检查。 |
||||||
|
|
||||||
|
### 3.2 禁止全局 `php_*` helper |
||||||
|
|
||||||
|
以下旧式写法是禁止的: |
||||||
|
|
||||||
|
```cpp |
||||||
|
php::Var php_deindirect(const php::Var &value); |
||||||
|
php::Str php_get_called_class(php::Object &this_); |
||||||
|
zend_class_entry *php_get_called_ce(php::Object &this_); |
||||||
|
auto php_get_create_object_fn(zend_class_entry *ce); |
||||||
|
``` |
||||||
|
|
||||||
|
它们必须写成: |
||||||
|
|
||||||
|
```cpp |
||||||
|
namespace php { |
||||||
|
|
||||||
|
Var deindirect(const Var &value); |
||||||
|
Str getCalledClass(Object &this_); |
||||||
|
zend_class_entry *getCalledCe(Object &this_); |
||||||
|
auto getCreateObjectFn(zend_class_entry *ce); |
||||||
|
|
||||||
|
} // namespace php |
||||||
|
``` |
||||||
|
|
||||||
|
原因是用户可以合法声明: |
||||||
|
|
||||||
|
```php |
||||||
|
function deindirect(mixed $value): mixed {} |
||||||
|
function get_called_ce(): string {} |
||||||
|
function get_create_object_fn(): string {} |
||||||
|
``` |
||||||
|
|
||||||
|
这些 PHP 函数会生成 `php_deindirect`、`php_get_called_ce` 和 |
||||||
|
`php_get_create_object_fn`。如果 PHPX 也在全局定义同名 helper,可能在声明、重载解析或链接阶段发生冲突。 |
||||||
|
|
||||||
|
### 3.3 命名风格 |
||||||
|
|
||||||
|
PHPX C++ API 使用现有的 camelCase 风格: |
||||||
|
|
||||||
|
```cpp |
||||||
|
php::getCalledClass(); |
||||||
|
php::getClassEntrySafe(); |
||||||
|
php::getPersistentCache(); |
||||||
|
php::stdCreateObject(); |
||||||
|
``` |
||||||
|
|
||||||
|
不要把 Zend 的 snake_case 名称机械地保留为全局 C++ 名称。底层调用可以继续使用 Zend 原始 API,例如 `zend_objects_new()`,但对生成代码暴露的包装层应使用 `php::`。 |
||||||
|
|
||||||
|
## 4. `typephp_<project>`:项目私有命名空间 |
||||||
|
|
||||||
|
每个 TypePHP 编译项目拥有独立的 C++ 命名空间: |
||||||
|
|
||||||
|
```text |
||||||
|
typephp_<target-name> |
||||||
|
``` |
||||||
|
|
||||||
|
例如项目名为 `tpc`: |
||||||
|
|
||||||
|
```cpp |
||||||
|
namespace typephp_tpc { |
||||||
|
// Project-private generated state and helpers. |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
项目名中的 `-` 和 `*` 会转换为 `_`,其余字符必须满足编译器的 target identifier 校验。由于固定带有 `typephp_` 前缀,即使项目名以数字开头,最终 C++ namespace 仍是合法标识符。 |
||||||
|
|
||||||
|
### 4.1 应放入该命名空间的内容 |
||||||
|
|
||||||
|
- literal string 表和 `get_str()`; |
||||||
|
- class/function/property cache 表及其访问函数; |
||||||
|
- 当前项目的全局变量存储; |
||||||
|
- class entry、object handler 和默认属性模板; |
||||||
|
- module entry、MINIT/RINIT/RSHUTDOWN 辅助状态; |
||||||
|
- `module_init()`、`module_clean()` 等仅在生成 extension 文件内部调用的函数; |
||||||
|
- Python module cache 等项目级生成状态。 |
||||||
|
|
||||||
|
示意: |
||||||
|
|
||||||
|
```cpp |
||||||
|
namespace typephp_demo { |
||||||
|
|
||||||
|
static php::Str literal_strings[] = { |
||||||
|
php::Str{"hello"}, |
||||||
|
}; |
||||||
|
|
||||||
|
php::Str &get_str(uint32_t index) { |
||||||
|
return literal_strings[index]; |
||||||
|
} |
||||||
|
|
||||||
|
static THREAD_LOCAL zend_class_entry *class_map[8]; |
||||||
|
|
||||||
|
zend_class_entry *get_class(int id, const php::Str &name) { |
||||||
|
// Resolve and cache a symbol owned by this project. |
||||||
|
} |
||||||
|
|
||||||
|
static void module_init() { |
||||||
|
// Initialize this project's generated state. |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace typephp_demo |
||||||
|
``` |
||||||
|
|
||||||
|
### 4.2 可见性与 ABI |
||||||
|
|
||||||
|
- `typephp_<project>` 内的名称是实现细节,不是 library stub ABI。 |
||||||
|
- 可限制为 `static` 的对象和函数应继续标记为 `static`。 |
||||||
|
- 生成头文件可以声明必须跨 translation unit 使用的项目内部 accessor,但不应暴露底层数组或缓存表。 |
||||||
|
- 外部手写 C++ 代码不得依赖 literal index、cache index 或项目内部 storage 名称。 |
||||||
|
- 不同 TypePHP 项目可以链接到同一进程,因为相同的内部短名称位于不同的项目 namespace 中。 |
||||||
|
|
||||||
|
### 4.3 作用域优先于名称拼写 |
||||||
|
|
||||||
|
项目 namespace 中仍可能出现历史生成名称,例如: |
||||||
|
|
||||||
|
```cpp |
||||||
|
typephp_demo::php_class_entry_App_User |
||||||
|
``` |
||||||
|
|
||||||
|
虽然成员名以 `php_` 开头,但完整符号位于 `typephp_demo` 中,因此它属于项目私有实现,而不是第 5 节所述的全局用户 callable ABI。新增项目内部 helper 应优先使用不带 `php_` 的短名称,例如 `get_class()`、`get_func()` 和 `get_str()`。 |
||||||
|
|
||||||
|
## 5. `php_`:用户 PHP callable 的 C++ ABI |
||||||
|
|
||||||
|
全局 `php_` 前缀用于 TypePHP 将用户声明的 PHP 函数和类方法映射为 C++ callable 符号。这套命名同时被生成代码、library stub 和外部 C++ 实现使用,因此不能随意改变。 |
||||||
|
|
||||||
|
示例: |
||||||
|
|
||||||
|
```php |
||||||
|
namespace App; |
||||||
|
|
||||||
|
function greet(string $name): string {} |
||||||
|
|
||||||
|
class User |
||||||
|
{ |
||||||
|
public function save(): bool {} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
概念上的 C++ 符号为: |
||||||
|
|
||||||
|
```cpp |
||||||
|
php::Str php_app__greet(php::Str name); |
||||||
|
php::Bool php_app__user__save(php::Object &this_); |
||||||
|
``` |
||||||
|
|
||||||
|
规则包括: |
||||||
|
|
||||||
|
- 使用 `php_` 标识“由 PHP 声明映射而来”; |
||||||
|
- PHP namespace、class 和 method/function 名经过规范化后组合; |
||||||
|
- `__` 是现有 ABI 的组合分隔符; |
||||||
|
- 实例方法的第一个参数是对象 `this_`; |
||||||
|
- stub、library 和消费方必须使用完全相同的映射规则。 |
||||||
|
|
||||||
|
### 5.1 为什么内部 helper 不能使用 `php_` |
||||||
|
|
||||||
|
`php_` 映射不是独立的保留关键字空间,而是用户 PHP 名称的机械 ABI。以下用户声明: |
||||||
|
|
||||||
|
```php |
||||||
|
function deindirect(mixed $value): mixed {} |
||||||
|
``` |
||||||
|
|
||||||
|
会自然生成: |
||||||
|
|
||||||
|
```cpp |
||||||
|
php::Var php_deindirect(php::Var value); |
||||||
|
``` |
||||||
|
|
||||||
|
因此框架若定义全局 `php_deindirect()`,就侵占了用户符号空间。正确做法是 `php::deindirect()`。 |
||||||
|
|
||||||
|
### 5.2 组合冲突 |
||||||
|
|
||||||
|
由于当前 ABI 使用 `__` 组合 PHP namespace、class 和 callable 名,下列两个 PHP 声明可能映射到同一个 C++ 符号: |
||||||
|
|
||||||
|
```php |
||||||
|
function App\user__test(): void {} |
||||||
|
|
||||||
|
namespace App; |
||||||
|
class User |
||||||
|
{ |
||||||
|
public function test(): void {} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
编译器必须在预处理阶段检测这种情况并抛出 FatalError,不能通过覆盖、链接顺序或增加运行时分派来处理。修改映射分隔规则会破坏既有 stub/ABI,因此冲突必须由用户重命名解决。 |
||||||
|
|
||||||
|
### 5.3 入口符号例外 |
||||||
|
|
||||||
|
少量 C ABI/嵌入入口由生成器固定定义,不属于普通用户 callable。例如: |
||||||
|
|
||||||
|
```cpp |
||||||
|
php_<project>_embed_get_module(); |
||||||
|
typephp_<project>_runtime_init(argc, argv); |
||||||
|
typephp_<project>_runtime_shutdown(); |
||||||
|
``` |
||||||
|
|
||||||
|
这些是 binary/library embed runtime 与当前项目 module entry 的连接点。定义和引用统一通过 |
||||||
|
`TYPEPHP_EMBED_GET_MODULE_FUNCTION()`、`TYPEPHP_RUNTIME_INIT_FUNCTION()`、 |
||||||
|
`TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION()` 及对应的符号宏生成,风格与 Zend 的 |
||||||
|
`PHP_MINIT_FUNCTION()`/`PHP_MINIT()` 一致。最终符号包含项目名,不得作为通用 helper 命名模板。 |
||||||
|
|
||||||
|
### 5.4 多扩展进程中的公共运行时 |
||||||
|
|
||||||
|
TypePHP 扩展不得分别编译或静态链接包含进程级 Zend 状态的 PHPX 实现。Reflection handler、 |
||||||
|
`FiberGenerator` class entry、作用域和 Property Hook 运行时均由共享的 `libphpx` 唯一提供: |
||||||
|
|
||||||
|
- host 模式的 extension/library 必须链接 `libphpx.so`、`libphpx.dylib` 或 `phpx.dll`,不能回退到 `libphpx.a`; |
||||||
|
- Unix PHP extension 不链接 Embed `libphp.so`,Zend/PHP 符号由加载它的 SAPI 提供; |
||||||
|
- macOS extension 使用 `-undefined dynamic_lookup` 解析宿主符号; |
||||||
|
- binary 和独立 WASI 程序仍可以静态链接,因为每个进程或 Wasm 实例只有一份运行时。 |
||||||
|
|
||||||
|
`src/core/typephp_*.cc` 只承载 TypePHP 专属的 `typephp_*` 运行时;`php::` ZendAPI 包装应放在不带 |
||||||
|
`typephp_` 前缀的 core 源文件中,例如 `src/core/scope.cc`。 |
||||||
|
|
||||||
|
## 6. 名称选择流程 |
||||||
|
|
||||||
|
新增 C++ API 时按以下顺序判断: |
||||||
|
|
||||||
|
1. **它是否是用户 PHP 函数或类方法的编译本体?** |
||||||
|
- 是:使用既有 `php_` callable ABI 生成器,禁止手写另一套映射。 |
||||||
|
2. **它是否只服务于当前一个 TypePHP 项目?** |
||||||
|
- 是:放入 `typephp_<project>`,并尽可能使用 `static` 或私有 accessor。 |
||||||
|
3. **它是否实现 TypePHP 独有语义?** |
||||||
|
- 是:使用 `typephp_` 前缀。 |
||||||
|
4. **它是否只是对 Zend/PHP 运行时能力的 C++ 封装?** |
||||||
|
- 是:放入 `namespace php`,使用 PHPX camelCase 风格。 |
||||||
|
5. **以上都不是?** |
||||||
|
- 不应随意加入 `typephp_helper.h`;应重新确认所属模块和公共 API 边界。 |
||||||
|
|
||||||
|
## 7. 代码审查清单 |
||||||
|
|
||||||
|
新增或修改生成 helper 时必须检查: |
||||||
|
|
||||||
|
- [ ] `typephp_helper.h` 中没有新增全局 `php_*` helper; |
||||||
|
- [ ] ZendAPI 包装位于 `namespace php`; |
||||||
|
- [ ] TypePHP 独有逻辑使用 `typephp_`; |
||||||
|
- [ ] 项目缓存和 storage 位于 `typephp_<project>`; |
||||||
|
- [ ] 项目私有表没有通过生成头文件直接 `extern` 暴露; |
||||||
|
- [ ] 用户 callable 仍使用统一的 `php_` ABI 生成器; |
||||||
|
- [ ] 新名称不会与用户可声明的 PHP 函数或方法发生冲突; |
||||||
|
- [ ] bin、lib、ext 和 WASM 构建使用相同的项目名推导规则; |
||||||
|
- [ ] 修改公开 callable 映射时同步评估 stub 和既有 ABI; |
||||||
|
- [ ] 至少增加一个用户同名函数的编译回归测试。 |
||||||
|
|
||||||
|
当前相关回归测试为: |
||||||
|
|
||||||
|
```text |
||||||
|
tests/compiler/basic/helper-symbol-collision.phpt |
||||||
|
``` |
||||||
|
|
||||||
|
## 8. 主要实现位置 |
||||||
|
|
||||||
|
| 责任 | 文件 | |
||||||
|
| --- | --- | |
||||||
|
| `php_` callable 前缀与组合分隔符 | `src/CompilerBase.php` | |
||||||
|
| callable 组合冲突检测 | `src/Preprocessor.php` | |
||||||
|
| `typephp_<project>` 生成及项目私有表 | `src/Translator.php` | |
||||||
|
| TypePHP extension 前缀常量 | `src/Metadata/Constants.php` | |
||||||
|
| PHPX/TypePHP helper 分类 | `vendor/swoole/phpx/include/typephp_helper.h` | |
||||||
|
| embed module accessor 拼接 | `vendor/swoole/phpx/src/misc/typephp_main.cc` | |
||||||
@ -0,0 +1,114 @@ |
|||||||
|
# `#[Immutable]` compile-time effect checking |
||||||
|
|
||||||
|
## Purpose |
||||||
|
|
||||||
|
`#[Immutable]` is a TypePHP compile-time annotation modelled after C++ `const`. |
||||||
|
It prevents accidental mutation in statically compiled code without adding a |
||||||
|
wrapper object, Zend metadata, runtime branch, or ABI change. |
||||||
|
|
||||||
|
It is intentionally a best-effort static tool rather than a security boundary. |
||||||
|
Calls whose target is deliberately made dynamic are an escape hatch and do not |
||||||
|
receive a runtime guard. |
||||||
|
|
||||||
|
## Supported targets |
||||||
|
|
||||||
|
```php |
||||||
|
#[Immutable] |
||||||
|
public function name(): string |
||||||
|
{ |
||||||
|
return $this->name; |
||||||
|
} |
||||||
|
|
||||||
|
function inspect(#[Immutable] User $user): string |
||||||
|
{ |
||||||
|
return $user->name(); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The attribute is valid on methods and on function, method, and closure |
||||||
|
parameters. On an instance method it makes `$this` immutable. On a parameter it |
||||||
|
makes the binding immutable and, when it can contain an object, treats the |
||||||
|
referenced object as immutable as well. |
||||||
|
|
||||||
|
## Rejected operations |
||||||
|
|
||||||
|
For an immutable root such as `$this` or `$user`, the compiler rejects: |
||||||
|
|
||||||
|
- assignment, destructuring, and array-element or object-property writes; |
||||||
|
- compound assignment, `++`, `--`, `unset()`, taking a reference, and |
||||||
|
`foreach (... as &$value)`; |
||||||
|
- a statically named method call unless the resolved method is also marked |
||||||
|
`#[Immutable]`; |
||||||
|
- a mutating value extension such as `$array->sort()`; read-only array/string |
||||||
|
methods remain available; |
||||||
|
- passing an object to a statically resolved parameter that is not itself |
||||||
|
`#[Immutable]`; |
||||||
|
- passing any immutable value to a mutable by-reference parameter, including |
||||||
|
extension functions such as `sort()`; |
||||||
|
- storing an immutable object identity in an object property, array, |
||||||
|
global/static variable, or returning/yielding it as a mutable value. |
||||||
|
|
||||||
|
An immutable by-reference parameter is supported. It acts like a C++ `const &`: |
||||||
|
the reference is accepted because the callee is checked against mutation. |
||||||
|
|
||||||
|
`#[MethodsFor]` follows the same contract. An object extension is callable on |
||||||
|
an immutable receiver only when its receiver parameter is marked |
||||||
|
`#[Immutable]`. |
||||||
|
|
||||||
|
## Aliases, closures, generators, and inheritance |
||||||
|
|
||||||
|
Local aliases of immutable objects remain immutable: |
||||||
|
|
||||||
|
```php |
||||||
|
$alias = $user; |
||||||
|
$alias->rename('new'); // compile-time error |
||||||
|
``` |
||||||
|
|
||||||
|
`clone` creates a distinct mutable object and therefore intentionally drops the |
||||||
|
annotation. Captured variables, arrow functions, closure `$this`, and Fiber |
||||||
|
generator bodies carry immutable metadata into their generated function |
||||||
|
contexts. |
||||||
|
|
||||||
|
An overriding class or interface method may strengthen an ordinary contract by |
||||||
|
adding `#[Immutable]`, but it cannot remove `#[Immutable]` from an inherited |
||||||
|
method or parameter. |
||||||
|
|
||||||
|
## Value versus object semantics |
||||||
|
|
||||||
|
Scalar values and PHP copy-on-write values can be read and copied normally. For |
||||||
|
example, `count($values)` and `$copy = $values` do not modify an immutable array. |
||||||
|
The compiler propagates immutability through an expression only when object |
||||||
|
identity is possible. |
||||||
|
|
||||||
|
## Explicit escape hatches |
||||||
|
|
||||||
|
The following intentionally bypass static method-effect checking: |
||||||
|
|
||||||
|
```php |
||||||
|
$method = 'rename'; |
||||||
|
$user->$method('new'); |
||||||
|
|
||||||
|
$callable = getRuntimeCallable(); |
||||||
|
$callable($user); |
||||||
|
``` |
||||||
|
|
||||||
|
The same applies to other runtime-only mechanisms that hide the target from the |
||||||
|
compiler, including reflection and dynamic ZendVM code. TypePHP neither inserts |
||||||
|
a runtime read-only proxy nor attempts to recover the escaped value later. |
||||||
|
|
||||||
|
This boundary is deliberate: `#[Immutable]` should cost nothing in generated |
||||||
|
code and should not complicate PHPX/ZendVM object semantics. |
||||||
|
|
||||||
|
## Property hooks and magic access |
||||||
|
|
||||||
|
Property-hook reads are lowered to generated method calls. Consequently, a hook |
||||||
|
used through an immutable receiver must itself carry an `#[Immutable]` method |
||||||
|
contract; otherwise the generated call is rejected. Fully dynamic magic access |
||||||
|
is covered by the same escape-hatch rule as other runtime-only behavior. |
||||||
|
|
||||||
|
## Implementation boundaries |
||||||
|
|
||||||
|
The implementation is isolated in `src/Immutable/ImmutableSupportTrait.php`. |
||||||
|
`FunctionDef` and `ArgInfo` retain only compile-time effect bits, while each |
||||||
|
`FunctionContext` stores the immutable roots and object aliases relevant to that |
||||||
|
body. Checks run during AST lowering and emit no C++ code when successful. |
||||||
@ -0,0 +1,116 @@ |
|||||||
|
# Interface Property Hooks 实现方案 |
||||||
|
|
||||||
|
本文记录 TP-AOT-010 的设计与实施计划。目标是支持 PHP 8.4 的 Interface Property Hook 契约,同时保持 TypePHP Native 调用的零成本抽象,并让 PHP 8.4 ZendVM 的 Reflection、动态类链接和继承检查获得完整元数据。 |
||||||
|
|
||||||
|
## 当前状态(2026-08-14) |
||||||
|
|
||||||
|
第一阶段已经落地:Interface 契约模型、AOT 实现检查、get/set 方向方差、PHPX 抽象 Hook 元数据、Reflection、动态 PHP 实现类及回归测试均已接通。显式 setter 参数类型仍按下文约定在编译期拒绝;完成独立写入类型模型后再开放。 |
||||||
|
|
||||||
|
## 1. 设计结论 |
||||||
|
|
||||||
|
Interface 中的 Hooked Property 只表示属性契约: |
||||||
|
|
||||||
|
```php |
||||||
|
interface Named |
||||||
|
{ |
||||||
|
public string $name { get; set; } |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
- Interface 不持有属性槽,不生成 getter/setter 实现,也不产生访问时的契约检查。 |
||||||
|
- TypePHP 在编译期验证已知 AOT 类是否满足属性的可见性、类型和 `get`/`set` 能力。 |
||||||
|
- PHP 8.4 目标在 MINIT 注册原生 Zend Hook 元数据,使 Reflection 和动态 PHP 类获得相同契约。 |
||||||
|
- TypePHP、PHPX 和最终目标运行时的最低版本均为 PHP 8.4,不提供旧版本降级路径。 |
||||||
|
|
||||||
|
## 2. 语法与诊断 |
||||||
|
|
||||||
|
支持三类契约: |
||||||
|
|
||||||
|
```php |
||||||
|
public string $readable { get; } |
||||||
|
public string $writable { set; } |
||||||
|
public string $readWrite { get; set; } |
||||||
|
``` |
||||||
|
|
||||||
|
Interface Property Hook 必须是 `public`、非 `static`、无默认值且 Hook 不得包含函数体。普通 Interface Property、`private`/`protected`、`readonly`、重复或未知 Hook,以及带实现体的 Hook 均在 TypePHP 编译期抛出 FatalError。错误信息应尽可能与 PHP 8.4 一致。 |
||||||
|
|
||||||
|
第一阶段只接收隐式 setter 参数: |
||||||
|
|
||||||
|
```php |
||||||
|
public string $name { set; } |
||||||
|
``` |
||||||
|
|
||||||
|
PHP 8.4 还允许 `set(string|Stringable $value)` 这类显式、可逆变的 setter 参数。该语法需要让编译期契约模型与 Zend Hook `arg_info` 同时保存独立于属性读取类型的写入类型;在这部分完成前,TypePHP 会给出明确的编译期错误,不生成可能错误的运行时元数据。 |
||||||
|
|
||||||
|
## 3. 编译器模型 |
||||||
|
|
||||||
|
Interface Property Hook 不应伪装成普通属性或 lowering 后的普通方法。为其建立独立契约模型,至少保存: |
||||||
|
|
||||||
|
- 属性名和声明节点; |
||||||
|
- 解析后的 TypePHP 类型与类类型; |
||||||
|
- 是否要求 `get`; |
||||||
|
- 是否要求 `set`; |
||||||
|
- 可见性及其他用于诊断的标志。 |
||||||
|
|
||||||
|
契约存放在 `InterfaceDef` 中。AST/预处理阶段只收集和验证声明,不为 Interface 分配属性槽,不运行具体类使用的 `PropertyHookLowering`,也不生成隐藏方法。 |
||||||
|
|
||||||
|
所有类型完成预处理后再执行契约链接:展开父 Interface 契约,然后检查实现类自身或父类提供的属性。普通 public backed property 同时满足读写契约;Hooked Property 根据实际 Hook 能力判断。get-only 类型按读取方向协变,set-only 类型按写入方向逆变,同时包含 get/set 时保持不变。 |
||||||
|
|
||||||
|
## 4. PHPX 与 Zend 元数据 |
||||||
|
|
||||||
|
现有 `typephp_register_property_hooks()` 用于具有真实 AOT getter/setter 的具体类,不能复用于抽象 Interface Hook。 |
||||||
|
|
||||||
|
PHPX 增加独立 helper: |
||||||
|
|
||||||
|
```cpp |
||||||
|
typephp_register_abstract_property_hooks( |
||||||
|
zend_class_entry *interface_ce, |
||||||
|
zend_property_info *property_info, |
||||||
|
bool readable, |
||||||
|
bool writable |
||||||
|
); |
||||||
|
``` |
||||||
|
|
||||||
|
TypePHP/PHPX 已统一要求 PHP 8.4+,因此该 helper 直接访问 PHP 8.4 ABI,并负责: |
||||||
|
|
||||||
|
- 持久化分配 `zend_property_info::hooks`; |
||||||
|
- 创建没有 handler 的 abstract `get`/`set` `zend_internal_function` 元数据; |
||||||
|
- 设置 `ZEND_ACC_PUBLIC | ZEND_ACC_ABSTRACT`、正确的参数/返回类型及 `common.prop_info`; |
||||||
|
- 更新 `num_hooked_props`,使 Zend inheritance 和 Reflection 识别该契约; |
||||||
|
- 保证所有字符串、Hook 表和函数描述具有 MINIT 级持久生命周期。 |
||||||
|
|
||||||
|
生成代码先注册 Interface,再以 `IS_UNDEF`、`ZEND_ACC_PUBLIC | ZEND_ACC_ABSTRACT | ZEND_ACC_VIRTUAL` 声明属性并挂载抽象 Hook,最后才注册和链接实现类。 |
||||||
|
|
||||||
|
## 5. PHP 版本边界 |
||||||
|
|
||||||
|
TypePHP 区分源码语言版本与链接运行时: |
||||||
|
|
||||||
|
- `--php-version` 只允许 `8.4` 或 `8.5`,用于解析语法和处理项目条件; |
||||||
|
- PHPX headers、`libphp` 与最终运行时必须为 PHP 8.4 或更高版本; |
||||||
|
- `--php-version` 与 `libphp.so` 的小版本不要求完全一致,例如使用 8.5 语法模式并链接 PHP 8.4 时,最终能否构建仍由实际使用的 Zend API 决定; |
||||||
|
- PHP 8.4 以下环境在 TypePHP/PHPX 构建入口直接拒绝。 |
||||||
|
|
||||||
|
## 6. TDD 覆盖 |
||||||
|
|
||||||
|
实现前先加入失败测试,覆盖: |
||||||
|
|
||||||
|
1. get-only、set-only、get/set Interface 契约; |
||||||
|
2. 普通 backed property、Hooked Property 和继承属性满足契约; |
||||||
|
3. 缺失属性、缺少 get/set、非 public 和类型不兼容的编译错误; |
||||||
|
4. Interface 继承、多个契约的合并与冲突; |
||||||
|
5. Reflection 的 abstract、virtual、hasHook/getHook 元数据; |
||||||
|
6. PHP 8.4 动态 PHP 类的成功与失败链接; |
||||||
|
7. O0/O3 结果一致,Interface 不生成属性槽或 Native Hook 实现; |
||||||
|
8. PHPX helper 在 NTS/ZTS 和 PHP 8.4/8.5 下的生命周期与 ABI 回归。 |
||||||
|
|
||||||
|
## 7. 实施顺序 |
||||||
|
|
||||||
|
1. 添加 TP-AOT-010 正常场景及语法错误 PHPT,确认当前失败。 |
||||||
|
2. 增加 Interface Property Contract 模型和预处理收集逻辑。 |
||||||
|
3. 实现 Interface 继承与实现类的编译期契约检查。 |
||||||
|
4. 在 PHPX 增加抽象 Hook 元数据 helper。 |
||||||
|
5. 修改 stub 生成和类注册顺序,接入 PHP 8.4 Zend 元数据。 |
||||||
|
6. 添加 Reflection、动态类链接、目标版本和生成代码测试。 |
||||||
|
7. 执行 Interface、Property Hook、Reflection 及全量编译器回归。 |
||||||
|
|
||||||
|
完成后的运行时属性访问仍直接进入实现类的普通属性或 Native Hook;Interface 契约本身只存在于编译期模型和 MINIT 元数据中,不进入请求热路径。 |
||||||
@ -0,0 +1,137 @@ |
|||||||
|
# Native Class 实现验收矩阵 |
||||||
|
|
||||||
|
> 审计日期:2026-08-17 |
||||||
|
> 本文记录 `#[Native]` 对象模型的需求、实现入口和直接验证证据。它是 |
||||||
|
> [NATIVE_CLASS_OBJECT.md](NATIVE_CLASS_OBJECT.md) 的实现验收附件,不替代语义设计文档。 |
||||||
|
|
||||||
|
## 1. 验收原则 |
||||||
|
|
||||||
|
每一项能力必须同时具有: |
||||||
|
|
||||||
|
1. 明确的语言边界; |
||||||
|
2. 可定位的编译器或 PHPX 实现; |
||||||
|
3. 正向 PHPT、负向 PHPUnit 或 PHPX C++ 单测中的直接证据。 |
||||||
|
|
||||||
|
仅有代码、仅有文档或“当前没有发现失败”均不视为完成。Native Object 没有 Zend |
||||||
|
表示,因此任何不能静态证明安全的跨边界行为都必须在生成 C++ 前拒绝。 |
||||||
|
|
||||||
|
## 2. 对象模型与代码生成 |
||||||
|
|
||||||
|
| 要求 | 实现证据 | 测试证据 | 结论 | |
||||||
|
|---|---|---|---| |
||||||
|
| `#[Native]` 只用于具名 class | `NativeClassAttributeLowering`、`NativeClassSupportTrait` | `testRejectsNativeAttributeOnInterface/Trait/Enum/AnonymousClass` | 已验证 | |
||||||
|
| 不注册 Zend class/object handlers | Native struct、descriptor 和自由函数生成路径 | `clone-and-zend-invisible.phpt`、Reflection 负向测试 | 已验证 | |
||||||
|
| 方法保持 `php_*` 自由函数 ABI | Native method/virtual thunk 生成路径 | `basic.phpt`、`chained-call.phpt` | 已验证 | |
||||||
|
| 静态可解析的 `new NativeClass()` 使用 Native Heap | `CompilerBase::parseNew()`、`php::nativeConstruct()` | `basic.phpt`、`construction-gc-roots.phpt` | 已验证 | |
||||||
|
| `new (表达式)()` 保持普通 PHP 动态实例化 | `parseNew()` 只对 `Node\\Name` 进入 Native 分支 | `testLeavesDynamicClassExpressionsToTheOrdinaryPhpPath` | 已验证 | |
||||||
|
| Native 对象本身不能充当动态 class target | `assertNotNativeObjectDynamicClassTarget()` | dynamic new/static call/class constant 负向测试 | 已验证 | |
||||||
|
| 所有不支持的用法在编译期终止 | Native 边界检查、类型兼容检查 | 131 项 `NativeClassValidationTest` | 已验证 | |
||||||
|
|
||||||
|
## 3. 属性与固定布局 |
||||||
|
|
||||||
|
| 要求 | 实现证据 | 测试证据 | 结论 | |
||||||
|
|---|---|---|---| |
||||||
|
| 所有属性必须声明类型 | Native field validation | `testRejectsUntypedProperty` | 已验证 | |
||||||
|
| bool/int/float 使用固定值字段 | Native field C++ type mapping | `basic.phpt`、`numeric-properties.phpt` | 已验证 | |
||||||
|
| string/array/object/typed object/Stream/mixed 可作为字段 | Native PHPX field mapping、写入检查 | `phpx-properties.phpt`、`stream-property.phpt`、`composite-property-types.phpt` | 已验证 | |
||||||
|
| BigInt/BigFloat/Decimal 可作为字段 | 高精度字段映射与 trace/destroy | `high-precision-properties.phpt` | 已验证 | |
||||||
|
| Native 类型字段保存裸指针,可形成循环类型 | struct 前置声明、descriptor trace | `mutual-reference-types.phpt`、`gc-cycle.phpt` | 已验证 | |
||||||
|
| 未显式初始化字段使用确定零值 | Native field initializer | `zero-values.phpt` | 已验证 | |
||||||
|
| 属性写入保持声明类型 | Native property assignment validation | composite、stream 及多项负向 PHPUnit | 已验证 | |
||||||
|
| 仅 `any` 属性允许取 PHP 引用 | Native property reference lowering | `any-property-reference.phpt` 及 mixed/fixed property 负向测试 | 已验证 | |
||||||
|
| Native 属性不支持 `unset()` | property unset validator | `testRejectsUnsetOnNativeObjectProperties` | 已验证 | |
||||||
|
| readonly 属性不支持 | Native declaration validator | `testRejectsReadonlyPropertyUntilNativeWriteStateIsImplemented` | 已验证 | |
||||||
|
| Box/Std Container 不能嵌入字段 | Native field validator | Box/Std Container property 负向测试 | 已验证 | |
||||||
|
|
||||||
|
## 4. 身份、空值与调用 ABI |
||||||
|
|
||||||
|
| 要求 | 实现证据 | 测试证据 | 结论 | |
||||||
|
|---|---|---|---| |
||||||
|
| `$a = $b` 只复制指针并共享对象身份 | Native pointer local representation | `parameter-semantics.phpt` | 已验证 | |
||||||
|
| Native 参数和返回必须显式声明具体类 | call argument/return boundary validation | untyped/mixed/interface 参数与返回负向测试 | 已验证 | |
||||||
|
| 普通 Native 参数非空,`?Class` 才可为空 | function entry/return checks | `non-null-parameter.phpt`、`nullable-signatures.phpt`、`return-nullability.phpt` | 已验证 | |
||||||
|
| Native 参数、返回和变量禁止 `&` | reference boundary validation | reference parameter/return/assignment/function/method 负向测试 | 已验证 | |
||||||
|
| Native variadic、union/intersection signature 不支持 | signature validation | variadic/union/null-union 负向测试 | 已验证 | |
||||||
|
| `unset($object)`/`$object = null` 只清当前 pointer slot | Native root slot lowering | `unset-alias.phpt` | 已验证 | |
||||||
|
| `===`/`!==` 与 `match` 使用指针身份 | Native identity lowering | `strict-identity.phpt`、`match-identity.phpt` | 已验证 | |
||||||
|
| ternary/match/coalesce 为兄弟子类选择最近公共 Native 基类 | `getCommonNativeObjectClass()`、selection pointer cast | `value-selection.phpt`、跨文件 global discovery 测试 | 已验证 | |
||||||
|
| 条件表达式检查非空指针,不调用 `toBool()` | Native condition lowering | `conditions.phpt` | 已验证 | |
||||||
|
| 松散比较、算术、位运算、增减、复合写入和 switch 禁止 | operator validators | 对应 PHPUnit 负向测试 | 已验证 | |
||||||
|
| `isset`/`empty`/`is_null`/nullsafe 保持 typed pointer | Native selection/nullsafe lowering | `isset-empty.phpt`、`is-null.phpt`、`nullsafe.phpt` | 已验证 | |
||||||
|
| 调用参数严格从左到右求值并在 safe point 精确 rooting | Native call argument materialization | `call-argument-roots.phpt`、`constructor-argument-roots.phpt` | 已验证 | |
||||||
|
|
||||||
|
## 5. 类语言能力 |
||||||
|
|
||||||
|
| 要求 | 实现证据 | 测试证据 | 结论 | |
||||||
|
|---|---|---|---| |
||||||
|
| 单继承、abstract 与有限虚分派 | Native C++ inheritance/virtual slot adapters | `abstract-method.phpt`、`polymorphic-clone.phpt`、`virtual-signature-variance.phpt` | 已验证 | |
||||||
|
| public/private/protected 在编译期检查 | Native member resolution | `method-visibility.phpt` 及不可访问方法/常量负向测试 | 已验证 | |
||||||
|
| Trait 在注入后按普通 Native member 编译 | 现有 Trait AST 注入 + Native member generation | `trait-inheritance-interface.phpt` | 已验证 | |
||||||
|
| Interface 仅作编译期契约,不能成为值表示 | interface contract validator | `internal-interface.phpt`、`interface-property-hooks.phpt` 及 interface escape 负向测试 | 已验证 | |
||||||
|
| 编译期可解析的 `instanceof` 折叠 | Native instanceof lowering | `instanceof.phpt`、dynamic instanceof 负向测试 | 已验证 | |
||||||
|
| Getter/Setter 注解生成直接调用 | annotation lowering + Native method path | `generators.phpt` | 已验证 | |
||||||
|
| Property Hook 只支持直接 get/set | Native hook lowering | `property-hooks.phpt`、`property-hook-native-object.phpt` 及间接操作负向测试 | 已验证 | |
||||||
|
| `clone` 保持动态子类、PHPX COW 和浅对象语义 | Native clone descriptor/thunk、`php::nativeClone()` | clone 系列 PHPT、`clone-phpx-fields.phpt` | 已验证 | |
||||||
|
| `__construct` 仅由 `new` 调用 | Native construction path、显式调用检查 | construction 系列 PHPT、explicit constructor 负向测试 | 已验证 | |
||||||
|
| `__destruct` 由 GC 至多执行一次,继承链 derived-to-base | Native finalizer chain | destructor/finalizer/lifecycle 系列 PHPT | 已验证 | |
||||||
|
| `__invoke` 和 `__toString` 使用确定 Native Call | Native magic method allow-list | `magic-methods.phpt` | 已验证 | |
||||||
|
| 动态魔术方法、变量属性/方法名不支持 | Native magic/dynamic access deny-list | dynamic magic、variable method/property 负向测试 | 已验证 | |
||||||
|
| `toArray/toString/toInt/toFloat/toBool/toObject` 要求实体方法、零参数和精确返回类型 | Native keyword method resolution | `keyword-conversions.phpt`、`testNativeObjectToObjectKeywordUsesDeclaredNativeMethod` 及签名负向测试 | 已验证 | |
||||||
|
| `count($obj)` 仅在实现 Countable 时特化 | Native count optimizer | `keyword-conversions.phpt`、count-without-countable 负向测试 | 已验证 | |
||||||
|
| `ArrayAccess` 直接语法映射到 Native `offset*()` 方法 | Native array access lowering | `array-access.phpt` | 已验证 | |
||||||
|
| Native `ArrayAccess` 禁止间接修改和引用 | writable-chain/reference validators | ArrayAccess compound/increment/nested/property/reference/coalesce 负向测试 | 已验证 | |
||||||
|
| Native `Iterator` foreach 映射到协议方法,保持 PHP 调用顺序 | Native foreach lowering | `iterator.phpt` | 已验证 | |
||||||
|
| `IteratorAggregate` 分流 Native Iterator 与 PHP Traversable | aggregate return-type lowering | `iterator.phpt` | 已验证 | |
||||||
|
| Native foreach 不枚举属性且禁止引用遍历 | interface/reference validators | foreach 负向 PHPUnit | 已验证 | |
||||||
|
|
||||||
|
## 6. GC 与生命周期 |
||||||
|
|
||||||
|
| 要求 | 实现证据 | 测试证据 | 结论 | |
||||||
|
|---|---|---|---| |
||||||
|
| Wren 风格精确、非移动、STW mark-sweep | `phpx/thirdparty/wren-gc`、`native_gc.cc` | PHPX `wren_gc.*` | 已验证 | |
||||||
|
| 裸指针写入无 RC、无 write barrier | Native pointer field/local codegen | 生成 C++ 审查、Native PHPT | 已验证 | |
||||||
|
| 16 MiB 初始阈值、1 MiB 下限、50% headroom | Wren GC 配置 | `wren_gc.uses_stable_native_heap_defaults` | 已验证 | |
||||||
|
| 精确 root frame 保持对象图存活 | `NativeRootFrame`、generated root slots | PHPX root tests、`gc-cycle.phpt` | 已验证 | |
||||||
|
| Fiber 非 LIFO 生命周期安全 | root frame registry | `fiber-lifetime.phpt`、`fiber-shutdown.phpt`、PHPX Fiber root tests | 已验证 | |
||||||
|
| global/static request roots 在 ZTS 下为 thread-local | generated globals/root registration | ZTS 环境下 `global-and-static.phpt`、PHPX request root tests | 已验证 | |
||||||
|
| RSHUTDOWN 清空 root 并销毁 heap | `nativeGcRequestShutdown()` | PHPX shutdown tests | 已验证 | |
||||||
|
| finalizer 可复活一次,之后不重复执行 | Wren/Native finalization state | `gc-cycle.phpt`、PHPX resurrection tests | 已验证 | |
||||||
|
| finalizer 中分配、异常和 Zend 状态安全 | finalizer queue/exception cleanup | finalizer/lifecycle PHPT、PHPX finalizer tests | 已验证 | |
||||||
|
| 构造或克隆失败不产生悬空对象,已逃逸对象保持有效 | `nativeConstruct()`、`nativeClone()` failure paths | `failed-lifecycle-escape.phpt`、`failed-clone-finalizer.phpt` | 已验证 | |
||||||
|
|
||||||
|
## 7. ZendVM 边界与容器 |
||||||
|
|
||||||
|
| 要求 | 实现证据 | 测试证据 | 结论 | |
||||||
|
|---|---|---|---| |
||||||
|
| Native Object 不能进入 PHP array/object property/mixed | escape and boundary validators | 对应 PHPUnit 负向测试 | 已验证 | |
||||||
|
| 不能传给 PHP/ZendVM 动态函数、Closure 或 constructor | call boundary validator | dynamic call、Closure、Zend constructor 负向测试 | 已验证 | |
||||||
|
| Reflection/WeakReference/serialize/json_encode 不支持 | facility-specific diagnostics | 对应 PHPUnit 负向测试 | 已验证 | |
||||||
|
| Generator 不能保存、接收或产出 Native pointer | generator boundary validator | generator 系列负向测试 | 已验证 | |
||||||
|
| 普通函数跨 Fiber suspend 的 Native local 有精确 root | root frame lifecycle | Fiber PHPT | 已验证 | |
||||||
|
| 局部 Std Container 可保存具体 Native pointer | Std Container Native value mapping/root frame | `std-containers.phpt` | 已验证 | |
||||||
|
| Native Std Container 不能逃逸为 Zend 值、static/global 或 closure capture | container escape validation | Std Container 系列负向 PHPUnit | 已验证 | |
||||||
|
| `include`/`eval` 不暴露 Native local 到 Zend symbol table | include scope filtering | `include-native-scope.phpt` | 已验证 | |
||||||
|
|
||||||
|
## 8. 项目级分析 |
||||||
|
|
||||||
|
| 要求 | 实现证据 | 测试证据 | 结论 | |
||||||
|
|---|---|---|---| |
||||||
|
| Native class 前向声明不依赖文件顺序 | declaration discovery pre-pass | `testDiscoversNativeTypesBeforeCrossFileSignaturePreprocessing` | 已验证 | |
||||||
|
| global Native slot ABI 在任一 C++ 文件生成前确定 | `NativeGlobalDiscovery`、`NativeGlobalTypeResolver` | `testDiscoversNativeGlobalSlotBeforeEarlierReaderIsConverted`,实际双文件构建 | 已验证 | |
||||||
|
| `global $slot` 与静态可解析的 `$GLOBALS[...]` 使用同一 Native root slot | literal/constant global slot lowering、request root registration | `global-and-static.phpt`、跨文件 Closure/常量 `$GLOBALS` fixture | 已验证 | |
||||||
|
| 动态 `$GLOBALS[$key]` 不得承载 Native Object | dynamic Zend boundary validation | `testRejectsNativeObjectStoredThroughDynamicGlobalsKey` | 已验证 | |
||||||
|
| global slot 固定首个 Native 类型,只允许子类或 null | global registration/type validation | `global-and-static.phpt`、global type change 负向测试 | 已验证 | |
||||||
|
| 未使用 Native Class 的项目跳过 Native global pre-pass | `discoverNativeGlobalObjects()` fast return | 源码检查、全量 PHPUnit | 已验证 | |
||||||
|
|
||||||
|
## 9. 当前验证命令 |
||||||
|
|
||||||
|
```bash |
||||||
|
./run-tests.php -j4 --compiler ./tpc tests/compiler/native-class/ |
||||||
|
vendor/bin/phpunit phpunit/src/NativeClass/NativeClassValidationTest.php |
||||||
|
/home/swoole/workspace/aot/phpx/build/bin/phpx-tests \ |
||||||
|
--gtest_filter='wren_gc.*:native_gc.*' |
||||||
|
``` |
||||||
|
|
||||||
|
本次 Iterator 专项结果为:`iterator.phpt` 1/1、Native Class PHPUnit 136/136, |
||||||
|
普通 foreach 回归 14/14。Native Class PHPT 目录现有 71 项;按当前任务约定暂未重复执行 |
||||||
|
该目录及编译器 PHPT 全量测试,留待下一轮统一回归。 |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,212 @@ |
|||||||
|
# Zend Object 创建与属性默认值初始化 |
||||||
|
|
||||||
|
本文记录 TypePHP 生成的 Zend Class 在 MINIT 和对象创建阶段的初始化职责,重点说明何时需要自定义 `create_object`、其中允许执行哪些行为,以及对象创建热路径上的性能边界。 |
||||||
|
|
||||||
|
本文只讨论注册到 ZendVM 的普通 TypePHP Class。`#[Native]` Class 使用 Native Heap 与 GC,不走本文流程。 |
||||||
|
|
||||||
|
## 1. 两个初始化阶段必须分开 |
||||||
|
|
||||||
|
TypePHP Class 的属性初始化分为两个阶段: |
||||||
|
|
||||||
|
1. `gen_stub.php` 在 MINIT 生成 `register_class_*()`,建立 `zend_class_entry`、属性元数据和默认属性表; |
||||||
|
2. 只有默认属性表无法准确表达的值,才在每次创建对象时由自定义 `create_object` 补充。 |
||||||
|
|
||||||
|
这两个阶段不能重复执行相同的属性赋值。`register_class_*()` 已写入的值会由 Zend 的 `object_properties_init()` 复制到新对象;再次调用 `zend_update_property()` 不仅没有语义价值,还会进入属性名查找、类型检查、handler 分派和引用计数路径。 |
||||||
|
|
||||||
|
## 2. gen_stub.php 负责的默认值 |
||||||
|
|
||||||
|
以下值可以准确写入 Zend Class 的默认属性表: |
||||||
|
|
||||||
|
| 源代码默认值 | 注册阶段表示 | 是否需要在 `create_object` 中再次写入 | |
||||||
|
|---|---|---| |
||||||
|
| `null` | `ZVAL_NULL` | 否 | |
||||||
|
| `bool` | `ZVAL_TRUE/FALSE` | 否 | |
||||||
|
| `int` | `ZVAL_LONG` | 否 | |
||||||
|
| `float` | `ZVAL_DOUBLE` | 否 | |
||||||
|
| `string` | 持久化 `zend_string` | 否 | |
||||||
|
| 标量常量表达式 | 编译期求值后的标量 zval | 否 | |
||||||
|
| `[]` | `ZVAL_EMPTY_ARRAY` | 否 | |
||||||
|
| 没有显式默认值的 TypePHP typed property | TypePHP 规定的零值、空字符串、空数组、`null` 或 `UNDEF` | 否 | |
||||||
|
|
||||||
|
例如: |
||||||
|
|
||||||
|
```php |
||||||
|
class Value |
||||||
|
{ |
||||||
|
private const BASE = 20; |
||||||
|
|
||||||
|
public int $id = self::BASE + 3; |
||||||
|
public string $name = 'type' . 'php'; |
||||||
|
public array $items = []; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
只要表达式能够在编译期安全求值,以上三个属性都应完全依赖 Zend Class 默认属性表。创建 `Value` 时不得再次调用 `zend_update_property()`。 |
||||||
|
|
||||||
|
## 3. 默认值何时需要运行时补充 |
||||||
|
|
||||||
|
当前 `gen_stub.php` 不能在默认属性表中准确表示以下值。 |
||||||
|
|
||||||
|
### 3.1 非空数组 |
||||||
|
|
||||||
|
非空数组默认值当前在注册函数中使用 `ZVAL_EMPTY_ARRAY` 作为占位值。每个对象必须构造独立、语义正确的数组值: |
||||||
|
|
||||||
|
```php |
||||||
|
class Request |
||||||
|
{ |
||||||
|
public array $options = ['timeout' => 10]; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
因此 `Request::$options` 需要在 `create_object` 中补充。多个对象仍遵守 PHP 数组的 copy-on-write 语义;修改一个对象的数组不得影响其他对象。 |
||||||
|
|
||||||
|
数组常量也遵守相同规则。若编译器只能确定它是数组、不能证明它为空,则保守地保留运行时初始化。 |
||||||
|
|
||||||
|
### 3.2 Enum case |
||||||
|
|
||||||
|
Enum case 是对象,不是标量常量: |
||||||
|
|
||||||
|
```php |
||||||
|
enum State |
||||||
|
{ |
||||||
|
case Ready; |
||||||
|
} |
||||||
|
|
||||||
|
class Task |
||||||
|
{ |
||||||
|
public State $state = State::Ready; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
类注册代码当前只能先生成占位值,`create_object` 再取得真正的 enum case 对象并写入属性。因此“只有非空数组才需要自定义 `create_object`”并不成立,enum case 是明确的第二类反例。 |
||||||
|
|
||||||
|
### 3.3 无法安全解析的常量表达式 |
||||||
|
|
||||||
|
若预处理阶段无法证明默认值可由 Zend 默认属性表准确表达,编译器必须保守地保留运行时初始化。优化只能删除已证明冗余的工作,不能根据表达式外形猜测其运行时类型。 |
||||||
|
|
||||||
|
## 4. handlers 与父类 allocator |
||||||
|
|
||||||
|
### 4.1 Property Hook 与非对称 set 可见性不单独触发 |
||||||
|
|
||||||
|
PHP 8.4 Property Hook、`private(set)` 和 `protected(set)` 会安装 TypePHP 自定义 object handlers,但这本身不要求覆盖 `create_object`。Zend 8.4 的 `object_properties_init()` 直接复制 class default table,不调用 read/write handler;普通 `php::stdCreateObject()` 已能正确设置最终 handlers。 |
||||||
|
|
||||||
|
只有该类同时含有非空数组、enum case 等运行时默认值时,才需要自定义创建流程。补充初始化必须绕过 setter;即使使用 `zend_std_write_property()`,PHP 8.4 也会根据 Hook 元数据调用 setter。当前生成代码因此使用编译期已知的 property offset,经 PHPX `Object::attr(offset)` 直接更新 backing slot。 |
||||||
|
|
||||||
|
### 4.2 父类自定义对象分配器 |
||||||
|
|
||||||
|
若父类来自 PHP 内置扩展,或祖先类拥有自定义对象存储布局,子类不能绕过父类的 allocator。当前类因运行时默认值确实需要自定义创建流程时,必须先调用保存的父类 `create_object`,再补充当前类的值。 |
||||||
|
|
||||||
|
TypePHP 父类已经安装自定义 allocator 时,普通子类通常直接继承它。只有子类自身也需要补充初始化时,才生成新的委派层。 |
||||||
|
|
||||||
|
## 5. 自定义 create_object 的执行流程 |
||||||
|
|
||||||
|
生成代码通过 `typephp_create_object_with_defaults()` 完成以下步骤: |
||||||
|
|
||||||
|
1. 保存类最终的 `default_object_handlers`; |
||||||
|
2. 若必须尊重父类对象布局,调用保存的父类 allocator;否则执行 `zend_objects_new()` 与 `object_properties_init()`; |
||||||
|
3. 临时把新对象切换到 Zend 标准 object handlers,确保异常路径和其他对象操作处于可控状态; |
||||||
|
4. 只执行标记为 `requiresRuntimeDefaultInit` 的属性初始化,并通过缓存的 declared-property offset 直接写 backing slot; |
||||||
|
5. 每次写入后检查 Zend 异常; |
||||||
|
6. 无论正常返回还是发生 C++ 异常,都恢复最终 handlers; |
||||||
|
7. 返回已完整初始化的 `zend_object *`。 |
||||||
|
|
||||||
|
初始化器是模板参数和编译期 lambda,不使用 `std::function`,也不会为 lambda 动态分配内存。`delegate_to_base` 是调用点确定的布尔值,优化构建中通常可被 C++ 编译器折叠。 |
||||||
|
|
||||||
|
以下行为不属于 `create_object`: |
||||||
|
|
||||||
|
- PHP `__construct()` 的函数体; |
||||||
|
- static property 默认值初始化;它在 `module_init()` 中完成; |
||||||
|
- 已由默认属性表表达的标量、`null` 和空数组赋值; |
||||||
|
- clone 后重新应用默认值;clone 应复制源对象当前状态,而不是重新创建默认状态。 |
||||||
|
|
||||||
|
## 6. 已修复的主要性能问题 |
||||||
|
|
||||||
|
旧生成逻辑只要类中存在任意显式非 static 默认值,就安装自定义 `create_object`,并在每次创建对象时重新 update 所有默认属性。这会产生两层重复成本: |
||||||
|
|
||||||
|
1. 只含 `public int $value = 0` 的普通类也绕过标准快速创建路径; |
||||||
|
2. 一个类只要含有一个非空数组,其他标量属性也会被逐个重复 update。 |
||||||
|
|
||||||
|
当前规则已经调整为: |
||||||
|
|
||||||
|
- 只有确实需要运行时补充的属性才使 `requireCtor` 生效; |
||||||
|
- 已由 `gen_stub.php` 准确注册的属性不会出现在运行时初始化 block 中; |
||||||
|
- 只有 Hook/非对称可见性而没有运行时默认值的类不再生成空的自定义 allocator; |
||||||
|
- Hook 与运行时默认值同时存在时,使用固定 property offset 更新 backing slot,不调用 setter。 |
||||||
|
|
||||||
|
在 micro benchmark 中,仅包含标量属性的 `new Foo()` 已从约 `1.8s` 降至约 `0.78s`,与同环境 ZendPHP 扣除空循环后的约 `0.83s` 接近。该数字只用于记录优化量级,不是跨机器性能承诺。 |
||||||
|
|
||||||
|
## 7. 已实现优化、剩余成本与后续方向 |
||||||
|
|
||||||
|
### 7.1 非空数组使用请求级模板与 copy-on-write |
||||||
|
|
||||||
|
不能把非空数组放进 internal class 的默认属性表,但这不等于必须为每个对象重新构建数组。当前生成器已经使用请求级默认值模板: |
||||||
|
|
||||||
|
1. 每个包含运行时数组默认值的类拥有一组 `THREAD_LOCAL php::Var` 模板和一个初始化状态,NTS 构建不引入锁; |
||||||
|
2. 第一次创建该类对象时,通过 `UNEXPECTED(!initialized)` 惰性构建该类的全部模板; |
||||||
|
3. 模板全部在局部临时值中成功构建后才提交并设置初始化标记,构造异常不会发布半初始化状态; |
||||||
|
4. 模板初始化发生在对象分配之前,失败时不会遗留一个尚未返回的对象; |
||||||
|
5. 后续创建对象时只把模板 zval 复制到目标 backing slot,即增加一次数组引用计数; |
||||||
|
6. 某个对象第一次修改该属性时,由 Zend/PHPX 的 `SEPARATE_ARRAY` 执行 copy-on-write; |
||||||
|
7. 在 `module_clean()` 中释放模板并重置初始化状态,request allocator 分配的 HashTable 不会跨越 RSHUTDOWN。 |
||||||
|
|
||||||
|
以如下默认值为例: |
||||||
|
|
||||||
|
```php |
||||||
|
class Request |
||||||
|
{ |
||||||
|
public array $options = [ |
||||||
|
'timeout' => 10, |
||||||
|
'headers' => ['Accept' => 'application/json'], |
||||||
|
]; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
若创建一万个对象但不修改 `$options`,数组及嵌套数组只构建一次;每个对象只持有共享 zval。若其中一个对象执行 `$request->options['timeout'] = 30`,只有该对象在写入时分离,其他对象和模板保持不变。嵌套数组也继续使用 Zend 原有的逐层 copy-on-write 规则。 |
||||||
|
|
||||||
|
PHP 属性默认数组不能包含引用,允许出现在常量表达式中的对象主要是不可变的 enum case,因此共享模板符合默认属性语义。PHPT 已覆盖顶层写入、嵌套写入、`unset`、引用写入和动态对象写入,确认这些路径都会正确分离。 |
||||||
|
|
||||||
|
不能简单地在 MINIT 构造持久化数组并传给 `zend_declare_typed_property()`。TypePHP 注册的是 `ZEND_INTERNAL_CLASS`,Zend 8.4 明确禁止 internal property 使用 refcounted default zval;`_object_properties_init()` 的 internal-class 快速路径也不会增加默认值引用计数。非空 array 与 enum object 都属于 refcounted value。 |
||||||
|
|
||||||
|
因此,在不改变“TypePHP Class 注册为 internal class”这一基础设计、也不修改 Zend ABI 的前提下,非空数组仍不进入 class default table;表外请求级模板把数组构造成本从“每个对象一次”降为“每个请求、每个默认值一次”。未修改默认数组的对象只承担 zval 复制和引用计数成本,实际修改的对象才承担数组分离成本。 |
||||||
|
|
||||||
|
模板按类惰性初始化,而不是在 RINIT 无条件构建全部模板:大型项目中很多类在一次请求内不会实例化。每个对象只增加一个高度可预测的初始化状态分支;第一次之后该分支稳定为 false。 |
||||||
|
|
||||||
|
暂不生成模块生命周期的 persistent immutable template。该方案需要完整验证 persistent HashTable、interned string、嵌套数组、MSHUTDOWN 和 ZTS,并且包含运行时常量或 enum case 的数组仍要走请求级路径。在 ZendVM 对这些组合的约束得到充分验证前,请求级模板是安全边界。 |
||||||
|
|
||||||
|
### 7.2 已改为固定属性槽写入 |
||||||
|
|
||||||
|
运行时补充的属性在编译期已经知道 class、属性名、offset 和类型。当前实现复用 persistent property-offset cache,并通过 `php::Object::attr(offset)` 更新槽位,已经省去每个对象上的属性名 hash 查询、通用 write handler 和 Property Hook setter。 |
||||||
|
|
||||||
|
这里仍会为 initializer 建立一个短生命周期 `php::Object` carrier,并读取 offset cache。后续若 profiling 证明它是热点,可以在 MINIT 后直接保存最终 offset,或在 PHPX 增加不取得对象所有权的初始化 helper。任何进一步优化都必须继续处理旧值析构、引用计数、父类 private slot、Hook backing slot 和异常安全,不能退回裸指针的无保护赋值。 |
||||||
|
|
||||||
|
### 7.3 Enum case 可提前绑定 |
||||||
|
|
||||||
|
Enum case 同样是 refcounted object,不能直接作为 internal class 默认 zval。可考虑在 MINIT 缓存稳定的 enum case 指针或 zval,再在每次创建对象时执行正确的引用计数复制,从而省去重复 class/case 查找;仍不能省略对象属性写入本身。 |
||||||
|
|
||||||
|
### 7.4 继承链上的多层 allocator |
||||||
|
|
||||||
|
父类和子类都拥有运行时默认值时,创建流程会逐层委派并执行各自初始化,成本随相关继承层数增长。未来可以对完全由 TypePHP 控制、且没有特殊对象布局的继承链合并初始化计划;内置扩展父类仍必须调用其 allocator。 |
||||||
|
|
||||||
|
### 7.5 保守常量可能产生不必要的 allocator |
||||||
|
|
||||||
|
无法在预处理阶段解析的常量会保守进入运行时路径。可以在符号准备完成后增加一次统一的常量默认值分类,减少“实际是标量,但早期无法证明”的自定义 allocator。该优化必须保留 enum case 和数组常量的区别。 |
||||||
|
|
||||||
|
### 7.6 自定义 handlers 的动态访问成本 |
||||||
|
|
||||||
|
TypePHP 当前为普通 Zend Class 安装属性 handlers,以支持 typed property 的 unset 语义、Property Hook 和非对称写可见性。安装发生在 MINIT,不等同于安装自定义 `create_object`;但动态属性读写仍可能进入 handler。已被编译器解析为固定槽位的 Native 属性访问不应因此退化。 |
||||||
|
|
||||||
|
## 8. 回归测试要求 |
||||||
|
|
||||||
|
修改该流程至少应覆盖: |
||||||
|
|
||||||
|
- 标量、标量常量表达式和空数组不生成自定义 allocator; |
||||||
|
- 非空数组生成 allocator,且两个对象的数组修改互不影响; |
||||||
|
- enum case 默认值在对象创建后是真正的 enum object; |
||||||
|
- 仅含 Property Hook 或非对称 set 可见性的类不生成空 allocator,且 Reflection、动态读写行为不退化; |
||||||
|
- Property Hook/非对称属性与运行时默认值组合时不触发 setter; |
||||||
|
- 父子类分别声明运行时默认值时,父类和子类属性都正确; |
||||||
|
- 继承内置扩展类时不破坏其对象布局; |
||||||
|
- 异常路径恢复 object handlers; |
||||||
|
- 自举编译和完整 PHPUnit/PHPT 回归通过。 |
||||||
|
|
||||||
|
当前针对代码生成的核心断言位于 `NewObjectCodegenTest`,运行语义由 `default-initialization-paths.phpt`、`default-expressions-inheritance.phpt` 和 Property Hook 测试组覆盖。 |
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue