Skip to content

Commit 558324d

Browse files
committed
Fix GH-24622: Preserve inherited JIT register spill requirements
1 parent ae3acfd commit 558324d

3 files changed

Lines changed: 164 additions & 0 deletions

File tree

‎NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ PHP NEWS
9191
empty array element. (jorgsowa)
9292
. Fixed bug GH-24396 (Assertion failure in zend_jit_def_reg() "reg forwarding"
9393
with chained Pi nodes). (ndossche)
94+
. Fixed bug GH-24622 (Tracing JIT skips spilling inherited register values
95+
before property helpers). (ndossche, kairosci)
9496

9597
- PDO:
9698
. Fixed invalid ATTR_ORACLE_NULLS values being accepted and truncated.

‎ext/opcache/jit/zend_jit_trace.c‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3351,6 +3351,7 @@ static zend_jit_reg_var* zend_jit_trace_allocate_registers(zend_jit_trace_rec *t
33513351
for (i = 0; i < op_array->last_var + op_array->T; i++) {
33523352
int var = STACK_VAR(stack, i);
33533353
if (var >= 0 && RA_HAS_REG(var)
3354+
&& ssa->vars[var].definition >= 0
33543355
&& !(RA_REG_FLAGS(var) & (ZREG_LOAD|ZREG_STORE|ZREG_LAST_USE))) {
33553356
RA_REG_FLAGS(var) |= ZREG_STORE;
33563357
}

‎ext/opcache/tests/jit/gh24622.phpt‎

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
--TEST--
2+
GH-24622: Tracing JIT must spill inherited register values before property helpers
3+
--EXTENSIONS--
4+
opcache
5+
spl
6+
--INI--
7+
opcache.enable=1
8+
opcache.enable_cli=1
9+
opcache.file_update_protection=0
10+
opcache.jit_buffer_size=64M
11+
opcache.jit=tracing
12+
zend.assertions=1
13+
assert.exception=1
14+
--CREDITS--
15+
frodeborli
16+
--FILE--
17+
<?php
18+
19+
final class StringBuffer
20+
{
21+
private \SplDoublyLinkedList $queue;
22+
private string $buffer = '';
23+
private int $length = 0;
24+
private int $offset = 0;
25+
private int $totalRead = 0;
26+
private int $totalWritten = 0;
27+
private bool $ended = false;
28+
29+
public function __construct()
30+
{
31+
$this->queue = new \SplDoublyLinkedList();
32+
}
33+
34+
public function isEmpty(): bool
35+
{
36+
return $this->offset === $this->length && $this->queue->isEmpty();
37+
}
38+
39+
public function write(string $chunk): void
40+
{
41+
if ($this->ended) {
42+
throw new \RuntimeException('Buffer has been ended');
43+
}
44+
$this->totalWritten += \strlen($chunk);
45+
$this->queue->push($chunk);
46+
}
47+
48+
public function read(int $maxLength): string
49+
{
50+
if ($maxLength < 0) {
51+
throw new \OutOfBoundsException("Can't read negative lengths");
52+
}
53+
$this->fill($maxLength);
54+
$chunk = \substr($this->buffer, $this->offset, $maxLength);
55+
$length = \strlen($chunk);
56+
$this->offset += $length;
57+
$this->totalRead += $length;
58+
59+
return $chunk;
60+
}
61+
62+
public function end(): void
63+
{
64+
if ($this->ended) {
65+
throw new \LogicException('StringBuffer already ended');
66+
}
67+
$this->ended = true;
68+
}
69+
70+
public function eof(): bool
71+
{
72+
return $this->ended && $this->offset === $this->length && $this->queue->isEmpty();
73+
}
74+
75+
public function unread(string $chunk): void
76+
{
77+
if ($this->ended && $this->offset === $this->length && $this->queue->isEmpty()) {
78+
throw new \LogicException("Can't unread to an ended and empty StringBuffer");
79+
}
80+
$chunkLength = \strlen($chunk);
81+
$this->totalRead -= $chunkLength;
82+
if ($this->length === $this->offset) {
83+
$this->queue->unshift($chunk);
84+
} else {
85+
$this->buffer = $chunk . \substr($this->buffer, $this->offset);
86+
$this->length += $chunkLength - $this->offset;
87+
$this->offset = 0;
88+
}
89+
}
90+
91+
protected function fill(int $requiredLength): bool
92+
{
93+
if ($this->offset > 4096) {
94+
$this->buffer = \substr($this->buffer, $this->offset);
95+
$this->length -= $this->offset;
96+
$this->offset = 0;
97+
}
98+
while ($this->length < $this->offset + $requiredLength && !$this->queue->isEmpty()) {
99+
$chunk = $this->queue->shift();
100+
$chunkLength = \strlen($chunk);
101+
102+
if ($this->length === $this->offset) {
103+
$this->buffer = $chunk;
104+
$this->length = $chunkLength;
105+
$this->offset = 0;
106+
} else {
107+
$this->buffer .= $chunk;
108+
$this->length += $chunkLength;
109+
}
110+
}
111+
112+
return $this->length >= $this->offset + $requiredLength;
113+
}
114+
}
115+
116+
$sb = new StringBuffer();
117+
$sb->write('A');
118+
assert($sb->read(1) === 'A');
119+
assert($sb->isEmpty() === true);
120+
$sb->write('B');
121+
$sb->unread('X');
122+
assert($sb->read(2) === 'XB');
123+
$sb->end();
124+
assert($sb->eof() === true);
125+
126+
$sb = new StringBuffer();
127+
$count = 0;
128+
for ($i = 0; $i < 1000; ++$i) { $sb->write('x'); }
129+
$sb->end();
130+
while (!$sb->eof()) { if ('' !== $sb->read(1)) { ++$count; } }
131+
assert($count === 1000);
132+
133+
$sb = new StringBuffer();
134+
$totalReceived = 0;
135+
$chunk = str_repeat('x', 1024);
136+
for ($i = 0; $i < 100; ++$i) { $sb->write($chunk); }
137+
$sb->end();
138+
while (!$sb->eof()) { $totalReceived += strlen($sb->read(8192)); }
139+
assert($totalReceived === 102400);
140+
141+
$sb = new StringBuffer();
142+
assert($sb->eof() === false);
143+
$sb->write('Hello');
144+
assert($sb->read(10000) === 'Hello');
145+
assert($sb->read(100) === '');
146+
assert($sb->write('ab') === null);
147+
assert($sb->read(1) === 'a');
148+
assert($sb->read(2) === 'b');
149+
$sb->write('abc');
150+
assert($sb->eof() === false);
151+
$sb->write('def');
152+
$sb->end();
153+
$sb->unread('b');
154+
assert($sb->read(5) === 'babcd');
155+
var_dump($sb->eof());
156+
assert($sb->read(10000) === 'ef');
157+
var_dump($sb->eof());
158+
159+
--EXPECT--
160+
bool(false)
161+
bool(true)

0 commit comments

Comments
 (0)