Skip to content

JIT compiled exit clobbers registers before saving #22915

Description

@arnaud-lb

Description

The following code:

<?phpfinalclass It implements Iterator {
publicreadonlyarray$values;
publicint$position = 0;
publicfunction__construct(array$values) {
$this->values = $values;
}
publicfunctionrewind(): void {}
publicfunctionvalid(): bool {
return$this->position === 0;
}
publicfunctioncurrent(): mixed {
if (!isset($this->values[$this->position])) {
thrownewException();
}
return$this->values[$this->position];
}
publicfunctionkey(): mixed {
return$this->position;
}
publicfunctionnext(): void {
$this->position++;
}
}
functioniter(It$it) {
foreach ($itas$value) {
var_dump($value);
if (!$valueinstanceof stdClass) {
continue;
}
}
}
echo"# First run\n";
for ($i = 0; $i < 5; $i++) {
getenv('F')(newIt([getenv('F')])); // non-immutable, packed array
}
// Next side-exit should deoptimizevar_dump(ini_set('opcache.jit_max_side_traces', '0'));
var_dump(ini_set('opcache.jit_blacklist_side_trace', '0'));
echo"# Second run\n";
for ($i = 0; $i < 5; $i++) {
getenv('F')(newIt([getenv('F'), 'map' => true])); // non-immutable, map, triggers exit
}
?>

When executed with

F=iter php -n -d opcache.enable_cli=1 -d opcache.jit=tracing -d opcache.jit_hot_func=1 -d opcache.jit_hot_side_exit=1 test.php

Resulted in this output:

# First run
string(4) "iter"
string(4) "iter"
string(4) "iter"
string(4) "iter"
string(4) "iter"
string(3) "128"
string(1) "8"
# Second run
string(4) "iter"
Fatal error: Uncaught Exception in test.php:18
Stack trace:
#0 test.php(34): It->current()
#1 test.php(53): iter(Object(It))
#2 {main}
thrown in test.php on line 18

But I expected this output instead:

# First run
string(4) "iter"
string(4) "iter"
string(4) "iter"
string(4) "iter"
string(4) "iter"
string(3) "128"
string(1) "8"
# Second run
string(4) "iter"
string(4) "iter"
string(4) "iter"
string(4) "iter"
string(4) "iter"

Cause:

During the second run phase, the compiled trace for It::current() exits because $this->values is not a packed array anymore. The side exit is blacklisted, but the generated code clobbers registers before saving them:

 ESCAPE-6-4: cmpb$0,0x69(%r12)je .L1 ; try_addref(T1)movq0x60(%r12), %rax addl$1, (%rax).L1: ; save T2 (%rax), but it was clobbered abovemovq %rax,0x70(%r12) movl$4,0x78(%r12) leaq -0xb01677d(%rip), %r13 addq$0x38, %rsp jmpq *(%r13)

This causes isset($this->values[$this->position]) to evaluate to false.

PHP Version

PHP 8.4

Operating System

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions