Skip to content

JIT: Use interned constants for offsets in jit_ADD_OFFSET() - #23952

Merged
ndossche merged 3 commits into
php:masterfrom
ndossche:jit-add-offset-interned-const
Sep 30, 2026
Merged

ndossche merged 3 commits into
php:masterfrom
ndossche:jit-add-offset-interned-const

Conversation

@ndossche

Copy link
Copy Markdown
Member

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:

function count_big($n) {
    $c = 0;
    for ($i = 0; $i < $n; $i++) {
        $big = $i > 5;
        if ($big) {
            $c++;
        }
    }
    return $c;
}

Loop body before:

movl %esi, 0x88(%r14)
cmpb $3, 0x88(%r14)
jne jit$$trace_exit_4

After:

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.

`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 dstogov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

@ndossche

Copy link
Copy Markdown
Member Author

Thanks for the historical context. That makes this patch more general and a nice cleanup.

@github-actions

Copy link
Copy Markdown

AWS x86_64 (c6id.metal)

Attribute Value
Environment aws
Instance type c6id.metal
Architecture x86_64
CPU Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz, 64 cores @ 2900 MHz
CPU settings disabled deeper C-states, disabled turbo boost, disabled hyper-threading
RAM 251 GB
Kernel 6.18.38-76.139.amzn2023.x86_64
OS Amazon Linux 2023.12.20260727
GCC 14.2.1
Binary layout strategy none
Time 2026-09-28 22:17:25 UTC
Job details https://github.com/php/php-src/actions/runs/36491453021 (Artifacts)
Changeset https://github.com/php/php-src/compare/a8caefcafb..d2e9b7ed02

Laravel 12.11.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@a8caefc 0.37388 0.37865 0.00082 0.22% 0.37447 0.00% 0.37424 0.00% 3.691 0.000 1.000 25.78 MB
PHP - baseline@a8caefc (JIT) 0.35175 0.35317 0.00030 0.08% 0.35247 -5.88% 0.35245 -5.82% 0.163 8.614 0.000 25.83 MB
PHP - jit-add-offset-interned-const 0.37409 0.37663 0.00050 0.13% 0.37470 0.06% 0.37455 0.08% 1.976 -4.795 0.000 25.85 MB
PHP - jit-add-offset-interned-const (JIT) 0.35198 0.35342 0.00031 0.09% 0.35259 -5.84% 0.35255 -5.80% 0.556 8.614 0.000 25.82 MB

Symfony 2.8.0 demo app - 50 iterations, 50 warmups, 100 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@a8caefc 0.67184 0.67305 0.00031 0.05% 0.67232 0.00% 0.67224 0.00% 0.877 0.000 1.000 26.24 MB
PHP - baseline@a8caefc (JIT) 0.63583 0.64239 0.00102 0.16% 0.63662 -5.31% 0.63648 -5.32% 4.548 8.614 0.000 26.25 MB
PHP - jit-add-offset-interned-const 0.67368 0.67513 0.00032 0.05% 0.67427 0.29% 0.67424 0.30% 0.529 -8.614 0.000 26.21 MB
PHP - jit-add-offset-interned-const (JIT) 0.63892 0.64035 0.00034 0.05% 0.63952 -4.88% 0.63950 -4.87% 0.572 8.614 0.000 26.24 MB

Wordpress 6.9 main page - 50 iterations, 20 warmups, 20 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@a8caefc 0.59182 0.59647 0.00079 0.13% 0.59274 0.00% 0.59252 0.00% 2.558 0.000 1.000 26.30 MB
PHP - baseline@a8caefc (JIT) 0.51999 0.52492 0.00113 0.22% 0.52170 -11.98% 0.52134 -12.01% 1.278 8.614 0.000 26.24 MB
PHP - jit-add-offset-interned-const 0.59020 0.59787 0.00113 0.19% 0.59117 -0.26% 0.59089 -0.27% 4.558 7.821 0.000 26.26 MB
PHP - jit-add-offset-interned-const (JIT) 0.51864 0.52303 0.00082 0.16% 0.51975 -12.31% 0.51958 -12.31% 2.262 8.614 0.000 26.29 MB

bench.php - 50 iterations, 20 warmups, 2 requests (sec)

PHP Min Max Std dev Rel std dev % Mean Mean diff % Median Median diff % Skewness Z-stat P-value Memory
PHP - baseline@a8caefc 0.45095 0.45536 0.00084 0.18% 0.45328 0.00% 0.45331 0.00% -0.185 0.000 1.000 26.32 MB
PHP - baseline@a8caefc (JIT) 0.14442 0.14534 0.00021 0.14% 0.14487 -68.04% 0.14487 -68.04% 0.064 8.614 0.000 26.26 MB
PHP - jit-add-offset-interned-const 0.45121 0.45508 0.00081 0.18% 0.45305 -0.05% 0.45303 -0.06% 0.519 1.727 0.084 26.28 MB
PHP - jit-add-offset-interned-const (JIT) 0.14439 0.14532 0.00022 0.15% 0.14481 -68.05% 0.14478 -68.06% 0.088 8.614 0.000 26.31 MB

@dstogov dstogov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines -603 to -611
#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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done to avoid repeatable hash lookup. You may keep the same code with iir_const_addr()

Comment thread ext/opcache/jit/zend_jit_ir.c Outdated
Comment on lines +616 to +561
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]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same.

@ndossche

Copy link
Copy Markdown
Member Author

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.
Intuitively, having the separate caches may be beneficial, so I'll keep them.
Thanks for checking.

@ndossche
ndossche merged commit 940ff20 into php:master Sep 30, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants