Skip to content

feat(zend): add ModuleGlobals for per-extension global state#715

Merged
ptondereau merged 1 commit intoextphprs:masterfrom
ptondereau:feat/module-globals
Apr 3, 2026
Merged

feat(zend): add ModuleGlobals for per-extension global state#715
ptondereau merged 1 commit intoextphprs:masterfrom
ptondereau:feat/module-globals

Conversation

@ptondereau
Copy link
Copy Markdown
Member

@ptondereau ptondereau commented Apr 3, 2026

Description

Add ModuleGlobal trait and ModuleGlobals<T> struct for per-extension module globals with full TSRM integration.

Problem: ext-php-rs had no support for PHP module globals — zend_module_entry always set globals_size: 0. Extensions needing request-isolated mutable state in ZTS builds had to drop to raw FFI.

Solution: A two-layer API inspired by dd-trace-php's profiling/src/module_globals.rs:

  • Ergonomic layer: ModuleGlobals<T> with safe get(), unsafe get_mut(), and as_ptr() escape hatch. Registers via ModuleBuilder::globals(&MY_GLOBALS).
  • ZTS/NTS transparent: In ZTS, TSRM allocates per-thread storage; in NTS, globals live inline. Users write the same code for both.
#[derive(Default)]
struct MyGlobals { counter: i64, max_depth: i32 }

impl ModuleGlobal for MyGlobals {
    fn ginit(&mut self) { self.max_depth = 512; }
}

static MY_GLOBALS: ModuleGlobals<MyGlobals> = ModuleGlobals::new();

#[php_function]
pub fn get_counter() -> i64 { MY_GLOBALS.get().counter }

#[php_module]
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
    module.globals(&MY_GLOBALS)
}

Checklist

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 3, 2026

🐰 Bencher Report

Branchfeat/module-globals
TestbedPHP 8.4.19 (cli) (built: Mar 13 2026 01:28:58) (NTS)

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
BenchmarkEstimated Cyclescycles x 1e3InstructionsinstructionsL1 HitshitsLL HitshitsRAM HitshitsTotal read+writereads/writes
binary_bench::callback::callback_calls lots_of_callback_calls:("callback_call.php", 100_000) -> php '-dextension...📈 view plot
⚠️ NO THRESHOLD
582,226.31 x 1e3📈 view plot
⚠️ NO THRESHOLD
397,175,228.00📈 view plot
⚠️ NO THRESHOLD
566,208,767.00📈 view plot
⚠️ NO THRESHOLD
3,202,276.00📈 view plot
⚠️ NO THRESHOLD
176.00📈 view plot
⚠️ NO THRESHOLD
569,411,219.00
binary_bench::callback::callback_calls multiple_callback_calls:("callback_call.php", 10) -> php '-dextension=/hom...📈 view plot
⚠️ NO THRESHOLD
70.25 x 1e3📈 view plot
⚠️ NO THRESHOLD
43,157.00📈 view plot
⚠️ NO THRESHOLD
61,249.00📈 view plot
⚠️ NO THRESHOLD
569.00📈 view plot
⚠️ NO THRESHOLD
176.00📈 view plot
⚠️ NO THRESHOLD
61,994.00
binary_bench::callback::callback_calls single_callback_call:("callback_call.php", 1) -> php '-dextension=/home...📈 view plot
⚠️ NO THRESHOLD
17.69 x 1e3📈 view plot
⚠️ NO THRESHOLD
7,418.00📈 view plot
⚠️ NO THRESHOLD
10,344.00📈 view plot
⚠️ NO THRESHOLD
237.00📈 view plot
⚠️ NO THRESHOLD
176.00📈 view plot
⚠️ NO THRESHOLD
10,757.00
binary_bench::function::function_calls lots_of_function_calls:("function_call.php", 100_000) -> php '-dextension...📈 view plot
⚠️ NO THRESHOLD
59,516.92 x 1e3📈 view plot
⚠️ NO THRESHOLD
43,100,145.00📈 view plot
⚠️ NO THRESHOLD
59,496,201.00📈 view plot
⚠️ NO THRESHOLD
3,954.00📈 view plot
⚠️ NO THRESHOLD
27.00📈 view plot
⚠️ NO THRESHOLD
59,500,182.00
binary_bench::function::function_calls multiple_function_calls:("function_call.php", 10) -> php '-dextension=/hom...📈 view plot
⚠️ NO THRESHOLD
7.23 x 1e3📈 view plot
⚠️ NO THRESHOLD
4,455.00📈 view plot
⚠️ NO THRESHOLD
6,061.00📈 view plot
⚠️ NO THRESHOLD
44.00📈 view plot
⚠️ NO THRESHOLD
27.00📈 view plot
⚠️ NO THRESHOLD
6,132.00
binary_bench::function::function_calls single_function_call:("function_call.php", 1) -> php '-dextension=/home...📈 view plot
⚠️ NO THRESHOLD
1.87 x 1e3📈 view plot
⚠️ NO THRESHOLD
576.00📈 view plot
⚠️ NO THRESHOLD
707.00📈 view plot
⚠️ NO THRESHOLD
43.00📈 view plot
⚠️ NO THRESHOLD
27.00📈 view plot
⚠️ NO THRESHOLD
777.00
binary_bench::method::method_calls lots_of_method_calls:("method_call.php", 100_000) -> php '-dextension=/...📈 view plot
⚠️ NO THRESHOLD
66,800.83 x 1e3📈 view plot
⚠️ NO THRESHOLD
48,100,000.00📈 view plot
⚠️ NO THRESHOLD
65,799,951.00📈 view plot
⚠️ NO THRESHOLD
200,028.00📈 view plot
⚠️ NO THRESHOLD
21.00📈 view plot
⚠️ NO THRESHOLD
66,000,000.00
binary_bench::method::method_calls multiple_method_calls:("method_call.php", 10) -> php '-dextension=/home/...📈 view plot
⚠️ NO THRESHOLD
7.43 x 1e3📈 view plot
⚠️ NO THRESHOLD
4,810.00📈 view plot
⚠️ NO THRESHOLD
6,550.00📈 view plot
⚠️ NO THRESHOLD
29.00📈 view plot
⚠️ NO THRESHOLD
21.00📈 view plot
⚠️ NO THRESHOLD
6,600.00
binary_bench::method::method_calls single_method_call:("method_call.php", 1) -> php '-dextension=/home/r...📈 view plot
⚠️ NO THRESHOLD
1.42 x 1e3📈 view plot
⚠️ NO THRESHOLD
481.00📈 view plot
⚠️ NO THRESHOLD
627.00📈 view plot
⚠️ NO THRESHOLD
12.00📈 view plot
⚠️ NO THRESHOLD
21.00📈 view plot
⚠️ NO THRESHOLD
660.00
binary_bench::static_method::static_method_calls lots_of_static_calls:("static_method_call.php", 100_000) -> php '-dexte...📈 view plot
⚠️ NO THRESHOLD
59,516.89 x 1e3📈 view plot
⚠️ NO THRESHOLD
43,100,145.00📈 view plot
⚠️ NO THRESHOLD
59,496,200.00📈 view plot
⚠️ NO THRESHOLD
3,956.00📈 view plot
⚠️ NO THRESHOLD
26.00📈 view plot
⚠️ NO THRESHOLD
59,500,182.00
binary_bench::static_method::static_method_calls multiple_static_calls:("static_method_call.php", 10) -> php '-dextension...📈 view plot
⚠️ NO THRESHOLD
7.20 x 1e3📈 view plot
⚠️ NO THRESHOLD
4,455.00📈 view plot
⚠️ NO THRESHOLD
6,060.00📈 view plot
⚠️ NO THRESHOLD
46.00📈 view plot
⚠️ NO THRESHOLD
26.00📈 view plot
⚠️ NO THRESHOLD
6,132.00
binary_bench::static_method::static_method_calls single_static_call:("static_method_call.php", 1) -> php '-dextension=...📈 view plot
⚠️ NO THRESHOLD
1.84 x 1e3📈 view plot
⚠️ NO THRESHOLD
576.00📈 view plot
⚠️ NO THRESHOLD
706.00📈 view plot
⚠️ NO THRESHOLD
45.00📈 view plot
⚠️ NO THRESHOLD
26.00📈 view plot
⚠️ NO THRESHOLD
777.00
🐰 View full continuous benchmarking report in Bencher

Add `ModuleGlobal` trait and `ModuleGlobals<T>` struct that integrate
with PHP's TSRM to provide per-extension module globals. In ZTS builds,
PHP allocates per-thread storage via `ts_allocate_id`; in NTS builds,
the globals live inline in a user-declared static.

API surface:
- `ModuleGlobal` trait: `Default + 'static` with optional `ginit`/`gshutdown`
- `ModuleGlobals<T>::new()`: const constructor for static declaration
- `ModuleGlobals<T>::get()`: safe shared access (PHP single-threaded request model)
- `ModuleGlobals<T>::get_mut()`: unsafe mutable access
- `ModuleGlobals<T>::as_ptr()`: raw pointer escape hatch
- `ModuleBuilder::globals()`: registration via existing builder pattern

C layer: `ext_php_rs_tsrmg_bulk()` wraps `TSRMG_BULK` macro for ZTS access,
matching the existing pattern for executor/compiler/process globals.
@ptondereau ptondereau force-pushed the feat/module-globals branch from 035230d to 80cf959 Compare April 3, 2026 12:22
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 3, 2026

Pull Request Test Coverage Report for Build 23946027973

Details

  • 138 of 142 (97.18%) changed or added relevant lines in 2 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.3%) to 65.848%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/zend/module_globals.rs 100 104 96.15%
Totals Coverage Status
Change from base Build 23862291269: 0.3%
Covered Lines: 8370
Relevant Lines: 12711

💛 - Coveralls

@ptondereau ptondereau marked this pull request as ready for review April 3, 2026 13:07
@ptondereau ptondereau merged commit f4f5c0a into extphprs:master Apr 3, 2026
66 checks passed
@ptondereau ptondereau deleted the feat/module-globals branch April 6, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants