Skip to content

Commit 1692bfd

Browse files
committed
preload inference and compile time monomorphization without explicit turbofish
1 parent 9076fd1 commit 1692bfd

27 files changed

Lines changed: 1479 additions & 38 deletions

Zend/Optimizer/dfa_pass.c

Lines changed: 600 additions & 0 deletions
Large diffs are not rendered by default.

Zend/Optimizer/zend_optimizer.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,7 +1306,7 @@ static void zend_optimize(zend_op_array *op_array,
13061306
}
13071307
}
13081308

1309-
static void zend_revert_pass_two(zend_op_array *op_array)
1309+
void zend_revert_pass_two(zend_op_array *op_array)
13101310
{
13111311
zend_op *opline;
13121312

@@ -1336,7 +1336,7 @@ static void zend_revert_pass_two(zend_op_array *op_array)
13361336
op_array->fn_flags &= ~ZEND_ACC_DONE_PASS_TWO;
13371337
}
13381338

1339-
static void zend_redo_pass_two(zend_op_array *op_array)
1339+
void zend_redo_pass_two(zend_op_array *op_array)
13401340
{
13411341
zend_op *opline, *end;
13421342
#if ZEND_USE_ABS_JMP_ADDR && !ZEND_USE_ABS_CONST_ADDR

Zend/Optimizer/zend_optimizer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ typedef void (*zend_optimizer_pass_t)(zend_script *, void *context);
9292

9393
BEGIN_EXTERN_C()
9494
ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_level, zend_long debug_level);
95+
ZEND_API uint32_t zend_aot_monomorphize_script(zend_script *script, zend_long opt_level);
96+
ZEND_API uint32_t zend_aot_upgrade_dispatch_to_ucall(zend_script *script);
9597
ZEND_API int zend_optimizer_register_pass(zend_optimizer_pass_t pass);
9698
ZEND_API void zend_optimizer_unregister_pass(int idx);
9799
zend_result zend_optimizer_startup(void);

Zend/Optimizer/zend_optimizer_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx);
111111
void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx);
112112
void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx);
113113
void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx);
114+
void zend_revert_pass_two(zend_op_array *op_array);
115+
void zend_redo_pass_two(zend_op_array *op_array);
114116
zend_result zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa);
115117
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map);
116118
void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Reification: a non-turbofish call to an all-defaulted generic callee with non-literal args infers nothing and stays generic (must not build an empty type-arg box)
3+
--FILE--
4+
<?php
5+
// Inferring zero type args must bail, not build a zero-length box (underflow -> crash).
6+
function neighbors<TNode = mixed, TWeight = mixed>(mixed $graph, TNode $node): array {
7+
return [$node];
8+
}
9+
10+
function caller(mixed $g, mixed $n): array {
11+
return neighbors($g, $n);
12+
}
13+
14+
var_dump(caller('graph', 'x'));
15+
var_dump(caller('graph', 42));
16+
echo "ok\n";
17+
?>
18+
--EXPECT--
19+
array(1) {
20+
[0]=>
21+
string(1) "x"
22+
}
23+
array(1) {
24+
[0]=>
25+
int(42)
26+
}
27+
ok
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Reification: a scalar literal argument monomorphizes a non-turbofish generic call at compile time (no opcache needed); the reified return type enforces the inferred T
3+
--FILE--
4+
<?php
5+
declare(strict_types=1);
6+
7+
function retStr<T : int|float|string>(T $x): T { return "s"; }
8+
function retInt<T : int|float|string>(T $x): T { return 7; }
9+
10+
function lit_int() { return retStr(42); }
11+
function lit_float() { return retStr(3.14); }
12+
function lit_string() { return retInt("hi"); }
13+
14+
foreach (['lit_int', 'lit_float', 'lit_string'] as $fn) {
15+
try {
16+
$fn();
17+
echo "$fn: NO error (not monomorphized!)\n";
18+
} catch (TypeError $e) {
19+
echo "$fn: ", $e->getMessage(), "\n";
20+
}
21+
}
22+
?>
23+
--EXPECT--
24+
lit_int: retStr(): Return value must be of type int, string returned
25+
lit_float: retStr(): Return value must be of type float, string returned
26+
lit_string: retInt(): Return value must be of type string, int returned
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
Reification: the opcache DFA optimizer monomorphizes non-turbofish generic calls from each argument's SSA-inferred type; reassignment binds the actual type, not the declared one, and unknown types stay generic
3+
--EXTENSIONS--
4+
opcache
5+
--INI--
6+
opcache.enable=1
7+
opcache.enable_cli=1
8+
opcache.optimization_level=-1
9+
--FILE--
10+
<?php
11+
declare(strict_types=1);
12+
13+
function retStr<T : int|float|string>(T $x): T { return "s"; }
14+
function retInt<T : int|float|string>(T $x): T { return 7; }
15+
16+
function from_int(int $i) { return retStr($i); }
17+
function from_float(float $f) { return retStr($f); }
18+
function local_literal() { $v = 1.5; return retStr($v); }
19+
20+
// Reassignment must bind T from the SSA type (string), not the declared int.
21+
function reassigned(int $val) { $val = 'x'; return retInt($val); }
22+
23+
function from_unknown(array $a) { $v = $a[0]; return retInt($v); }
24+
25+
function check(string $name, callable $fn): void {
26+
try {
27+
$r = $fn();
28+
echo "$name: ", var_export($r, true), "\n";
29+
} catch (TypeError $e) {
30+
echo "$name: TypeError\n";
31+
}
32+
}
33+
34+
check('from_int', fn() => from_int(1));
35+
check('from_float', fn() => from_float(1.5));
36+
check('local_literal', fn() => local_literal());
37+
check('reassigned', fn() => reassigned(1));
38+
check('from_unknown', fn() => from_unknown([42]));
39+
?>
40+
--EXPECT--
41+
from_int: TypeError
42+
from_float: TypeError
43+
local_literal: TypeError
44+
reassigned: TypeError
45+
from_unknown: 7
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Bench;
4+
5+
function id<T>(T $x): T { return $x; }
6+
7+
function add<T : int|float>(T $a, T $b): T { return $a + $b; }
8+
9+
// No value params: dispatch must fire from the turbofish alone, nothing inferable from args.
10+
function zero<T : int|float|string>(): string { return "z"; }
11+
12+
class Runner {
13+
public static function ints(int $n): int {
14+
$acc = 0;
15+
for ($i = 0; $i < $n; $i++) {
16+
$acc = add::<int>($acc, id::<int>($i));
17+
}
18+
return $acc;
19+
}
20+
21+
public static function strs(): string {
22+
return id::<string>("hello") . ":" . zero::<int>();
23+
}
24+
25+
public static function badType(): void {
26+
add::<int>(1, []);
27+
}
28+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
Reification: AOT-lowered preloaded generic calls (INIT_FCALL/DO_UCALL + concrete RECV + by-value SEND) stay correct and type-safe
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.preload={PWD}/preload_aot_call_lowering.inc
8+
--EXTENSIONS--
9+
opcache
10+
--SKIPIF--
11+
<?php
12+
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
13+
?>
14+
--FILE--
15+
<?php
16+
use Bench\Runner;
17+
18+
var_dump(Runner::ints(5));
19+
var_dump(Runner::strs());
20+
21+
// Weak-mode coercion still applies on the synthesized RECV: "5" -> 5.
22+
var_dump(Bench\add::<int>("5", 2));
23+
24+
try {
25+
Runner::badType();
26+
} catch (\TypeError $e) {
27+
echo $e->getMessage(), "\n";
28+
}
29+
echo "done\n";
30+
?>
31+
--EXPECTF--
32+
int(10)
33+
string(7) "hello:z"
34+
int(7)
35+
Bench\add(): Argument #2 ($b) must be of type int, array given, called in %s on line %d
36+
done
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
// preload_fix_trait_op_array must not re-sync the monomorph's generic-inheritance method clones as trait clones.
4+
5+
final class Vec<T> {
6+
public function __construct(public array $items = []) {}
7+
public function with(T $x): Vec<T> {
8+
$n = $this->items;
9+
$n[] = $x;
10+
return new Vec::<T>($n);
11+
}
12+
public function sum(): int {
13+
$s = 0;
14+
foreach ($this->items as $v) { $s += $v; }
15+
return $s;
16+
}
17+
public function size(): int { return count($this->items); }
18+
}
19+
20+
class Driver {
21+
public static function build(): string {
22+
$v = new Vec::<int>([1, 2]);
23+
$v = $v->with(3);
24+
$v = $v->with(4);
25+
return $v->sum() . ':' . $v->size() . ':' . get_class($v);
26+
}
27+
}

0 commit comments

Comments
 (0)