- 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