Le mar. 2 juin 2026 à 15:12, Go Kudo <[email protected]> a écrit :

>
>
> 2026年5月17日(日) 0:19 Go Kudo <[email protected]>:
>
>> Hi internals,
>>
>> I'd like to start the discussion for a new RFC, OPcache Static Cache.
>>
>> RFC: https://wiki.php.net/rfc/opcache_static_cache
>> Implementation: https://github.com/php/php-src/pull/22052
>>
>> The proposal adds an OPcache-managed shared-memory cache for explicit
>> userland values and for selected PHP static state. It introduces explicit
>> functions under the OPcache namespace (volatile_* and persistent_*) and two
>> attributes, #[OPcache\VolatileStatic] and #[OPcache\PersistentStatic], that
>> let selected static properties and method static variables survive across
>> requests. The feature is disabled by default and only activates once memory
>> is allocated through the new INI directives.
>>
>> The RFC covers the motivation, the deliberate split between the two
>> backends, the trust model (one PHP runtime = one trust domain; this is not
>> a tenant isolation boundary), and benchmarks against APCu on NTS php-fpm
>> and ZTS FrankenPHP. The PR is the full implementation, with PHPT coverage
>> summarized in the Validation section.
>>
>> One thing to flag on the implementation status: the Windows build is
>> currently broken. I don't have a Windows development environment available
>> yet — one is being arranged through work, and I'll get the Windows side
>> fixed once that's in place.
>>
>> Feedback welcome.
>>
>> Best Regards,
>> Go Kudo
>>
>
>  Hi Nicolas, Jakub, Timo, Larry
>
> I update RFC and Implementation:
> RFC: https://wiki.php.net/rfc/opcache_static_cache
> PR: https://github.com/php/php-src/pull/22052
>
> I'm folding replies to all three of you into one message, since the
> threads overlap. Most of it answers Nicolas's measurements; further down
> there is a section for Jakub's FPM pool-isolation concern and a short note
> for Timo's pointer to prior art.
>
> Nicolas, thank you for building my branch and running your own A/B/C
> measurements. That moved the discussion onto concrete ground, and I
> appreciate it.
>
> Since your review I have pushed a revised branch and bumped the RFC to
> 2.0.0. The API changes discussed below are in it (the SAPI opt-in model,
> and `getCacheStoreType()` for storage-path visibility), and the object
> workloads you flagged are now substantially faster: native now beats the
> deepclone path on every nested case I tried. Details and numbers follow.
>
> I agree with most of your points. I'll go through them in order, concede
> the ones where you are right, and try to narrow what is left. I think it
> comes down to one question: whether a userland array-hydration layer is an
> acceptable replacement for engine-level object storage. Most of the rest I
> can give you.
>
> ## The resulting public API
>
> For reference, here is the shape the explicit API settled into, summarised
> from the stub:
>
> ```php
> namespace OPcache;
>
> // Explicit cache: two final classes, static methods only, no instances.
> final class VolatileCache
> {
>     public static function get(string $key,
> null|bool|int|float|string|array|object $default = null):
> null|bool|int|float|string|array|object;
>     public static function getMultiple(array $keys, ?array $default =
> null): array|false;
>     public static function set(string $key,
> null|bool|int|float|string|array|object $value, int $ttl = 0): bool;
>     public static function setMultiple(array $values, int $ttl = 0): bool;
>     public static function has(string $key): bool;
>     public static function delete(string $key_or_class): bool;
>     public static function deleteMultiple(array $keys): bool;
>     public static function clear(): bool;
>     public static function lock(string $key, int $lease = 0): bool;
>     public static function unlock(string $key): bool;
>     public static function getCacheStoreType(string $key_or_property,
> ?string $class_name = null): CacheStoreType;
>     public static function info(): StaticCacheInfo;
> }
>
> // PinnedCache is the same set, except set()/setMultiple() take no $ttl,
> // plus two atomic counters:
> final class PinnedCache
> {
>     // get/getMultiple/set/setMultiple/has/delete/deleteMultiple/clear/
>     // lock/unlock/getCacheStoreType/info  -- as above
>     public static function increment(string $key, int $step = 1):
> int|false;
>     public static function decrement(string $key, int $step = 1):
> int|false;
> }
>
> // getCacheStoreType() reports how a value is stored, without decoding it:
> enum CacheStoreType
> {
>     case NotFound;          // no entry for the key/property
>     case Scalar;            // stored inline
>     case SharedGraph;       // zero-copy graph laid out in SHM (the fast
> path)
>     case OPcacheSerialized; // OPcache binary serializer (SHM-safe, no
> userland)
>     case PHPSerialized;     // php_var_serialize() last resort
> }
>
> // Declarative static state, over the same storage:
> #[Attribute] final class VolatileStatic {
>     public function __construct(int $ttl = 0, CacheStrategy $strategy =
> CacheStrategy::Immediate);
> }
> #[Attribute] final class PinnedStatic {}
> enum CacheStrategy: int { case Immediate = 0; case Tracking = 1; }
>
> // Status object and the single exception type:
> final readonly class StaticCacheInfo { /* enabled, available,
> configured_memory, entry_count, ... */ }
> class StaticCacheException extends \Exception {}
> ```
>
> Two final classes with static methods, no instances and no shared
> interface. Misses and contention return the default or `false`; genuine
> backend failures return `false` (or `int|false` for the atomic counters);
> `Closure` and resource values are rejected with a `TypeError`; and
> `StaticCacheException` is reserved for strict `#[OPcache\PinnedStatic]`
> publication.
>
> ## SAPI availability: the unsafe flag is gone, opt-in instead
>
> > these are safe SAPIs, they just don't have a scoping concept built in
> > [...] enable it by default with a single default scope for those SAPIs,
> > plus a clear internal API so a SAPI can define its own scoped segments
>
> I implemented it the way you suggested. There is no longer an
> `opcache.static_cache.allow_unsafe_runtime` directive and no SAPI-name
> allowlist in the engine. Availability is opt-in: a SAPI, or an embedder,
> calls a small internal C API, `zend_opcache_static_cache_opt_in()`, before
> request handling to enable Static Cache for its runtime. That call is the
> runtime declaring that a trust/storage boundary holds for the lifetime of
> the shared-memory owner.
>
> The bundled `fpm`, `cli`, `cli-server` and `phpdbg` SAPIs call it at
> startup, so they are available by default. The difference from before is
> the
> mechanism: instead of the engine guessing from the SAPI name and offering
> an
> "unsafe" override, each runtime states that it owns a boundary. A runtime
> with a real per-tenant boundary scopes it with the partition API
> (`zend_opcache_static_cache_partition_create` / `_activate`, which `fpm`
> already uses per pool). A runtime without one, such as a shared
> multi-tenant
> web SAPI with no pre-request identity, never opts in and stays unavailable,
> with nothing left to misconfigure.
>
> The `embed` SAPI does not auto-opt-in, on purpose. The embedding
> application
> owns the runtime and its trust boundary, so it opts in from its own startup
> code. That keeps the rule consistent for every embedder, including one that
> registers its own SAPI module instead of reusing the bundled `embed` one.
> FrankenPHP does exactly that, so it opts in with the same one-line call
> (or a
> scoped partition when it isolates per worker); there is no `embed`
> special-case that covers `php_embed` users but silently misses FrankenPHP.
>
> That is your internal-API point, and it removes the naming question by
> deleting the flag entirely. The full ext/opcache suite passes with the
> directive gone.
>
> ## API shape: remember()
>
> > I could also add VolatileCache::remember($key, $compute, $ttl = 0)
> > wrapping the safe lock -> build-outside-the-lock -> store sequence
>
> I would rather not add this one. `remember()` takes a callable, and to
> actually prevent a stampede it has to hold the entry lock across the call
> to
> `$compute()`. That means running arbitrary userland PHP while holding a
> cross-process SHM lock. The callable can run unbounded, throw, fork, or
> re-enter the cache, and a re-entrant `lock()` on the same key (or a key in
> the same lock stripe) while the lock is held is a deadlock. The lease
> bounds
> the duration, but not the re-entrancy and not the exception path.
>
> Not holding the lock while computing gives no stampede protection at all;
> it
> is then just sugar over `get()`-then-`set()` that looks atomic, which is
> worse than not having it.
>
> Since I already expose `lock()`/`unlock()` with a lease, userland can do
> the
> safe thing itself, with the compute step outside any engine lock:
>
> ```php
> if (!VolatileCache::lock($key, $lease)) {
>     return VolatileCache::get($key, $default);
> }
> try {
>     $value = $compute(); // runs outside the engine lock
>     VolatileCache::set($key, $value, $ttl);
>     return $value;
> } finally {
>     VolatileCache::unlock($key);
> }
> ```
>
> That keeps the closure's execution, its scope, and any exception it throws
> in
> userland, never inside the engine's critical section. I would rather
> document
> this recipe than move userland execution into the primitive. If you see a
> safe construction I have missed, I will reconsider.
>
> ## References and the silent fallback
>
> > I'd rather make it visible (surface the chosen path in info(), or in a
> > debug build) than ban objects
>
> Agreed, and that is implemented: visibility, not a ban. There is a new
> introspection method on both cache classes:
>
> ```php
> VolatileCache::getCacheStoreType(string $key_or_property, ?string
> $class_name = null): OPcache\CacheStoreType
> PinnedCache::getCacheStoreType(string $key_or_property, ?string
> $class_name = null): OPcache\CacheStoreType
> ```
>
> It returns an `OPcache\CacheStoreType` enum (`NotFound`, `Scalar`,
> `SharedGraph`, `OPcacheSerialized`, `PHPSerialized`), so you can see per
> key
> which path a value took, without decoding it, in any build rather than
> only a
> debug one. Passing `$class_name` inspects the attribute-backed
> static-property storage for that class instead of an explicit key. A value
> that fell back to serialization is now one call away from being observable.
>
> The enum also pins down a correction. The first fallback off the shared
> graph
> is not `php_var_serialize` but the OPcache binary serializer, which is
> SHM-safe and runs no userland code. That is why `getCacheStoreType` reports
> `OPcacheSerialized` and `PHPSerialized` as separate cases;
> `php_var_serialize`
> is the last resort, not the first. So "bail == APCu parity" understates the
> middle tier, though your underlying point holds: even that tier is slower
> than
> the fast path and should be visible.
>
> > no real objection to rejecting top-level hard refs up front [...]
> > "top-level hard ref" confuses me
>
> You are right to be confused, and I will retract the phrase; it is a no-op.
> `store($key, $value)` takes `$value` by value, so the engine dereferences
> any
> top-level reference (`ZVAL_DEREF`) before storage ever sees it. A top-level
> hard ref cannot reach the storage layer as a reference. The case that
> matters
> is a nested reference, a `&` inside an array element or object property,
> and
> that cannot be rejected cheaply up front: detecting it requires walking the
> whole graph, which is the walk the shared-graph builder already does. So
> the
> honest answer for nested refs is the visibility above (the value reports
> the
> serialize path), not an up-front rejection.
>
> ## Scalars and arrays-of-scalars only
>
> This is where the discussion helped most. I argued before that scalars-only
> gave up a real win; you pushed back with measurements; so I built your
> setup
> and measured it properly, including the large nested workloads that are the
> actual case for a cache. You were right that native was losing. That sent
> me
> into the implementation, and I found the cause and fixed it. The path is
> worth setting out.
>
> Two of your framings I agree with up front:
>
> 1. For array-of-scalars config/metadata, an immutable interned array is
>    essentially free, and the cache should not claim to beat it.
> 2. The "Nx faster than APCu" headline is size-dependent; APCu is only a few
>    microseconds for small payloads.
>
> ### (a) The config array
>
> > an immutable array is essentially free (0.045 us) [...] the static
> > cache's own array fetch, which pays an O(n) walk per read and so doesn't
> > even deliver the immutable-array win that opcache literals already give
>
> You are structurally right, and I have fixed it. Two facts first. I could
> not
> reproduce 331 us: a pure-scalar 4k-entry array fetches in about 7 us,
> scaling
> at roughly 1.7 ns/entry, and the decode itself was already zero-copy (a
> scalar array is stored once as `IS_ARRAY_IMMUTABLE` and returned as
> `ZVAL_ARR()` straight into SHM). The O(n) you felt was one layer up: every
> warm fetch re-walked the array in `value_needs_request_local_clone()` to
> decide whether it needed a deep clone, when that answer is fixed at store
> time. I removed that walk for shared-graph values (the same change as in
> (c)); the 4k fetch is now about 0.64 us and flat in the entry count.
>
> It is still not the 0.014 us of a resident literal read, and I am not
> claiming it should be. For read-only scalar config the preload/literal path
> wins, and that is fine. It is a separate matter from objects.
>
> ### (b) Objects: I measured your A/B/C, found native losing, and chased why
>
> I built this branch with APCu master and your deepclone, all NTS, JIT off,
> timing warm fetches where C rebuilds the same isolated object graph B
> returns
> (resident dehydrated array plus `deepclone_from_array`). As you said,
> native
> lost, and worse as the graph grew. us/op:
>
> ```
> array of nested ORM entities     objects   A apcu   B native   C hydrate
>                                      1000     1800        799        501
>                                      2000     4171       1903       1043
> object tree                          8191     1582       1736        498
>                                      9841     1928       1836        523
> ```
>
> Two things you were right about that I had wrong: `deepclone_to_array` /
> `deepclone_from_array` are generic (no per-class hydrator to charge for),
> and
> C hands back the same isolated objects B does. So this was a real loss,
> not a
> measurement artifact.
>
> The cause was structural, but not where I first guessed. The warm fetch
> kept
> a request-local prototype of the materialized graph and deep-cloned it on
> every repeat fetch, and for an object graph that clone is slower than
> decoding
> the compact SHM layout again. A shared graph never holds shared identity or
> cycles, so each decode is already an independent copy; the prototype was
> pure
> overhead. On top of that the decoder re-resolved the class
> (`zend_lookup_class`) for every object, and the builder stored a separate
> copy
> of each repeated class and property name.
>
> ### (c) The fix
>
> Three changes, all behind the existing API, with no visible behaviour or
> format change:
>
> - Skip the request-local prototype for shared-graph values and decode from
>   SHM on each fetch. (This also removes the O(n) array walk in (a).)
> - Deduplicate equal strings within a payload at build time, so a class or
>   property name repeated across thousands of objects is stored once.
> - Memoize the resolved class per (buffer, offset) during a decode, so a
>   homogeneous graph resolves its class once, not once per node.
>
> Same A/B/C after the change, NTS, JIT off, us/op:
>
> ```
> array of nested ORM entities     objects   A apcu   B native   C hydrate
>                                      1000     1781        357        492
>                                      2000     3868        721       1036
> object tree                          8191     1565        462        485
>                                      9841     1830        499        513
> ```
>
> Native now beats deepclone on every nested workload I tried: about 1.4x on
> the 2000-entity array, and the deep trees that lost 3.5x now win. The
> 400-object case went from 72 to 23 us. The full ext/opcache suite passes,
> plus new regression tests, on NTS and ZTS.
>
> To make this reproducible on your terms, I added a deepclone backend to my
> own
> HTTP benchmark harness (dehydrate with `deepclone_to_array()`, keep the
> array
> in the volatile cache, rehydrate with `deepclone_from_array()` on each
> fetch)
> and re-ran `vote_read_long` under the published conditions (php-fpm + nginx
> NTS and FrankenPHP ZTS, 20 iterations / 3 warmup / 3000 ops, JIT off). The
> APCu baselines match the published table within about 2%, so the runtimes
> are
> comparable. native vs deepclone, mean us/op (NTS):
>
> ```
> workload                 APCu     native   deepclone
> route_table_read        161.2      0.90      0.91     (array: tie)
> large_array              90.9      0.88      0.88     (array: tie)
> metadata_object_read    185.3      1.12      1.32     (native)
> metadata_object_mutate  162.4      1.03      1.19     (native)
> safe_direct_object        2.5      1.22      3.03     (native; deepclone
> slower than APCu)
> carbon_datetime_object  185.4     46.0     166.3      (native, ~3.6x)
> spl_collection_object    21.0      5.48      1.89     (deepclone)
> ```
>
> So under the RFC's own methodology native is faster than the deepclone
> path on
> every object workload except SPL collections, and ties on arrays. The SPL
> case
> is the one real win for deepclone, and it is specific: those classes go
> through
> the safe-direct serialized path, whose per-fetch copy handler is heavier
> than
> rebuilding from a flat array. I have noted it in the RFC as a concrete
> follow-up (a tighter SPL copy handler); it does not change the overall
> picture.
> The updated tables are in the RFC.
>
> Honest edges remain: for a tiny object deepclone's tight path is a hair
> faster
> (sub-microsecond), and for read-only scalar config a resident literal still
> wins outright, as in (a). But for the workload this feature is actually
> for,
> large nested object graphs from a database, in-engine storage is now the
> faster option.
>
> ### (d) Not just performance
>
> This does not rest on performance alone. Object support is also useful for
> being built in and generic (no third-party extension, nothing to
> pre-generate)
> and for being one primitive: the store side and the runtime cross-worker
> sharing live in the same place, instead of "cache the array" plus "hydrate
> in
> userland" wired together by every library. And the safe-direct registry is
> not
> a userland protocol: a plain user object with no magic and no cycles or
> refs
> takes the fast path automatically via `can_restore_direct()`, and the
> C-only
> registry only covers a few internal classes whose state the generic path
> cannot read. Keeping objects imposes nothing on the ecosystem.
>
> ## Dropping pinned (and the attributes)
>
> > PinnedStatic on the Carbon shape is ~1.5 us [...] there's no preload
> > trick that reaches that number, because preload can't bake a live object
> > graph into an opcode literal
>
> Pinned is the one place a live-object representation still wins clearly,
> for a
> reason the volatile numbers above do not capture. Pinned (and
> `#[PinnedStatic]`) materialize the graph once per worker; after that it is
> a
> plain static read on every subsequent request in that worker, near zero per
> request. The hydration approach pays its hydrate cost on every request
> instead.
> preload cannot reach this either: it can only intern scalar and array
> literals, not bake a live object graph into an opcode literal.
>
> The caveat is that this holds for read-only / immutable shared state, where
> keeping one live instance across requests is correct; a mutable shared
> instance
> would leak between requests. But that is a real and common case: a
> compiled DI
> container, a routing table, config value objects. Your request-registry
> counter
> rebuilds per request from the cache, so it does not reach the per-worker
> amortization, and for the read-only data where it would help, pinned
> already
> does it with less per-request cost.
>
> The attributes are the ergonomic surface over that same mechanism, so I
> would
> keep them in this RFC rather than split them out. They add no new storage
> model; they remove the explicit store/fetch boilerplate for the
> static-state
> case.
>
> ## Where this leaves us
>
> What is already done or committed: the SAPI opt-in model (the
> `allow_unsafe_runtime` flag and the SAPI allowlist are gone, replaced by
> the
> internal opt-in/partition API); the error model; storage-path visibility
> via
> `getCacheStoreType()`; dropping the "top-level ref" idea; the config-array
> fix
> (skipping the request-local prototype for shared graphs, which removes the
> per-fetch array walk so a warm scalar-array fetch is zero-copy); and the
> large-nested object path from (d), with numbers on this same A/B/C. I am
> declining `remember()`, for the lock-safety reason above.
>
> On the central question I went where the measurements led. You were right
> that
> native lost as shipped; I found why (a request-local prototype clone slower
> than re-decoding, plus per-object class lookups and duplicated strings),
> fixed
> all three, and native now beats your deepclone path on the nested object
> workloads, with the full opcache suite and new regression tests passing on
> NTS
> and ZTS. For tiny objects deepclone is still a hair ahead, and for
> read-only
> scalar config a resident literal still wins; I concede both.
>
> So I do think in-engine object storage earns its place now, on performance
> and
> on being a built-in, generic, single primitive (and on pinned's per-worker
> amortization for read-only state). But if the body still prefers a focused
> better-APCu plus a core hydration primitive, that is an outcome I can
> support;
> the capability matters to me more than where it sits, and the work above
> transfers either way.
>
> The revised branch is pushed and the harness is published, so you can check
> the numbers directly; I will also post the full before/after A/B/C here.
> If you
> have a methodology you would prefer, I will run that too.
>
> Thanks again. This got much sharper because you measured it, and it sent
> me to
> a fix I would not have found otherwise.
>
> ## Jakub: the FPM pool boundary is preserved
>
> > The FPM shared hosting part is a problem [...] we consider data leaks
> > between pools as security issues [...] Maybe the solution would be to
> > allow it only if there is one pool enabled.
>
> This is the concern I most wanted to get right, and I think the
> implementation
> answers it without the single-pool restriction. Static Cache is not one
> cache
> shared across pools. FPM creates a separate partition per worker pool in
> the
> master, before any worker forks; each partition owns its own volatile and
> pinned shared-memory backend, and each worker activates only its own pool's
> partition during child initialization, before user code runs. Every cache
> API,
> status call, clear, and the Static Cache part of `opcache_reset()`
> operates on
> the active pool's partition. There is no API path from one pool to another
> pool's data, so the pool boundary stays a security boundary and no policy
> change is needed. If a pool's partition fails to start it gets no Static
> Cache;
> it never falls back to a shared one.
>
> One honest caveat, for the record: the per-pool segments are anonymous
> shared
> mappings created in the master before fork, so a worker inherits every
> pool's
> segment in its address space even though it can only ever address its own
> pool's partition. That is the same exposure model as the main OPcache SHM,
> which is already shared across pools today; the Static Cache is in fact
> more
> isolated, because it is logically partitioned per pool where the script
> cache
> is not. The data-leak-through-the-feature case you raised, one pool reading
> another's cached values through the API, does not exist in this design. If
> on
> top of that we want address-space isolation, so a worker cannot even see
> another pool's bytes, that is a worthwhile hardening (per-pool named
> segments
> mapped only in that pool's children, or unmapping the others post-fork),
> and I
> am happy to do it as a follow-up if you consider it in scope.
>
> Your single-pool suggestion would also work, but per-pool partitions keep
> the
> feature usable for the multi-pool shared-hosting setups where a
> single-cache
> design would otherwise be unacceptable.
>
> ## Timo: thanks for the immutable_cache pointer
>
> > See also Tyson's php-immutable_cache [...] related APCu discussions
>
> Thank you. Tyson told me about `immutable_cache` himself a while ago, and
> it
> shaped my thinking here. I built an internal extension along the same
> lines,
> `colopl_cache`, an APCu-style drop-in for immutable values. What that work
> showed me is that the parts that matter most for this use case (OPcache
> compatibility, behaviour under a JIT-heavy workload, and the Zend VM
> intervention needed for static-state caching) are very hard to get right
> as an
> ordinary extension. That is why I brought this to OPcache as an RFC
> instead of
> shipping another extension: it needs cooperation from the engine, the VM,
> and a
> few internal classes that an extension cannot coordinate cleanly. So the
> prior
> art is genuinely appreciated; it is part of how I arrived here.
>
> Best regards,
> Go Kudo
>

Thanks,

Larry's right, I'm just one reviewer among others. I'm raising the points I
make not as rebuttals but as food for thought for everybody interested
also. And I'm interested in reading what others think about the points I
make, especially the object-serialization part.

I take no criticism of deepclone at all when you improve your
implementation.

I submitted a PR on your fork [1] with a significant cleanup of the
implementation, basically removing the serialize fallback when copying into
SHM. That drops entirely the need for getCacheStoreType() and its enum,
which exposed internal concerns anyway.
The result is both faster, a win for all.

What remains to me (in no specific order):

1. Reserved keys and the FQCN rejection. set()/get() still reject keys
starting with the reserved prefixes and reject loaded class names, and
delete() still takes $key_or_class, because of the attributes. That's leaky
to me.

2. Accepting objects at all - aka implicit serialize on set(), which I
argued as something leaky also to me. I appreciate that you showed that
doing this in one go provides some perf benefits over my two steps
approach. My abstraction-related arguments still stand and I'd be happy to
see what others think about this.

3. static methods - see Larry's reply

4. Attributes. You kept them as "ergonomic surface, no new storage model",
but that does not answer the cost I raised: the JIT paths, the VM hooks,
and the CacheStrategy::Tracking machinery exist only for the attribute
case, and the reserved-key leak in #1 is their concrete footprint on the
explicit API. None of that is needed by the explicit cache. That is exactly
why I would split them into a follow-up: the explicit cache can land and be
reviewed on its own, while the attribute semantics (cross-request shared
mutable state, mutation tracking) get the separate scrutiny they deserve.
Bundled, they couple the review of a simple primitive to the riskiest part
of the patch.

5. Pinned/non-volatile I remain unconvinced. You proved that "materialize
once per worker, then a near-zero static read per request" is something
per-request hydrate cannot match. That is real but it is narrow. If one
cares about perf that much, then moving to a worker-based runtime model
(aka FrankenPHP workers) provides way more evident perf improvement and
doesn't need pinning at all since there, static properties are live for a
worker-long duration.

I feel like it could be easier for everybody to agree on a tighter RFC and
that's what I'm trying to help figure out - the MVP of your proposal.

Again, I'd be happy to read more feedback from others now that I laid my
current stand.

Thanks,
Nicolas

[1] https://github.com/colopl/php-src/pull/4

Reply via email to