- Add forceArrayArgs parameter to CallArgumentGenerator methods to properly handle named arguments with setValue method instead of set - Implement direct foreach array target optimization to improve iteration performance by allowing direct assignment to variant variables - Add comprehensive tests for dynamic call cache argument preservation including small, large, named, unpacked, reference, and exception arguments - Add tests for dynamic property access with string, referenced-string, and converted names - Add tests for foreach direct array targets preserving keys, values, references - Update CI workflows to checkout PHPX from third_party directory instead of vendor - Add smoke test for full static builds to verify module globals setup - Enhance benchmark suite with additional dynamic call cases for different argument counts (zero, two, four args) to separate cache lookup from argument materialization costs - Add case filtering option to propertymaster
parent
dd4cd6d162
commit
5689170839
21 changed files with 478 additions and 68 deletions
@ -0,0 +1,43 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/** |
||||||
|
* Smoke test for --full-static builds. |
||||||
|
* |
||||||
|
* The fully-static artifact embeds its own PHP runtime from the bundled SDK, so |
||||||
|
* this exercises the parts of that runtime most likely to break: the module |
||||||
|
* globals set up during php_module_startup (pcre was where a musl/glibc thread- |
||||||
|
* local storage mismatch used to crash), plus the usual string/array/JSON paths. |
||||||
|
* |
||||||
|
* The output is a fixed token so both the x64 and arm64 workflows can compare |
||||||
|
* it exactly, regardless of the matrix PHP version installed on the runner. |
||||||
|
*/ |
||||||
|
|
||||||
|
function requireFullStatic(bool $condition, string $message): void |
||||||
|
{ |
||||||
|
if (!$condition) { |
||||||
|
throw new RuntimeException($message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
// pcre reads its compile context from a ZTS module global allocated during |
||||||
|
// php_module_startup; it is the first thing to break when the TLS layout is |
||||||
|
// inconsistent with the C runtime that initialised the thread pointer. |
||||||
|
requireFullStatic(preg_match('/^(\d+)\.(\d+)/', PHP_VERSION, $m) === 1, 'PHP_VERSION did not match'); |
||||||
|
|
||||||
|
$words = ['typephp', 'aot', 'static']; |
||||||
|
sort($words); |
||||||
|
requireFullStatic(implode(',', $words) === 'aot,static,typephp', 'sort() failed'); |
||||||
|
|
||||||
|
requireFullStatic(strtoupper('musl') === 'MUSL', 'strtoupper() failed'); |
||||||
|
requireFullStatic(json_encode(['ok' => true]) === '{"ok":true}', 'json_encode() failed'); |
||||||
|
|
||||||
|
$sum = 0; |
||||||
|
for ($i = 0; $i < 100000; $i++) { |
||||||
|
$sum += $i; |
||||||
|
} |
||||||
|
requireFullStatic($sum === 4999950000, 'loop result mismatch'); |
||||||
|
|
||||||
|
echo "full-static-smoke-ok\n"; |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
name: full-static-smoke |
||||||
|
mode: bin |
||||||
|
build-dir: build |
||||||
|
output: full_static_smoke |
||||||
|
cxx-std: c++17 |
||||||
|
|
||||||
|
sources: |
||||||
|
- main.php |
||||||
@ -0,0 +1,51 @@ |
|||||||
|
--TEST-- |
||||||
|
Dynamic call cache preserves small, large, named, unpacked, reference, and exception arguments |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function cached_sum(int $a, int $b, int $c, int $d, int $e = 0): int |
||||||
|
{ |
||||||
|
return $a + $b + $c + $d + $e; |
||||||
|
} |
||||||
|
|
||||||
|
function cached_increment(int &$value): int |
||||||
|
{ |
||||||
|
return ++$value; |
||||||
|
} |
||||||
|
|
||||||
|
function cached_throw(string $message): never |
||||||
|
{ |
||||||
|
throw new RuntimeException($message); |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$sum = 'cached_sum'; |
||||||
|
var_dump($sum(1, 2, 3, 4)); |
||||||
|
var_dump($sum(1, 2, 3, 4, 5)); |
||||||
|
var_dump($sum(d: 4, c: 3, b: 2, a: 1)); |
||||||
|
|
||||||
|
$arguments = [1, 2, 3, 4, 5]; |
||||||
|
var_dump($sum(...$arguments)); |
||||||
|
|
||||||
|
$increment = 'cached_increment'; |
||||||
|
$value = 10; |
||||||
|
var_dump($increment(refval($value))); |
||||||
|
var_dump($value); |
||||||
|
|
||||||
|
$throw = 'cached_throw'; |
||||||
|
try { |
||||||
|
$throw('cached failure'); |
||||||
|
} catch (RuntimeException $exception) { |
||||||
|
echo $exception->getMessage(), "\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(10) |
||||||
|
int(15) |
||||||
|
int(10) |
||||||
|
int(15) |
||||||
|
int(11) |
||||||
|
int(11) |
||||||
|
cached failure |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
--TEST-- |
||||||
|
foreach direct array targets preserve keys, values, references, and assignment order |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
function collect(array $values): array |
||||||
|
{ |
||||||
|
$result = []; |
||||||
|
foreach ($values as $key => $value) { |
||||||
|
$result[] = $key . ':' . $value; |
||||||
|
} |
||||||
|
return $result; |
||||||
|
} |
||||||
|
|
||||||
|
function valuesOnly(array $values): array |
||||||
|
{ |
||||||
|
$result = []; |
||||||
|
foreach ($values as $value) { |
||||||
|
$result[] = $value; |
||||||
|
} |
||||||
|
return $result; |
||||||
|
} |
||||||
|
|
||||||
|
function sameTarget(array $values): mixed |
||||||
|
{ |
||||||
|
$item = null; |
||||||
|
foreach ($values as $item => $item) { |
||||||
|
} |
||||||
|
return $item; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
var_dump(collect([2 => 'two', 'name' => 'value'])); |
||||||
|
var_dump(valuesOnly([10, 20])); |
||||||
|
var_dump(sameTarget([7 => 'last-value'])); |
||||||
|
|
||||||
|
$source = 10; |
||||||
|
$values = [&$source]; |
||||||
|
foreach ($values as $value) { |
||||||
|
$value = 99; |
||||||
|
} |
||||||
|
var_dump($source, $value); |
||||||
|
|
||||||
|
$snapshot = ['a' => 1, 'b' => 2]; |
||||||
|
$seen = []; |
||||||
|
foreach ($snapshot as $key => $value) { |
||||||
|
$seen[] = $key . ':' . $value; |
||||||
|
unset($snapshot[$key]); |
||||||
|
} |
||||||
|
var_dump($seen, $snapshot); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(5) "2:two" |
||||||
|
[1]=> |
||||||
|
string(10) "name:value" |
||||||
|
} |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
int(10) |
||||||
|
[1]=> |
||||||
|
int(20) |
||||||
|
} |
||||||
|
string(10) "last-value" |
||||||
|
int(10) |
||||||
|
int(99) |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(3) "a:1" |
||||||
|
[1]=> |
||||||
|
string(3) "b:2" |
||||||
|
} |
||||||
|
array(0) { |
||||||
|
} |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
--TEST-- |
||||||
|
dynamic property access preserves string, referenced-string, and converted names |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
final class DynamicNameBag |
||||||
|
{ |
||||||
|
private array $values = []; |
||||||
|
|
||||||
|
public function __get(string $name): mixed |
||||||
|
{ |
||||||
|
return $this->values[$name] ?? null; |
||||||
|
} |
||||||
|
|
||||||
|
public function __set(string $name, mixed $value): void |
||||||
|
{ |
||||||
|
$this->values[$name] = $value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function readName(DynamicNameBag $bag, mixed $name): mixed |
||||||
|
{ |
||||||
|
return $bag->{$name}; |
||||||
|
} |
||||||
|
|
||||||
|
function writeName(DynamicNameBag $bag, mixed $name, mixed $value): void |
||||||
|
{ |
||||||
|
$bag->{$name} = $value; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$bag = new DynamicNameBag(); |
||||||
|
$name = 'answer'; |
||||||
|
writeName($bag, $name, 42); |
||||||
|
var_dump(readName($bag, $name)); |
||||||
|
|
||||||
|
$alias = &$name; |
||||||
|
var_dump(readName($bag, $alias)); |
||||||
|
|
||||||
|
writeName($bag, 7, 'seven'); |
||||||
|
var_dump(readName($bag, 7)); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(42) |
||||||
|
int(42) |
||||||
|
string(5) "seven" |
||||||
Loading…
Reference in new issue