From 393ea1d369e2277fd776e13bec554e3010591a75 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 8 Sep 2026 10:42:23 +0800 Subject: [PATCH] Added native iOS application development support --- docs/zh-cn/LOCAL_CLOSURE_OPTIMIZATION.md | 231 ++++++++++++++++ examples/objective-c-macos/Info-iOS.plist | 18 +- examples/objective-c-macos/README.md | 36 ++- .../objective-c-macos/generate-ios-icons.sh | 42 +++ .../ios-assets/AppIcon-1024.png | Bin 0 -> 40497 bytes .../ios-assets/AppIcon-120.png | Bin 0 -> 5342 bytes .../ios-assets/AppIcon-180.png | Bin 0 -> 8388 bytes .../ios-assets/AppIcon-40.png | Bin 0 -> 1479 bytes .../ios-assets/AppIcon-58.png | Bin 0 -> 2274 bytes .../ios-assets/AppIcon-60.png | Bin 0 -> 2382 bytes .../ios-assets/AppIcon-80.png | Bin 0 -> 3360 bytes .../ios-assets/AppIcon-87.png | Bin 0 -> 3649 bytes .../objective-c-macos/ios-assets/AppIcon.svg | 7 + .../ios-assets/AppIcon60x60@2x.png | Bin 0 -> 5342 bytes .../ios-assets/AppIcon60x60@3x.png | Bin 0 -> 8388 bytes .../objective-c-macos/ios-src/uikit_bridge.mm | 37 ++- .../ios-src/uikit_bridge.stub.php | 2 +- examples/objective-c-macos/ios.yml | 4 +- .../objc-src/macos_bridge.mm | 11 +- .../objc-src/macos_bridge.stub.php | 2 +- examples/objective-c-macos/package-ios-app.sh | 5 + .../objective-c-macos/php-src/application.php | 110 ++++++-- phpunit/code/local-native-closure-codegen.php | 23 ++ .../Build/PrecompiledHeaderManagerTest.php | 33 +++ phpunit/src/ClosureTest.php | 5 +- phpunit/src/FixedReferenceStorageTest.php | 20 +- phpunit/src/LocalClosureCodegenTest.php | 41 +++ phpunit/src/VarIntTypesTest.php | 15 +- src/Analysis/LocalClosureAnalyzer.php | 222 ++++++++++++++++ src/Build/PrecompiledHeaderManager.php | 10 +- src/CompilerBase.php | 3 + src/Context/FunctionContext.php | 14 + src/Generator/ClosureGenerator.php | 247 +++++++++++++++++- src/Parser/AssignOpTrait.php | 5 + src/Parser/FunctionCallTrait.php | 8 + src/Translator.php | 5 + .../closure/local-native-closure.phpt | 68 +++++ 37 files changed, 1169 insertions(+), 55 deletions(-) create mode 100644 docs/zh-cn/LOCAL_CLOSURE_OPTIMIZATION.md create mode 100755 examples/objective-c-macos/generate-ios-icons.sh create mode 100644 examples/objective-c-macos/ios-assets/AppIcon-1024.png create mode 100644 examples/objective-c-macos/ios-assets/AppIcon-120.png create mode 100644 examples/objective-c-macos/ios-assets/AppIcon-180.png create mode 100644 examples/objective-c-macos/ios-assets/AppIcon-40.png create mode 100644 examples/objective-c-macos/ios-assets/AppIcon-58.png create mode 100644 examples/objective-c-macos/ios-assets/AppIcon-60.png create mode 100644 examples/objective-c-macos/ios-assets/AppIcon-80.png create mode 100644 examples/objective-c-macos/ios-assets/AppIcon-87.png create mode 100644 examples/objective-c-macos/ios-assets/AppIcon.svg create mode 100644 examples/objective-c-macos/ios-assets/AppIcon60x60@2x.png create mode 100644 examples/objective-c-macos/ios-assets/AppIcon60x60@3x.png create mode 100644 phpunit/code/local-native-closure-codegen.php create mode 100644 phpunit/src/LocalClosureCodegenTest.php create mode 100644 src/Analysis/LocalClosureAnalyzer.php create mode 100644 tests/compiler/closure/local-native-closure.phpt diff --git a/docs/zh-cn/LOCAL_CLOSURE_OPTIMIZATION.md b/docs/zh-cn/LOCAL_CLOSURE_OPTIMIZATION.md new file mode 100644 index 00000000..351dface --- /dev/null +++ b/docs/zh-cn/LOCAL_CLOSURE_OPTIMIZATION.md @@ -0,0 +1,231 @@ +# 局部非逃逸 Closure 原生化设计 + +## 1. 背景 + +TypePHP 当前把所有匿名函数和箭头函数都实现为真正的 Zend `Closure`。编译器先生成 +一个 `php::ClosureFn`,PHPX 再为每个闭包创建: + +- `zend_function` 及其参数信息; +- 保存 C++ 回调、绑定对象和捕获值的 `ClosureCarrier`; +- 由 `zend_create_fake_closure()` 创建的 Zend `Closure` 对象。 + +调用闭包时仍需经过 callable 解析、Zend 调用帧和参数数组。这个实现是 PHP 可见 +Closure 的必要兼容路径,但对于只在当前函数内直接调用的闭包,Closure 对象从未被 +观察,以上分配和动态调用没有语义价值。 + +本优化把编译器能够证明不逃逸的局部闭包生成为具体 C++ lambda,并把其调用点生成为 +直接 C++ 调用。不能证明安全时必须完整回退到现有 Closure Carrier 路径。 + +## 2. 目标 + +第一阶段目标如下: + +1. 消除局部闭包创建时的 `zend_function`、Carrier 和 Zend Closure 分配; +2. 消除每次调用的 callable 查询及 Zend execute frame; +3. 保留 PHP 的闭包创建时捕获、按值复制、可变捕获状态及参数求值顺序; +4. 不改变任何可能观察 Closure 对象身份或 Zend 调用帧的代码; +5. 以保守证明为前提,分析失败不产生诊断,只回退到原实现。 + +本优化属于 TypePHP 编译器内部实现,不新增 PHPX 公共 API。 + +## 3. 第一阶段适用范围 + +仅优化以下形态: + +```php +function calculate(int $value): int +{ + $offset = 1; + $callback = static fn (int $item): int => $item + $offset; + return $callback($value); +} +``` + +候选闭包必须同时满足: + +- 赋值是函数顶层的一条独立语句; +- 左值是普通局部变量; +- 该变量在函数中只有这一个定义; +- 至少存在一个调用点; +- 每个读取位置都只能是 `$callback(...)` 的直接调用目标; +- 赋值在源码和控制流上支配全部调用点; +- 调用只使用固定位置参数,暂不支持命名参数和 unpack; +- 参数没有默认值、引用或 variadic; +- 按值捕获,或对固定 `int/string/float/bool/array` 存储的按引用捕获; +- 闭包不是 generator,不按引用返回,也不包含嵌套函数或闭包; +- 闭包体不依赖 Zend 调用帧,例如不调用 `func_get_args()` 或 + `debug_backtrace()`; +- 第一阶段只处理全局函数体,不处理类方法中的 `$this`、词法 class scope 和 + late static binding。 + +这里的“函数顶层”限制很重要。TypePHP 通常把普通局部变量声明提升到 C++ 函数入口, +但 C++ lambda 的匿名类型必须通过 `auto` 在初始化位置声明;同时 PHP 的按值捕获发生 +在闭包表达式求值时,不能把初始化提前到函数入口。 + +## 4. 必须回退的情况 + +只要 Closure 值可能被 PHP 代码观察,就继续生成真实 Closure: + +```php +return $callback; +$array[] = $callback; +$object->callback = $callback; +$other = $callback; +array_map($callback, $values); +is_callable($callback); +new ReflectionFunction($callback); +``` + +以下情况也回退: + +- 多次赋值、条件赋值、循环内创建或 `unset()`; +- 动态 `var-ref` 捕获、引用参数或递归闭包; +- 不同闭包在同一个变量中汇合; +- generator Closure; +- 命名参数、参数展开、variadic 或默认参数; +- 捕获 Native Object、强类型容器或其他需要专门 GC root 的值; +- 嵌套闭包以及任何分析器未明确识别的使用方式。 + +回退不是错误,也不应产生 warning。优化器只能在正向证明完整时启用。 + +## 5. 生成模型 + +原始代码: + +```php +$base = 10; +$callback = static function ($value) use ($base) { + return $base + $value; +}; +$result = $callback(5); +``` + +目标代码的结构为: + +```cpp +auto callback = [base = base](php::Var value) mutable -> php::Var { + return base + value; +}; +result = callback(5); +``` + +使用显式 init-capture 有两个原因: + +1. 捕获在闭包创建位置形成快照,而不是引用外层变量; +2. `mutable` 允许闭包内部修改自己的捕获副本,并让该状态在多次直接调用之间保留。 + +`php::Var` 的复制构造会解除普通 PHP reference,符合 `use ($value)` 的按值语义。 +固定 `int/string/float/bool/array` 的 C++ 值则直接复制。第一阶段不接受需要特殊 +生命周期处理的 Native Object 和 std container 捕获。 + +固定类型的引用捕获不需要先制造 Zend reference。因为分析器已经证明 lambda 不逃逸, +其生命周期完全包含在外层 C++ 函数帧内,所以: + +```php +$callback = function () use (&$number, &$text, &$items): void { + $number++; + $text .= '!'; + $items[] = $number; +}; +``` + +可直接生成: + +```cpp +auto callback = [&number, &text, &items]() mutable -> php::Var { + // ... +}; +``` + +闭包内部把这三个名字登记为对应的 `INT_REF/STR_REF/ARRAY_REF`,继续复用 TypePHP +现有 typed-ref 赋值检查。这样没有 `php::Ref`、`RefWrap`、`zend_reference` 或写回步骤。 +动态 `php::Var` 引用以及对象、resource、stream、Native Object、std container 等捕获 +仍回退到真实 Zend Closure 路径。 + +第一阶段继续使用 `php::Var` 作为闭包参数和返回 ABI,以复用现有闭包体生成逻辑并 +降低语义风险。后续可以在独立优化中,根据参数和返回声明把 ABI 收窄到 +`php::Int`、`php::Str` 等固定类型。 + +## 6. 参数求值顺序 + +PHP 固定为从左到右求值参数,而 C++17 不保证不同函数实参之间的求值顺序。直接调用 +不能简单输出: + +```cpp +callback(parse(a), parse(b)); +``` + +多参数调用必须沿用 TypePHP 的 ordered operand 机制,在调用前按源码顺序物化存在 +副作用的值,然后再调用 lambda。第一阶段即使参数表达式较简单,也不能依赖 C++ +编译器碰巧采用的顺序。 + +## 7. 分析与代码生成 + +新增局部 Closure 分析器,在函数 SSA 分析完成后、convert 生成语句前运行: + +1. 收集函数顶层 `$var = Closure/ArrowFunction` 候选; +2. 遍历当前函数 AST,分类该变量的每个定义和使用; +3. 任一使用不是直接调用目标即淘汰候选; +4. 检查闭包和调用参数是否位于第一阶段支持集合; +5. 把证明结果写入当前 `FunctionContext`; +6. 赋值生成器在原始语句位置输出 `auto` lambda; +7. 函数调用生成器识别该局部变量并输出直接调用; +8. 普通局部变量声明生成器跳过已经在源码位置声明的 lambda。 + +分析数据只属于当前函数的 `FunctionContext`,不得写入 persistent/request runtime +cache,也不得跨函数复用。 + +## 8. 安全不变量 + +实现必须始终满足: + +1. **无误优化**:不能证明时回退,绝不猜测; +2. **创建时捕获**:不能把 lambda 初始化提升到原 PHP 表达式之前; +3. **生命周期包含**:lambda 及全部调用点位于同一 C++ 函数帧; +4. **无 PHP 可见身份**:优化后的值不能进入 zval、数组、属性、参数或返回值; +5. **求值顺序一致**:实参和闭包体中的副作用顺序必须与 PHP 一致; +6. **异常边界一致**:PHP 异常仍通过现有 C++ `zend_object *` 路径传播; +7. **回退等价**:移除优化标记后,同一源码必须仍可由 Carrier 路径编译运行。 + +## 9. 测试计划 + +正向测试至少覆盖: + +- 无捕获箭头函数; +- 按值捕获及创建时快照; +- 捕获副本在多次调用之间保持内部修改; +- 固定 int/string/float/bool/array 的直接引用捕获; +- 普通匿名函数; +- 多次直接调用; +- 参数表达式的左到右求值。 + +回退测试至少覆盖: + +- 作为参数传给 `array_map()`; +- 从函数返回; +- 写入数组或属性; +- 赋给另一个变量; +- 动态 `var-ref` 引用捕获; +- 默认参数、variadic、unpack 和命名参数; +- generator、递归及嵌套 Closure; +- class method 中的 `$this`; +- `ReflectionFunction`、`is_callable()` 等身份观察。 + +生成代码测试需要同时断言:正向候选不包含 +`newClosureWithParameters`/`ClosureCarrier` 调用,而回退用例仍包含真实 Closure 创建。 + +## 10. 性能验证 + +`benchmark/dynamic-call/benchmark.php` 中的 `closure_monomorphic` 是第一阶段的主要性能 +用例。还应保留 `closure_alternating` 作为不能原生化的对照组。 + +验证指标包括: + +- Closure 创建次数和堆分配; +- 每次调用耗时; +- TypePHP/Zend PHP 比值; +- 生成 C++ 大小及编译时间; +- checksum 与 Zend PHP 一致。 + +性能提升不是放宽安全条件的理由。任何会暴露 Closure 对象或无法静态证明的场景, +即使处于热点,也继续使用 Closure Carrier。 diff --git a/examples/objective-c-macos/Info-iOS.plist b/examples/objective-c-macos/Info-iOS.plist index 61df13f3..b123edc8 100644 --- a/examples/objective-c-macos/Info-iOS.plist +++ b/examples/objective-c-macos/Info-iOS.plist @@ -12,14 +12,28 @@ org.swoole.typephp.ios-hello CFBundleInfoDictionaryVersion 6.0 + CFBundleIcons + + CFBundlePrimaryIcon + + CFBundleIconFiles + + AppIcon60x60 + + + + CFBundleIconFiles + + AppIcon60x60 + CFBundleName TypePHP iOS Hello CFBundlePackageType APPL CFBundleShortVersionString - 0.1.0 + 0.2.0 CFBundleVersion - 1 + 2 LSRequiresIPhoneOS MinimumOSVersion diff --git a/examples/objective-c-macos/README.md b/examples/objective-c-macos/README.md index 7ff7515c..aa58cda6 100644 --- a/examples/objective-c-macos/README.md +++ b/examples/objective-c-macos/README.md @@ -66,13 +66,29 @@ sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer xcrun --sdk iphoneos --show-sdk-path ``` -Install this SDK at `ios/iphoneos-arm64` inside the PHPX checkout, matching the -integrated layouts already used by `full-static/sdk` and -`wasm/wasm32-wasip2`. Build the executable from the TypePHP repository root; -`PHPX_HOME` selects both PHPX sources and the target SDK: +Build the SDK with swoole-cli. PHP source updates must go through +`sync-source-code.php`; this also validates and synchronizes the generated Zend +parser/scanner sources from the official php.net release archive: ```sh -export PHPX_HOME=/path/to/phpx +cd /path/to/swoole-cli +php sync-source-code.php --action run +php prepare.php @iphoneos-arm64 --with-parallel-jobs=8 +./make.sh all-library +./make.sh config +./make.sh libphp +./make.sh phpx +./make.sh sdk +``` + +The installed SDK is under +`thirdparty/phpx/ios/iphoneos-arm64`. The `php-version` major in `ios.yml` must +match `sapi/PHP-VERSION.conf` used by swoole-cli. Build the executable from the +TypePHP repository root; `PHPX_HOME` selects both PHPX sources and the target +SDK: + +```sh +export PHPX_HOME=/path/to/swoole-cli/thirdparty/phpx php bin/tpc.php examples/objective-c-macos/ios.yml --no-progress ``` @@ -83,9 +99,19 @@ the login keychain: ```sh export TYPEPHP_IOS_PROVISIONING_PROFILE=/path/to/profile.mobileprovision export TYPEPHP_IOS_CODE_SIGN_IDENTITY='Apple Development: Your Name (TEAMID)' +# Set this when the profile uses a different identifier than the example. +export TYPEPHP_IOS_BUNDLE_IDENTIFIER='your.provisioned.bundle.identifier' sh examples/objective-c-macos/package-ios-app.sh ``` +The package includes iPhone icon sizes generated from the repository's +`swoole-logo.svg`. To regenerate them after changing the logo, install FFmpeg +and run: + +```sh +sh examples/objective-c-macos/generate-ios-icons.sh +``` + Find the connected iPhone and install the bundle: ```sh diff --git a/examples/objective-c-macos/generate-ios-icons.sh b/examples/objective-c-macos/generate-ios-icons.sh new file mode 100755 index 00000000..a2a03d42 --- /dev/null +++ b/examples/objective-c-macos/generate-ios-icons.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +source_icon="$project_dir/ios-assets/AppIcon.svg" +logo_source="$project_dir/../../swoole-logo.svg" +output_dir="$project_dir/ios-assets" +master_icon="$output_dir/AppIcon-1024.png" +temporary_dir=$(mktemp -d "${TMPDIR:-/tmp}/typephp-ios-icon.XXXXXX") +temporary_logo="$temporary_dir/swoole-logo.svg" +trap 'rm -rf "$temporary_dir"' EXIT HUP INT TERM + +if ! command -v ffmpeg >/dev/null 2>&1; then + echo "FFmpeg is required to render the SVG app icon." >&2 + exit 1 +fi + +# Override only the SVG viewport size. Its vector paths remain unchanged and +# are rasterized directly at the size used by the 1024px master artwork. +sed 's/width="100px" height="60px"/width="820px" height="282px"/' \ + "$logo_source" > "$temporary_logo" + +ffmpeg -hide_banner -loglevel error -y \ + -i "$source_icon" \ + -i "$temporary_logo" \ + -filter_complex '[0:v][1:v]overlay=102:371:format=auto,format=rgb24' \ + -frames:v 1 \ + "$master_icon" + +for size in 40 58 60 80 87 120 180; do + ffmpeg -hide_banner -loglevel error -y \ + -i "$master_icon" \ + -vf "scale=${size}:${size}:flags=lanczos" \ + -frames:v 1 -pix_fmt rgb24 \ + "$output_dir/AppIcon-${size}.png" +done + +cp "$output_dir/AppIcon-120.png" "$output_dir/AppIcon60x60@2x.png" +cp "$output_dir/AppIcon-180.png" "$output_dir/AppIcon60x60@3x.png" + +echo "Generated iOS icons in $output_dir" diff --git a/examples/objective-c-macos/ios-assets/AppIcon-1024.png b/examples/objective-c-macos/ios-assets/AppIcon-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..f0590f510f4d79a20741e00436b4d235fd555cfb GIT binary patch literal 40497 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 literal 0 HcmV?d00001 diff --git a/examples/objective-c-macos/ios-assets/AppIcon-120.png b/examples/objective-c-macos/ios-assets/AppIcon-120.png new file mode 100644 index 0000000000000000000000000000000000000000..4604ac21d66306363ab6a8d244224b9c5b69ed31 GIT binary patch literal 5342 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 literal 0 HcmV?d00001 diff --git a/examples/objective-c-macos/ios-assets/AppIcon-180.png b/examples/objective-c-macos/ios-assets/AppIcon-180.png new file mode 100644 index 0000000000000000000000000000000000000000..a9af72eace57411a4439913a285b0ddd9e75d88c GIT binary patch literal 8388 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 literal 0 HcmV?d00001 diff --git a/examples/objective-c-macos/ios-assets/AppIcon-40.png b/examples/objective-c-macos/ios-assets/AppIcon-40.png new file mode 100644 index 0000000000000000000000000000000000000000..c66f70d04b966755241e4f6b2547b87d57de676b GIT binary patch literal 1479 zcmV;&1vvVNP)1h@_6O0Vt)f<9n$&1% z(P~0SLu{$|Dw;-l2t?Fsb$w%1#7I#=Er@-RVto`yT7Q^mYn94l5o8~Cciml<-Mf$b zn11(O3cD<;t8L*&JDJ>k_s*H~%{g=CoVmo{qC?Wj|HQ$&-bHmR)w2?2T-4~6I-?A+ z^3Xp{td~`T#^$9gPrU-=MJmW00I&FnAqeP0rhhasOfkD9)dKZvqTGEO1Rw@s9sBo) zm9;zg9s)6hu82ALzUhxmjA8ISnV^OpD5j1yMxr z1tyoMW5t1qk%KxNLbn$wLG*GQr19i^9`3Jw}lgI-t$ zeojtaz1vyc)P3yNE{hvNq> z6c`vLH8nMN( z^81<^fR`ptgt{$1KZrls`9ZDG>Xt(B2ID3>TE`x4-*AN?dyKE-RzUK#nF$!m%E|(M zE;2Ity=8jVpIBn$Xp7MvHu~wfxVSGXD(=Shr(+T7-MjHRZrr${Lx%uT7r(vz zhKJY{k+}R^-Ra<6lWbRh7x#iiSdmT16M!%EefB5j>$;rv9SFZ++ zjEG2>J*UZFGjToJ+~aDP>!ft~idE#VR#8z=S6{DSxP9fSo}u4vH%hVEV1j+v)YmJt zI7qn-53=eW+(8wTv(}}{mq*KO+O%mKHg3eRM36ooHU6&NV(v$b4Q4#l;^L1TD_*l^ zZDmyz<|E^LQC3!R{J2)9`~CLq;^N{nXU}#!Sq!z=ysCq3WJ?SByI&c4lL;^9Fl1JX z)oQibkhWNt3)pV&(peq8>AEjvVj&3kzPu=k!BQqC4>eglpI)<`Y3xSQ6y^NI`=rmn zW(k(7<(eqk-KYbcm*bVf;+3#eFnB>wu;^o%Z}#qAHsqqzQ=>%D$px=Yk6RYyi>-*e zZ77x79B>aAjEjKdkr#oXy!sf1_OdrL*IQq^2m3>u-gqyP7a{{$WQRYpQ~z8(Mo002ovPDHLkV1lGO)_ni~ literal 0 HcmV?d00001 diff --git a/examples/objective-c-macos/ios-assets/AppIcon-58.png b/examples/objective-c-macos/ios-assets/AppIcon-58.png new file mode 100644 index 0000000000000000000000000000000000000000..67a69d79666263728241e7b436619fd8bca98e33 GIT binary patch literal 2274 zcmV<82p#u{P)I97mF>Twh)jTX*>>3

`HZ~=I&t|#?ZK!NNX8?P$y{I zVmQP@;I{F4olsVR7&jWyDm>xB31CvZog{nb{2o*$ARTrSV15&<6yPfP;D$r z`_NNVwcggOvki@lq^U*E_L*({NeRnhurLvwmo}oZ{E`IOGpkeKyMJF;r*n?$<2bcx zDBM;(tL1z5_VsCddW5+g(=K;~(DRz3l^w+T(GAV3;-;wV=A$(loT^oA#U&+Qm6SZJ ztD~y9UR70cFl)O*cUR{M+^GAz=0YsjP(H=jVsxsZ*z7V`FKbn*Uz^U1=}# zQGH;VGALasN>MPMNK$H{0q&Yo&m4woL9+m!F%>xWdKm zK|v25K2#f6_mP1&5hNLO3|(bec{yF^u3fv(0$11{A1`Qk+qP}+GYCI==_O@L>w>H{ z80gHCin2#nWOTlsd8DjX`#4@IUo8KQb>nJ?%*YlIM;u)(DLEN)B_t+74hRVN)7x)D@b~w}aoxIg5fKp(el{nFV@~{f;Z3S5)6)GD(*yGOLOCRE|Dp?slSo*56$l19b5>vAC+Ls?%t9Yr9wk z+_0%zk40a+bO}0MxpF0)?o3)5;wv>-8X9-*+!^q+;Ph8(*W!M)t$OB&3^uUzeDl|B z1|lDmPXckG45h(L-cPax?QY-h zty^#3xkKgn_;|U3v_qp*-x=r5g@=bjm@SU!I+Sc(AQV3 zqHrfpp2X~dfq`^(cy4<~#~KJ zu3gK`!*e(PN@*!to-{OMXJ=n3EJUM9trpUQ-4*T3$9yW2Btdi}Zxxar;j zoG>~UEKW6K0!gew@0^{|G}o~Vz8TFPDOFr$$GnWj@GlxSm8g_G7P=Av^!p?j83j_f zrUL_j{xvMPgfSa5@DtyUv#c+0U*HP$|J`R<64DNotpET307*qoM6N<$f&rjb;Q#;t literal 0 HcmV?d00001 diff --git a/examples/objective-c-macos/ios-assets/AppIcon-60.png b/examples/objective-c-macos/ios-assets/AppIcon-60.png new file mode 100644 index 0000000000000000000000000000000000000000..48a36f4abc94f834f87e5e9981ccc222c5f0fcbc GIT binary patch literal 2382 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 AQ2+n{ literal 0 HcmV?d00001 diff --git a/examples/objective-c-macos/ios-assets/AppIcon-80.png b/examples/objective-c-macos/ios-assets/AppIcon-80.png new file mode 100644 index 0000000000000000000000000000000000000000..a2cc2b0dbf38f69fd68da9cedff020533b6ead14 GIT binary patch literal 3360 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 Tlc2*X00000NkvXXu0mjfGF|e| literal 0 HcmV?d00001 diff --git a/examples/objective-c-macos/ios-assets/AppIcon.svg b/examples/objective-c-macos/ios-assets/AppIcon.svg new file mode 100644 index 00000000..beaa2270 --- /dev/null +++ b/examples/objective-c-macos/ios-assets/AppIcon.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/examples/objective-c-macos/ios-assets/AppIcon60x60@2x.png b/examples/objective-c-macos/ios-assets/AppIcon60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4604ac21d66306363ab6a8d244224b9c5b69ed31 GIT binary patch literal 5342 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 literal 0 HcmV?d00001 diff --git a/examples/objective-c-macos/ios-assets/AppIcon60x60@3x.png b/examples/objective-c-macos/ios-assets/AppIcon60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a9af72eace57411a4439913a285b0ddd9e75d88c GIT binary patch literal 8388 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 literal 0 HcmV?d00001 diff --git a/examples/objective-c-macos/ios-src/uikit_bridge.mm b/examples/objective-c-macos/ios-src/uikit_bridge.mm index 7a9dea96..303a5a74 100644 --- a/examples/objective-c-macos/ios-src/uikit_bridge.mm +++ b/examples/objective-c-macos/ios-src/uikit_bridge.mm @@ -117,6 +117,7 @@ php::Int php_ui_add_label( ? [UIFont systemFontOfSize:static_cast(fontSize) weight:UIFontWeightSemibold] : [UIFont systemFontOfSize:static_cast(fontSize)]; + label.textColor = bold ? UIColor.labelColor : UIColor.secondaryLabelColor; label.textAlignment = NSTextAlignmentCenter; label.numberOfLines = 0; label.adjustsFontSizeToFitWidth = YES; @@ -131,15 +132,31 @@ php::Int php_ui_add_button( php::Int x, php::Int y, php::Int width, - php::Int height) + php::Int height, + php::Int style) { @autoreleasepool { UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; button.frame = logical_rect(x, y, width, height); - [button setTitle:to_ns_string(title) forState:UIControlStateNormal]; - button.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold]; - button.backgroundColor = UIColor.secondarySystemBackgroundColor; - button.layer.cornerRadius = 10; + UIButtonConfiguration *configuration; + if (style == 1) { + configuration = [UIButtonConfiguration filledButtonConfiguration]; + configuration.baseBackgroundColor = UIColor.systemBlueColor; + configuration.baseForegroundColor = UIColor.whiteColor; + } else if (style == 2) { + configuration = [UIButtonConfiguration tintedButtonConfiguration]; + configuration.baseBackgroundColor = UIColor.systemBlueColor; + } else { + configuration = [UIButtonConfiguration grayButtonConfiguration]; + } + configuration.title = to_ns_string(title); + configuration.cornerStyle = UIButtonConfigurationCornerStyleLarge; + configuration.contentInsets = NSDirectionalEdgeInsetsMake(12, 18, 12, 18); + button.configuration = configuration; + button.titleLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightSemibold]; + button.titleLabel.adjustsFontSizeToFitWidth = YES; + button.titleLabel.minimumScaleFactor = 0.75; + button.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail; [button addTarget:bridge action:@selector(controlActivated:) forControlEvents:UIControlEventTouchUpInside]; @@ -156,7 +173,15 @@ void php_ui_set_control_text(php::Int controlId, php::Str text) if ([control isKindOfClass:UILabel.class]) { ((UILabel *) control).text = value; } else if ([control isKindOfClass:UIButton.class]) { - [(UIButton *) control setTitle:value forState:UIControlStateNormal]; + UIButton *button = (UIButton *) control; + UIButtonConfiguration *configuration = button.configuration; + if (configuration != nil) { + configuration.title = value; + button.configuration = configuration; + } else { + [button setTitle:value forState:UIControlStateNormal]; + } + button.accessibilityLabel = value; } } } diff --git a/examples/objective-c-macos/ios-src/uikit_bridge.stub.php b/examples/objective-c-macos/ios-src/uikit_bridge.stub.php index 7ec56f2a..20aea9af 100644 --- a/examples/objective-c-macos/ios-src/uikit_bridge.stub.php +++ b/examples/objective-c-macos/ios-src/uikit_bridge.stub.php @@ -16,7 +16,7 @@ function ui_add_label( bool $bold ): int {} -function ui_add_button(string $title, int $x, int $y, int $width, int $height): int {} +function ui_add_button(string $title, int $x, int $y, int $width, int $height, int $style): int {} function ui_set_control_text(int $controlId, string $text): void {} diff --git a/examples/objective-c-macos/ios.yml b/examples/objective-c-macos/ios.yml index 2d77596f..db3be067 100644 --- a/examples/objective-c-macos/ios.yml +++ b/examples/objective-c-macos/ios.yml @@ -2,7 +2,7 @@ name: typephp_ios_hello version: 0.1.0 mode: bin output: typephp_ios_hello -php-version: "8.5" +php-version: "8.4" cxx-std: c++17 cpp-compiler: xcrun --sdk iphoneos clang++ target-platform: arm64-apple-ios15.0 @@ -22,3 +22,5 @@ ld-flags: - -Wl,-dead_strip - -framework UIKit - -framework Foundation + - -framework CoreGraphics + - -lresolv diff --git a/examples/objective-c-macos/objc-src/macos_bridge.mm b/examples/objective-c-macos/objc-src/macos_bridge.mm index cf12ad88..5ef4e1b8 100644 --- a/examples/objective-c-macos/objc-src/macos_bridge.mm +++ b/examples/objective-c-macos/objc-src/macos_bridge.mm @@ -135,7 +135,8 @@ php::Int php_ui_add_button( php::Int x, php::Int y, php::Int width, - php::Int height) + php::Int height, + php::Int style) { @autoreleasepool { NSButton *button = [NSButton buttonWithTitle:to_ns_string(title) @@ -147,7 +148,13 @@ php::Int php_ui_add_button( static_cast(width), static_cast(height)); button.bezelStyle = NSBezelStyleRounded; - button.keyEquivalent = @"\r"; + button.controlSize = NSControlSizeLarge; + button.font = [NSFont systemFontOfSize:14 weight:NSFontWeightSemibold]; + if (style == 1) { + button.bezelColor = NSColor.controlAccentColor; + button.contentTintColor = NSColor.whiteColor; + button.keyEquivalent = @"\r"; + } button.autoresizingMask = NSViewMinXMargin | NSViewMaxXMargin | NSViewMinYMargin; [bridge.window.contentView addSubview:button]; return static_cast(store_control(button)); diff --git a/examples/objective-c-macos/objc-src/macos_bridge.stub.php b/examples/objective-c-macos/objc-src/macos_bridge.stub.php index 4afc0e9c..dacd8303 100644 --- a/examples/objective-c-macos/objc-src/macos_bridge.stub.php +++ b/examples/objective-c-macos/objc-src/macos_bridge.stub.php @@ -15,7 +15,7 @@ function ui_add_label( bool $bold ): int {} -function ui_add_button(string $title, int $x, int $y, int $width, int $height): int {} +function ui_add_button(string $title, int $x, int $y, int $width, int $height, int $style): int {} function ui_set_control_text(int $controlId, string $text): void {} diff --git a/examples/objective-c-macos/package-ios-app.sh b/examples/objective-c-macos/package-ios-app.sh index 75991178..a1be434b 100755 --- a/examples/objective-c-macos/package-ios-app.sh +++ b/examples/objective-c-macos/package-ios-app.sh @@ -7,6 +7,7 @@ executable="$project_dir/typephp_ios_hello" bundle="$project_dir/dist/TypePHP iOS Hello.app" profile=${TYPEPHP_IOS_PROVISIONING_PROFILE:-} identity=${TYPEPHP_IOS_CODE_SIGN_IDENTITY:-} +bundle_identifier=${TYPEPHP_IOS_BUNDLE_IDENTIFIER:-} if [ ! -x "$executable" ]; then echo "Missing $executable; build ios.yml first." >&2 @@ -33,8 +34,12 @@ plutil -extract Entitlements xml1 -o "$entitlements" "$profile_plist" rm -rf "$bundle" mkdir -p "$bundle" cp "$project_dir/Info-iOS.plist" "$bundle/Info.plist" +if [ -n "$bundle_identifier" ]; then + plutil -replace CFBundleIdentifier -string "$bundle_identifier" "$bundle/Info.plist" +fi cp "$executable" "$bundle/typephp_ios_hello" cp "$profile" "$bundle/embedded.mobileprovision" +cp "$project_dir"/ios-assets/AppIcon*.png "$bundle/" codesign \ --force \ diff --git a/examples/objective-c-macos/php-src/application.php b/examples/objective-c-macos/php-src/application.php index c6afffde..0f104f4d 100644 --- a/examples/objective-c-macos/php-src/application.php +++ b/examples/objective-c-macos/php-src/application.php @@ -2,40 +2,106 @@ final class HelloApplication { + private const BUTTON_PRIMARY = 1; + private const BUTTON_SECONDARY = 2; + private const BUTTON_TERTIARY = 3; + + private static int $titleLabel = 0; + private static int $descriptionLabel = 0; private static int $statusLabel = 0; - private static int $button = 0; + private static int $countButton = 0; + private static int $resetButton = 0; + private static int $languageButton = 0; private static int $clickCount = 0; + private static bool $isChinese = false; public static function build(string $platformApi): void { - ui_create_window('TypePHP Native Hello', 640, 380); - ui_add_label('TypePHP + Objective-C++', 44, 292, 552, 40, 28, true); - ui_add_label( - "The interface and application state are implemented in TypePHP.\n" . - "Objective-C++ only provides thin wrappers around $platformApi.", - 64, - 178, - 512, - 88, - 15, - false, - ); - - self::$statusLabel = ui_add_label('Waiting for a native UI event.', 64, 130, 512, 24, 13, false); - self::$button = ui_add_button('Send event to TypePHP', 210, 64, 220, 40); + if ($platformApi === 'UIKit') { + self::buildPhoneLayout(); + } else { + self::buildDesktopLayout(); + } + self::renderText(); } public static function handleEvent(int $controlId): void { - if ($controlId !== self::$button) { + if ($controlId === self::$languageButton) { + self::$isChinese = !self::$isChinese; + self::renderText(); + return; + } + + if ($controlId === self::$resetButton) { + self::$clickCount = 0; + self::renderStatus(); + return; + } + + if ($controlId !== self::$countButton) { return; } self::$clickCount++; - $suffix = self::$clickCount === 1 ? 'time' : 'times'; - ui_set_control_text( - self::$statusLabel, - 'Button clicked ' . self::$clickCount . " $suffix; handled by TypePHP.", - ); + self::renderStatus(); + } + + private static function buildPhoneLayout(): void + { + ui_create_window('TypePHP Native Hello', 390, 844); + self::$titleLabel = ui_add_label('', 24, 672, 342, 76, 30, true); + self::$descriptionLabel = ui_add_label('', 30, 544, 330, 104, 16, false); + self::$statusLabel = ui_add_label('', 30, 454, 330, 58, 18, true); + self::$countButton = ui_add_button('', 30, 354, 330, 58, self::BUTTON_PRIMARY); + self::$resetButton = ui_add_button('', 30, 280, 158, 52, self::BUTTON_SECONDARY); + self::$languageButton = ui_add_button('', 202, 280, 158, 52, self::BUTTON_TERTIARY); + } + + private static function buildDesktopLayout(): void + { + ui_create_window('TypePHP Native Hello', 640, 420); + self::$titleLabel = ui_add_label('', 44, 336, 552, 40, 28, true); + self::$descriptionLabel = ui_add_label('', 64, 222, 512, 82, 15, false); + self::$statusLabel = ui_add_label('', 64, 164, 512, 30, 16, true); + self::$countButton = ui_add_button('', 160, 92, 320, 46, self::BUTTON_PRIMARY); + self::$resetButton = ui_add_button('', 160, 34, 150, 40, self::BUTTON_SECONDARY); + self::$languageButton = ui_add_button('', 330, 34, 150, 40, self::BUTTON_TERTIARY); + } + + private static function renderText(): void + { + if (self::$isChinese) { + ui_set_control_text(self::$titleLabel, 'TypePHP 原生应用'); + ui_set_control_text( + self::$descriptionLabel, + "界面逻辑和应用状态均由 TypePHP 实现。\nObjective-C++ 仅提供轻量的原生 UI 桥接。", + ); + ui_set_control_text(self::$countButton, '点击计数'); + ui_set_control_text(self::$resetButton, '重置'); + ui_set_control_text(self::$languageButton, 'English'); + } else { + ui_set_control_text(self::$titleLabel, 'TypePHP Native App'); + ui_set_control_text( + self::$descriptionLabel, + "Application logic and state are implemented in TypePHP.\n" . + 'Objective-C++ provides only a thin native UI bridge.', + ); + ui_set_control_text(self::$countButton, 'Count with TypePHP'); + ui_set_control_text(self::$resetButton, 'Reset'); + ui_set_control_text(self::$languageButton, '中文'); + } + self::renderStatus(); + } + + private static function renderStatus(): void + { + if (self::$isChinese) { + ui_set_control_text(self::$statusLabel, '已点击 ' . self::$clickCount . ' 次'); + return; + } + + $suffix = self::$clickCount === 1 ? 'click' : 'clicks'; + ui_set_control_text(self::$statusLabel, self::$clickCount . " $suffix"); } } diff --git a/phpunit/code/local-native-closure-codegen.php b/phpunit/code/local-native-closure-codegen.php new file mode 100644 index 00000000..46adc729 --- /dev/null +++ b/phpunit/code/local-native-closure-codegen.php @@ -0,0 +1,23 @@ + $base + $value; + var_dump($direct(2)); + + $escaped = static fn (int $value): int => $value + 1; + array_map($escaped, [1]); + + $dynamic = std::any(1); + $dynamicRef = function () use (&$dynamic): void { + $dynamic++; + }; + $dynamicRef(); +} diff --git a/phpunit/src/Build/PrecompiledHeaderManagerTest.php b/phpunit/src/Build/PrecompiledHeaderManagerTest.php index 72df23b3..7af56606 100644 --- a/phpunit/src/Build/PrecompiledHeaderManagerTest.php +++ b/phpunit/src/Build/PrecompiledHeaderManagerTest.php @@ -62,6 +62,39 @@ final class PrecompiledHeaderManagerTest extends TestCase $this->assertCount(8, $managedEntries); } + public function testDependencyMtimeChangeInvalidatesCachedArtifact(): void + { + $dependencyDirectory = $this->cacheDirectory . '/dependencies'; + mkdir($dependencyDirectory); + $dependency = $dependencyDirectory . '/runtime.h'; + file_put_contents($dependency, "#pragma once\n"); + + $backend = $this->createMock(CompilerBackend::class); + $backend->method('supportsPrecompiledHeaders')->willReturn(true); + $backend->method('getName')->willReturn('test'); + $backend->method('getCompilerCommand')->willReturn('true'); + $backend->method('getPrecompiledHeaderArtifact') + ->willReturnCallback(static fn(string $header): string => $header . '.gch'); + $backend->method('buildNativeCompileCommand') + ->willReturnCallback( + static fn(string $source, string $object): string => 'touch ' . escapeshellarg($object), + ); + + $manager = new PrecompiledHeaderManager($backend, new NativeBuilder($backend)); + $options = new CompileOptions([]); + $first = $manager->prepare(['runtime.h'], [$dependencyDirectory], $this->cacheDirectory, $options); + $cached = $manager->prepare(['runtime.h'], [$dependencyDirectory], $this->cacheDirectory, $options); + + touch($dependency, filemtime($dependency) + 10); + clearstatcache(true, $dependency); + $refreshed = $manager->prepare(['runtime.h'], [$dependencyDirectory], $this->cacheDirectory, $options); + + $this->assertFalse($first['cached']); + $this->assertTrue($cached['cached']); + $this->assertFalse($refreshed['cached']); + $this->assertNotSame($first['artifact'], $refreshed['artifact']); + } + private function createCacheEntry(int $number, int $mtime): string { $directory = $this->cacheDirectory . '/' . sprintf('%024x', $number); diff --git a/phpunit/src/ClosureTest.php b/phpunit/src/ClosureTest.php index 5dd4c845..4f99fa25 100644 --- a/phpunit/src/ClosureTest.php +++ b/phpunit/src/ClosureTest.php @@ -17,8 +17,9 @@ class ClosureTest extends \BaseTest $code = file_get_contents($generated); self::assertIsString($code); - self::assertSame(3, substr_count($code, 'php::newClosureWithParameters(')); - self::assertSame(3, substr_count($code, 'php::ClosureStrictTypes::Enabled')); + self::assertStringContainsString('auto copy = [arr = arr]() mutable -> php::Var {', $code); + self::assertSame(2, substr_count($code, 'php::newClosureWithParameters(')); + self::assertSame(2, substr_count($code, 'php::ClosureStrictTypes::Enabled')); } public function testClosureRebindingIsRejectedAtCompileTime(): void diff --git a/phpunit/src/FixedReferenceStorageTest.php b/phpunit/src/FixedReferenceStorageTest.php index 17e2058e..bffe79b4 100644 --- a/phpunit/src/FixedReferenceStorageTest.php +++ b/phpunit/src/FixedReferenceStorageTest.php @@ -16,14 +16,30 @@ final class FixedReferenceStorageTest extends BaseTest public static function fixedStorageProvider(): iterable { - yield 'string' => ['fixed-reference-string.php', 'php::Str']; - yield 'array' => ['fixed-reference-array.php', 'php::Array']; yield 'object' => ['fixed-reference-generic-object.php', 'php::Object']; yield 'typed object' => ['fixed-reference-object.php', 'php::Object']; yield 'stream' => ['fixed-reference-stream.php', 'php::Stream']; yield 'std container' => ['fixed-reference-std-container.php', 'php::StdVector']; } + /** @dataProvider localClosureFixedReferenceProvider */ + public function testNonEscapingLocalClosureUsesNativeReferenceCapture( + string $fixture, + string $capture, + ): void + { + $code = $this->compileFixture($fixture); + + self::assertStringContainsString($capture, $code); + self::assertStringNotContainsString('php::newClosureWithParameters(', $code); + } + + public static function localClosureFixedReferenceProvider(): iterable + { + yield 'string' => ['fixed-reference-string.php', 'auto closure = [&value]() mutable -> php::Var {']; + yield 'array' => ['fixed-reference-array.php', 'auto closure = [&value]() mutable -> php::Var {']; + } + public function testFixedStorageUsesNativeReferenceForKnownTypedParameter(): void { $code = $this->compileFixture('fixed-reference-argument.php'); diff --git a/phpunit/src/LocalClosureCodegenTest.php b/phpunit/src/LocalClosureCodegenTest.php new file mode 100644 index 00000000..5c271f07 --- /dev/null +++ b/phpunit/src/LocalClosureCodegenTest.php @@ -0,0 +1,41 @@ +addFiles([$source]); + $compiler->prepareFile($source); + $generated = $compiler->convertFile($source); + $code = file_get_contents($generated); + + self::assertIsString($code); + self::assertStringContainsString( + 'auto direct = [base = base](php::Var value) mutable -> php::Var {', + $code, + ); + self::assertStringContainsString('direct(2L)', $code); + self::assertStringNotContainsString('typephp_call_cached(direct', $code); + + // Escaped values and dynamic references remain real Zend Closures. + self::assertSame(2, substr_count($code, 'php::newClosureWithParameters(')); + self::assertStringContainsString('typephp_call_cached(dynamicRef', $code); + } +} diff --git a/phpunit/src/VarIntTypesTest.php b/phpunit/src/VarIntTypesTest.php index 3059f69b..faae9d3e 100644 --- a/phpunit/src/VarIntTypesTest.php +++ b/phpunit/src/VarIntTypesTest.php @@ -71,17 +71,18 @@ final class VarIntTypesTest extends BaseTest } } - public function testNativeScalarCannotBePromotedForReferenceCapture(): void + public function testNativeScalarUsesDirectReferenceCaptureInNonEscapingClosure(): void { - $this->expectException(TestError::class); - $this->expectExceptionMessage( - 'Cannot create a reference to variable $changed of fixed type php::Bool; initialize it with std::any()', - ); - - $this->compileSource( + $code = $this->compileSource( $this->createCompiler(), TYPEPHP_ROOT_PATH . '/phpunit/code/native-scalar-reference-capture.php', ); + + self::assertStringContainsString( + 'auto set = [&changed]() mutable -> php::Var {', + $code, + ); + self::assertStringNotContainsString('php::newClosureWithParameters(', $code); } public function testExplicitAnySupportsReferenceCapture(): void diff --git a/src/Analysis/LocalClosureAnalyzer.php b/src/Analysis/LocalClosureAnalyzer.php new file mode 100644 index 00000000..da88b509 --- /dev/null +++ b/src/Analysis/LocalClosureAnalyzer.php @@ -0,0 +1,222 @@ + */ + private array $candidates = []; + + /** @var array */ + private array $invalid = []; + + /** @var array */ + private array $defined = []; + + /** + * @param list $statements + * @return array + */ + public function analyze(array $statements): array + { + $this->candidates = []; + $this->invalid = []; + $this->defined = []; + + $duplicateNames = []; + foreach ($statements as $statement) { + if (!$statement instanceof Stmt\Expression + || !$statement->expr instanceof Expr\Assign + || !$statement->expr->var instanceof Expr\Variable + || !is_string($statement->expr->var->name) + || (!$statement->expr->expr instanceof Expr\Closure + && !$statement->expr->expr instanceof Expr\ArrowFunction) + ) { + continue; + } + + $name = $statement->expr->var->name; + if (isset($this->candidates[$name])) { + $duplicateNames[$name] = true; + continue; + } + if (!$this->isSupportedClosure($statement->expr->expr)) { + continue; + } + $this->candidates[$name] = [ + 'assignment' => $statement->expr, + 'closure' => $statement->expr->expr, + 'calls' => 0, + ]; + } + + foreach ($duplicateNames as $name => $_) { + unset($this->candidates[$name]); + } + if ($this->candidates === []) { + return []; + } + + foreach ($statements as $statement) { + $this->scanNode($statement); + } + + foreach ($this->candidates as $name => $candidate) { + if (isset($this->invalid[$name]) || $candidate['calls'] === 0) { + unset($this->candidates[$name]); + } + } + return $this->candidates; + } + + private function isSupportedClosure(Expr\Closure|Expr\ArrowFunction $closure): bool + { + if ($closure->byRef) { + return false; + } + if ($closure->returnType instanceof Node\Identifier + && strtolower($closure->returnType->name) === 'never' + ) { + return false; + } + foreach ($closure->params as $parameter) { + if ($parameter->byRef || $parameter->variadic || $parameter->default !== null) { + return false; + } + } + $body = $closure instanceof Expr\ArrowFunction ? $closure->expr : $closure->stmts; + return !$this->containsUnsupportedClosureNode($body, false); + } + + private function containsUnsupportedClosureNode(mixed $value, bool $root = true): bool + { + foreach (is_array($value) ? $value : [$value] as $node) { + if (!$node instanceof Node) { + continue; + } + if (!$root && $node instanceof FunctionLike) { + return true; + } + if ($node instanceof Expr\Yield_ + || $node instanceof Expr\YieldFrom + || $node instanceof Stmt\Static_ + || $node instanceof Stmt\Global_ + ) { + return true; + } + if ($node instanceof Expr\FuncCall && $node->name instanceof Node\Name) { + $name = strtolower(ltrim($node->name->toString(), '\\')); + if (in_array($name, [ + 'func_get_arg', + 'func_get_args', + 'func_num_args', + 'debug_backtrace', + 'debug_print_backtrace', + ], true)) { + return true; + } + } + foreach ($node->getSubNodeNames() as $field) { + if ($this->containsUnsupportedClosureNode($node->{$field}, false)) { + return true; + } + } + } + return false; + } + + private function scanNode( + mixed $value, + ?Node $parent = null, + string $parentField = '', + int $functionDepth = 0, + ): void { + foreach (is_array($value) ? $value : [$value] as $node) { + if (!$node instanceof Node) { + continue; + } + + // Textual order is not a dominance proof in the presence of goto: + // a jump may bypass the lambda initialization or re-enter its + // scope. Keep all such functions on the Zend Closure path. + if ($node instanceof Stmt\Goto_ || $node instanceof Stmt\Label) { + foreach ($this->candidates as $name => $_candidate) { + $this->invalid[$name] = true; + } + return; + } + + if ($node instanceof Expr\Variable && is_string($node->name)) { + $this->classifyVariableUse($node->name, $parent, $parentField, $functionDepth); + } + + $childFunctionDepth = $functionDepth + ($node instanceof FunctionLike ? 1 : 0); + foreach ($node->getSubNodeNames() as $field) { + $this->scanNode($node->{$field}, $node, $field, $childFunctionDepth); + } + } + } + + private function classifyVariableUse( + string $name, + ?Node $parent, + string $parentField, + int $functionDepth, + ): void { + if (!isset($this->candidates[$name]) || isset($this->invalid[$name])) { + return; + } + + $candidate = $this->candidates[$name]; + if ($parent instanceof Expr\Assign && $parentField === 'var') { + if ($parent === $candidate['assignment'] && !isset($this->defined[$name])) { + $this->defined[$name] = true; + return; + } + $this->invalid[$name] = true; + return; + } + + if ($functionDepth !== 0 + || !$parent instanceof Expr\FuncCall + || $parentField !== 'name' + || $parent->isFirstClassCallable() + || !isset($this->defined[$name]) + || !$this->isSupportedDirectCall($parent, count($candidate['closure']->params)) + ) { + $this->invalid[$name] = true; + return; + } + + $this->candidates[$name]['calls']++; + } + + private function isSupportedDirectCall(Expr\FuncCall $call, int $parameterCount): bool + { + if (count($call->args) !== $parameterCount) { + return false; + } + foreach ($call->args as $argument) { + if (!$argument instanceof Node\Arg || $argument->unpack || $argument->name !== null) { + return false; + } + } + return true; + } +} diff --git a/src/Build/PrecompiledHeaderManager.php b/src/Build/PrecompiledHeaderManager.php index 21549ac6..de403bc9 100644 --- a/src/Build/PrecompiledHeaderManager.php +++ b/src/Build/PrecompiledHeaderManager.php @@ -149,7 +149,15 @@ final readonly class PrecompiledHeaderManager } sort($files, SORT_STRING); foreach ($files as $file) { - hash_update($context, $file . "\0"); + $metadata = stat($file); + if ($metadata === false) { + throw new \RuntimeException('Cannot stat precompiled-header dependency: ' . $file); + } + // Clang records dependency mtimes in a PCH and rejects the PCH even + // when a rewritten header still has identical contents. Include + // the metadata as well as the content so such SDK refreshes select + // a new cache entry before native compilation begins. + hash_update($context, $file . "\0" . $metadata['size'] . "\0" . $metadata['mtime'] . "\0"); if (!hash_update_file($context, $file)) { throw new \RuntimeException('Cannot fingerprint precompiled-header dependency: ' . $file); } diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 3ef2a3a8..4e841d09 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -5184,6 +5184,9 @@ class CompilerBase implements PropertyAccessContext { $code = ''; foreach ($localVars as $name => $type) { + if (isset($this->context->nativeLocalClosures[$name])) { + continue; + } if (isset($this->context->arguments[$name])) { continue; } diff --git a/src/Context/FunctionContext.php b/src/Context/FunctionContext.php index 0953ecdb..a4e52591 100644 --- a/src/Context/FunctionContext.php +++ b/src/Context/FunctionContext.php @@ -72,6 +72,16 @@ class FunctionContext public array $explicitNativeTypeVars = []; /** @var array C++ initializers folded into function-scope local declarations. */ public array $localVarInitializers = []; + /** + * Proven non-escaping local Closure candidates. These are declaration-site + * plans only; the generator moves a successfully lowered entry into + * nativeLocalClosures when it emits the concrete C++ lambda. + * + * @var array + */ + public array $localClosureCandidates = []; + /** @var array Local variables already emitted as concrete C++ lambdas. */ + public array $nativeLocalClosures = []; /** @var array typed-ref local => directly referenced local. */ public array $typedRefBindings = []; /** @var array typed-ref local => canonical fixed-storage root. */ @@ -137,6 +147,8 @@ class FunctionContext $this->localVars = []; $this->explicitNativeTypeVars = []; $this->localVarInitializers = []; + $this->localClosureCandidates = []; + $this->nativeLocalClosures = []; $this->typedRefBindings = []; $this->typedRefRoots = []; $this->typedRefAliases = []; @@ -196,6 +208,8 @@ class FunctionContext { $this->localVars = $localVars; $this->localVarInitializers = []; + $this->localClosureCandidates = []; + $this->nativeLocalClosures = []; $this->typedRefBindings = []; $this->typedRefRoots = []; $this->typedRefAliases = []; diff --git a/src/Generator/ClosureGenerator.php b/src/Generator/ClosureGenerator.php index 9826bf1f..e1f130eb 100644 --- a/src/Generator/ClosureGenerator.php +++ b/src/Generator/ClosureGenerator.php @@ -53,6 +53,12 @@ trait ClosureGenerator } protected function parseArrowFunction(Expr\ArrowFunction $expr): string + { + return $this->genClosure($expr, $expr->params, $this->collectArrowFunctionUses($expr)); + } + + /** @return list */ + private function collectArrowFunctionUses(Expr\ArrowFunction $expr): array { $nodeFinder = new NodeFinder(); $vars = $nodeFinder->findInstanceOf($expr->expr, Variable::class); @@ -75,9 +81,7 @@ trait ClosureGenerator } $uses[$varName] = new Node\ClosureUse($var); } - $uses = array_values($uses); - - return $this->genClosure($expr, $expr->params, $uses); + return array_values($uses); } protected function parseClosure(Expr\Closure $expr): string @@ -85,6 +89,243 @@ trait ClosureGenerator return $this->genClosure($expr, $expr->params, $expr->uses); } + /** + * Lower a proven non-escaping local Closure at its PHP creation site. + * Returning null deliberately selects the ordinary Zend Closure path. + */ + protected function parseNativeLocalClosureAssignment(Expr\Assign $assign): ?string + { + if (!$this->isVarExpr($assign->var) || !is_string($assign->var->name)) { + return null; + } + + $sourceName = $assign->var->name; + $name = $this->parseIdentifier($assign->var); + $candidate = $this->context->localClosureCandidates[$sourceName] ?? null; + if ($candidate === null || $candidate['assignment'] !== $assign) { + return null; + } + + $expr = $candidate['closure']; + $uses = $expr instanceof Expr\ArrowFunction + ? $this->collectArrowFunctionUses($expr) + : $expr->uses; + $capturePlan = $this->buildNativeLocalClosureCapturePlan($uses); + if ($capturePlan === null) { + return null; + } + + // The local lambda no longer crosses a Zend boundary, but its declared + // PHP signature remains observable at each direct call. + foreach ($expr->params as $param) { + if (!$param->type instanceof Node\Name) { + $this->resolveTypeDecl($param->type, self::DECL_TYPE_OF_PARAM); + } + } + if (!$expr->returnType instanceof Node\Name) { + $this->resolveTypeDecl($expr->returnType, self::DECL_TYPE_OF_RETURN); + } + + $entryContext = $this->context; + $entryIndent = $this->indentLevel; + $entryInGeneratorBody = $this->inGeneratorBody; + $parameters = []; + foreach ($expr->params as $param) { + $parameters[] = Type::VAR . ' ' . $this->parseIdentifier($param->var); + } + + $code = 'auto ' . $name . ' = [' . implode(', ', $capturePlan['cpp']) . '](' + . implode(', ', $parameters) . ') mutable -> ' . Type::VAR . ' {' . PHP_EOL; + + try { + $this->context = new FunctionContext(); + $this->context->inClosure = true; + $this->inGeneratorBody = false; + $this->indentLevel = $entryIndent + 1; + + $returnType = $expr->returnType; + $returnTypeName = $returnType instanceof Node\Identifier + ? strtolower($returnType->name) + : ''; + if ($returnType !== null && $returnTypeName !== 'void') { + $returnTypeInfo = $this->buildTypeCheckFromNode($returnType, true); + if (!empty($returnTypeInfo['check'])) { + $this->context->closureReturnTypeCheck = $returnTypeInfo['check']; + $this->context->closureReturnTypeStr = $returnTypeInfo['typeStr']; + } + } + + $parameterChecks = ''; + foreach ($expr->params as $index => $param) { + $paramName = $this->parseIdentifier($param->var); + $this->addArgument($paramName, Type::VAR); + if (CompileTimeAttribute::consume($param, 'Immutable')) { + $this->context->immutableVars[$paramName] = true; + if ($this->immutableTypeNodeMayBeObject($param->type)) { + $this->context->immutableObjectVars[$paramName] = true; + } + if ($param->type !== null) { + [, $parameterClass] = $this->resolveTypeDecl( + $param->type, + self::DECL_TYPE_OF_PARAM, + ); + if ($parameterClass !== '') { + $this->addObject($paramName, $parameterClass); + } + } + } + $parameterChecks .= $this->genNativeLocalClosureParamTypeCheck($param, $paramName, $index); + } + + foreach ($capturePlan['bindings'] as $binding) { + $this->addArgument($binding['name'], $binding['type']); + if ($binding['class'] !== '') { + $this->addObject($binding['name'], $binding['class']); + } + if ($binding['immutable']) { + $this->context->immutableVars[$binding['name']] = true; + if ($binding['immutableObject']) { + $this->context->immutableObjectVars[$binding['name']] = true; + } + } + } + + $body = $this->genClosureBody($expr); + if ($this->context->needsUserCodeCallableScope) { + $body = $this->genUserCodeCallableScopeGuard() . $body; + } + $code .= $this->genScopeVarDecl() . $parameterChecks . $body; + if (!str_ends_with($code, PHP_EOL)) { + $code .= PHP_EOL; + } + $code .= $this->getIndent(0) . '}'; + } finally { + $this->context = $entryContext; + $this->indentLevel = $entryIndent; + $this->inGeneratorBody = $entryInGeneratorBody; + } + + $this->addLocalVar($name, Type::OBJECT); + $this->context->nativeLocalClosures[$name] = true; + return $code; + } + + /** + * @param list $uses + * @return array{ + * cpp: list, + * bindings: list + * }|null + */ + private function buildNativeLocalClosureCapturePlan(array $uses): ?array + { + $cpp = []; + $bindings = []; + foreach ($uses as $useItem) { + if (!$this->isVarExpr($useItem->var) || !is_string($useItem->var->name)) { + return null; + } + $name = $this->parseIdentifier($useItem->var); + if (!$this->hasLocalVar($name) + || $this->isNativeObjectVar($name) + || $this->isStdContainer($name) + ) { + return null; + } + + $rawType = $this->getRawVarType($name); + $valueType = Type::getReferencedType($rawType); + if ($useItem->byRef) { + $captureType = Type::getReferenceType($valueType); + if ($captureType === null) { + return null; + } + $cpp[] = '&' . $name; + } else { + if ($rawType === Type::REF || !in_array($valueType, [ + Type::VAR, + Type::BOOL, + Type::INT, + Type::FLOAT, + Type::OBJECT, + Type::ARRAY, + Type::STR, + ], true)) { + return null; + } + $captureType = $valueType; + $cpp[] = $name . ' = ' . $name; + } + + $bindings[] = [ + 'name' => $name, + 'type' => $captureType, + 'class' => $valueType === Type::OBJECT ? $this->getDeclaredObjectType($name) : '', + 'immutable' => isset($this->context->immutableVars[$name]), + 'immutableObject' => isset($this->context->immutableObjectVars[$name]), + ]; + } + return ['cpp' => $cpp, 'bindings' => $bindings]; + } + + private function genNativeLocalClosureParamTypeCheck(Node\Param $param, string $var, int $index): string + { + if ($param->type === null) { + return ''; + } + $typeInfo = $this->buildTypeCheckFromNode($param->type, true); + if (empty($typeInfo['check'])) { + return ''; + } + + $argInfo = new ArgInfo(); + $argInfo->name = $var; + $argInfo->phpName = is_string($param->var->name) + ? $param->var->name + : $this->unescapeVarName($var); + $argInfo->type = Type::VAR; + $argInfo->typeCheck = $typeInfo['check']; + $argInfo->typeStr = $typeInfo['typeStr']; + $argInfo->typeNode = $param->type; + return $this->genClosureParamCheck($argInfo, $index); + } + + protected function parseNativeLocalClosureCall(Expr\FuncCall $expr, string $name): ?string + { + if (!isset($this->context->nativeLocalClosures[$name])) { + return null; + } + + $arguments = []; + $forceMaterialize = count($expr->args) > 1; + foreach ($expr->args as $argument) { + $this->assertExprCanBeUsedAsValue($argument->value, 'function argument'); + if ($this->isVarExpr($argument->value)) { + $this->assertStdContainerDoesNotEscapeNativeObjects( + $argument, + $this->parseIdentifier($argument->value), + ); + } + $class = $this->detectClassOfExpr($argument->value); + if ($class !== '' && $this->isNativeObjectClass($class)) { + $this->fatalError( + $argument, + 'Native objects cannot cross a local Closure php::Var parameter boundary', + ); + } + + if ($this->isVarExpr($argument->value) + && $this->isStdContainer($this->parseIdentifier($argument->value)) + ) { + $value = $this->parseOrderedArg($argument); + } else { + $value = $this->parseOrderedOperand($argument->value, false, $forceMaterialize); + } + $arguments[] = $this->materializeCallArgValue($argument->value, $value); + } + return $name . '(' . implode(', ', $arguments) . ')'; + } + protected function isReturnStmtInLastLine(array $stmts): bool { if (count($stmts) === 0) { diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index 3dc784d0..e87ab9f6 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -172,6 +172,11 @@ trait AssignOpTrait protected function parseAssign(Expr\Assign $v): string { + $nativeClosure = $this->parseNativeLocalClosureAssignment($v); + if ($nativeClosure !== null) { + return $nativeClosure; + } + $left = $v->var; $right = $v->expr; if ($this->isAssignExpr($right)) { diff --git a/src/Parser/FunctionCallTrait.php b/src/Parser/FunctionCallTrait.php index 6e482f68..eb202b43 100644 --- a/src/Parser/FunctionCallTrait.php +++ b/src/Parser/FunctionCallTrait.php @@ -83,6 +83,14 @@ trait FunctionCallTrait return $pythonObjectCall; } + if ($this->isVarExpr($expr->name) && is_string($expr->name->name)) { + $localName = $this->parseIdentifier($expr->name); + $nativeClosureCall = $this->parseNativeLocalClosureCall($expr, $localName); + if ($nativeClosureCall !== null) { + return $nativeClosureCall; + } + } + $callableClass = $this->detectClassOfExpr($expr->name); if ($this->isNativeObjectClass($callableClass)) { if ($expr->isFirstClassCallable()) { diff --git a/src/Translator.php b/src/Translator.php index 1feb84fb..00f1c54b 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -11,6 +11,7 @@ namespace TypePhp; use Ajaxray\AnsiKit\AnsiTerminal; use Ajaxray\AnsiKit\Components\Progressbar; use MJS\TopSort\Implementations\StringSort; +use TypePhp\Analysis\LocalClosureAnalyzer; use TypePhp\Analysis\SsaBuilder; use TypePhp\Backend\CompilerFactory; use TypePhp\Build\CompileOptions; @@ -5088,6 +5089,10 @@ CODE; } } + if ($v->stmts && !$this->class && $this->methodDef === null) { + $this->context->localClosureCandidates = (new LocalClosureAnalyzer())->analyze($v->stmts); + } + $stmts = ''; $this->indentLevel++; try { diff --git a/tests/compiler/closure/local-native-closure.phpt b/tests/compiler/closure/local-native-closure.phpt new file mode 100644 index 00000000..a51da208 --- /dev/null +++ b/tests/compiler/closure/local-native-closure.phpt @@ -0,0 +1,68 @@ +--TEST-- +Non-escaping local Closures preserve captures, typed references, checks and argument order +--FILE-- + $left . ':' . $right; + var_dump($pair($next(), $next()), $counter); + + $typed = static fn (int $value): int => $value; + try { + $typed(std::any('bad')); + } catch (TypeError $error) { + echo "parameter type checked\n"; + } + + $badReturn = static fn (): int => std::any('bad'); + try { + $badReturn(); + } catch (TypeError $error) { + echo "return type checked\n"; + } +} +?> +--EXPECT-- +int(12) +int(13) +int(100) +int(2) +string(2) "a!" +float(2) +bool(false) +array(1) { + [0]=> + int(2) +} +string(3) "0:1" +int(2) +parameter type checked +return type checked