Repository navigation
JIT: Use interned constants for offsets in jit_ADD_OFFSET() - #23952
Conversation
`jit_ADD_OFFSET()` created the offset through `jit_CONST_ADDR()`, which
returns a unique (non-interned) constant. IR's own folding of nested
offsets, `ADD(ADD(x, c1), c2)`, creates an interned constant for `c1+c2`.
The same address can therefore end up with two different offset
refs, and CSE and store-to-load forwarding, which both need an
identical address ref, miss it.
The uniqueing exists likely for the exit addresses mostly, but for
offsets this is wrong.
For example, the type store and the type load of the same zval end up
with different refs:
```php
function count_big($n) {
$c = 0;
for ($i = 0; $i < $n; $i++) {
$big = $i > 5;
if ($big) {
$c++;
}
}
return $c;
}
```
Loop body before:
```asm
movl %esi, 0x88(%r14)
cmpb $3, 0x88(%r14)
jne jit$$trace_exit_4
```
After:
```asm
movl %esi, 0x88(%r14)
cmpb $3, %sil ; no reload
jne jit$$trace_exit_4
```
In general, more redundant loads can be avoided, and in some cases type
guards can be eliminated due to store->load forwarding.
dstogov
left a comment
There was a problem hiding this comment.
I think the problem is more serious.
At the time when I implemented JIT for PHP, IR didn't have a good way to de-duplicate many address constants. De-duplication through linear list made quadratic complexity, so I created a hash based de-duplicator at PHP/JIT level. Later a hash based de-duplicator was implemented in IR.
I suppose now hashing at PHP/JIT level should be removed and all ir_unique_const_addr() should be replaced by ir_const_addr().
|
Thanks for the historical context. That makes this patch more general and a nice cleanup. |
AWS x86_64 (c6id.metal)
Laravel 12.11.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)
Symfony 2.8.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)
Wordpress 6.9 main page - 50 iterations, 20 warmups, 20 requests (sec)
bench.php - 50 iterations, 20 warmups, 2 requests (sec)
|
dstogov
left a comment
There was a problem hiding this comment.
You have removed the caching of addresses that, very probably, may be requested few times (especially in function JIT). I'm not sure about contribution of this caching now. Please, measure and take the decision your self.
Everything else should be fine.
| #else | ||
| ir_ref ref = jit->eg_exception_addr; | ||
|
|
||
| if (UNEXPECTED(!ref)) { | ||
| ref = ir_unique_const_addr(&jit->ctx, (uintptr_t)&EG(exception)); | ||
| jit->eg_exception_addr = ref; | ||
| } | ||
| return ref; | ||
| #endif |
There was a problem hiding this comment.
This was done to avoid repeatable hash lookup. You may keep the same code with iir_const_addr()
| ir_ref ref = jit->stub_addr[id]; | ||
|
|
||
| if (UNEXPECTED(!ref)) { | ||
| ref = ir_unique_const_addr(&jit->ctx, (uintptr_t)zend_jit_stub_handlers[id]); | ||
| jit->stub_addr[id] = ref; | ||
| } | ||
| return ref; | ||
| return ir_CONST_ADDR(zend_jit_stub_handlers[id]); |
|
I brought back the stub and exception caches. I was not able to measure a difference (besides noise) in the total execution. Difference in Valgrind instruction count is also within noise. |
jit_ADD_OFFSET()created the offset throughjit_CONST_ADDR(), which returns a unique (non-interned) constant. IR's own folding of nested offsets,ADD(ADD(x, c1), c2), creates an interned constant forc1+c2. The same address can therefore end up with two different offset refs, and CSE and store-to-load forwarding, which both need an identical address ref, miss it.The uniqueing exists likely for the exit addresses mostly, but for offsets this is wrong.
For example, the type store and the type load of the same zval end up with different refs:
Loop body before:
After:
In general, more redundant loads can be avoided, and in some cases type guards can be eliminated due to store->load forwarding.