Skip to content

perf: move EG() and CG() in ZTS builds into __thread storage - #23227

Open
henderkes wants to merge 12 commits into
php:masterfrom
henderkes:perf/1-tls-eg-cg
Open

perf: move EG() and CG() in ZTS builds into __thread storage#23227
henderkes wants to merge 12 commits into
php:masterfrom
henderkes:perf/1-tls-eg-cg

Conversation

@henderkes

Copy link
Copy Markdown
Contributor

replay of #22231

Moves EG and CG into __thread storage after all. We first moved them into constant offsets (#22287) from *_tsrm_ls_cache, but I couldn't find a way to stop gcc or clang from reloading _tsrm_ls_cache base pointer between function calls, leading to an extra pointer load once per function.

This eliminates the pointer load, making the access sequence to EG/CG just a single mov (x64) / mrs + add + ldr (aarch64) under local-exec. initial-exec likewise loses the pointer load so 3 -> 2 instructions (x64).
cc @arnaud-lb

What I'm adding here to counter the global-dynamic fallback slowdown is the option to explicitly opt-in to initial-exec, under the knowledge that host programs loading it will need to increase the static tls surplus. This is not an issue for package providers.

PS: Actually figured out that the explicit model choice doesn't happen for musl, not sure about IE (static tls surplus on musl?), but LE should work just fine. That's for another PR though.

@arnaud-lb arnaud-lb 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 like the --with-tsrm-tls-model idea.

Did a first pass, but I will take the time to review carefully.

Comment thread ext/opcache/jit/zend_jit_ir.c Outdated
jit->tls = ir_TLS(
tsrm_ls_cache_tcb_offset ? tsrm_ls_cache_tcb_offset : tsrm_tls_index,
tsrm_ls_cache_tcb_offset ? IR_NULL : tsrm_tls_offset);
jit->tls = ir_TLS(tsrm_tls_index, tsrm_tls_offset + offsetof(zend_tsrm_ls_cache, self));

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 JIT changes seem wrong.

Can we postpones these changes to a separate PR? I believe that ir_TLS() should just work, as *(void**)_tsrm_ls_cache is still valid due to the cache field in _zend_tsrm_ls_cache. We just need to change the zend_jit_get_tsrm_ls_cache() function?

In the separate PR we could change ir_TLS() so that it returns the address of _tsrm_ls_cache instead of loading it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I can look into that later. I'm not sure here. Jumping on my PC and changing the other two remarks quickly, but don't have time for this right now.

@henderkes henderkes Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Edit: seems to work.

That said, the jit IR needs to be changed later anyway (I'll have to upstream this change), because the new global-dynamic branch is broken in IR today.

Comment thread TSRM/TSRM.h Outdated
Comment thread Zend/zend.c Outdated
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