Skip to content

preload time monomorphization of functions#12

Open
henderkes wants to merge 30 commits into
bottledcode:reifyfrom
henderkes:reify
Open

preload time monomorphization of functions#12
henderkes wants to merge 30 commits into
bottledcode:reifyfrom
henderkes:reify

Conversation

@henderkes

Copy link
Copy Markdown

No description provided.

azjezz and others added 28 commits June 17, 2026 19:40
Signed-off-by: azjezz <azjezz@protonmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>

fix inheritance chaining

Signed-off-by: Robert Landers <landers.robert@gmail.com>

fix inheritance chaining

Signed-off-by: Robert Landers <landers.robert@gmail.com>
Specialize concrete turbofish calls at compile time: Graph\dfs::<string,int>()
compiles to a direct by-name call to the mangled monomorph and drops the
per-call ZEND_VERIFY_GENERIC_ARGUMENTS opcode, making it byte-identical to a
non-generic call. Monomorphs are synthesized and registered on first
reference; the runtime verify/bind path remains as the fallback for
non-concrete / inference-only call sites.

Also: persist the generic value-check plan into opcache SHM, and gate
destroy_op_array's monomorph-table scan on ZEND_ACC2_HAS_GENERIC_CALL_OPS so
non-generic code is not taxed.

PSL\Graph bench (aarch64, opcache, two-point Ir/iter): reified-vs-erased
overhead 6.51%->4.14% (all) / 5.72%->4.19% (query); wall 9.04%->5.89% (no-JIT).
Erased baseline unchanged; generics tax -37%. Digests identical across
no-JIT/tracing/-n, generics .phpt 0 new regressions, valgrind-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
Turbofish monomorphs are shallow copies of their base op_array that share the
opcode buffer (zend_synthesize_function_monomorph). The tracing JIT keys
compiled code and patched opline handlers on opcode addresses and bakes
op_array-specific constants (arg_info for RECV / VERIFY_RETURN_TYPE, inferred
operand types, cached literals), which are valid for only one substituted
arg_info layout. A trace compiled for one monomorph was therefore reused for a
sibling carrying a different binding, enforcing the wrong type: e.g.
nestedGen::<Fooable&Barable>($fb) was checked against an earlier
nestedGen::<int|string> binding and wrongly threw "must be of type string|int"
under opcache.jit=tracing (the interpreter and jit=disable were correct).

The interpreter avoids this via the TRAIT_CLONE slow-path RECV; the JIT has no
equivalent and the hazard is not limited to RECV, so keep monomorphizable
generics and their monomorphs interpreted until per-monomorph JIT state exists.

Signed-off-by: Robert Landers <landers.robert@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a bare type-parameter leaf (`T $x`) is bound to a concrete generic
instantiation via turbofish (e.g. `id::<DBox<L2<int>>>($v)`), the binding
arrives as a pre-erasure named-with-args type. zend_substitute_leaf_type_param_origin
returned it verbatim, so the synthesized monomorph's arg_info (and return type)
kept the named-with-args payload. The reified RECV check then reached
zend_fetch_ce_from_type(), which reads ZEND_TYPE_NAME() and interpreted that
payload as a zend_string: at depth >= 2 it dereferenced a garbage length and
tried a multi-terabyte allocation; at depth 1 it raised a spurious TypeError.

Fold a fully concrete named-with-args binding to a plain CLASS reference to the
monomorph's canonical class name, mirroring the named-with-args branch in the
same function. That is the erased shape the runtime arg / property / return
checks already understand, and it matches the class the value itself carries
(so inference and turbofish now agree). Reproduces in the interpreter and under
opcache; not JIT-specific.

Signed-off-by: Robert Landers <landers.robert@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
zend_try_synthesize_monomorph_by_name() backs the class-existence lookup path
(class_exists(), interface_exists(), is-a probes, the autoloader). It raised in
two cases: a non-generic base carrying type arguments ("Plain<int>") threw, and
an out-of-bound argument ("Box<int>" for Box<T : object>) reached
zend_synthesize_monomorph(), whose bound check fatals via zend_error_noreturn.
So class_exists('Box<int>') aborted with a fatal instead of returning false.

A name whose arguments are invalid simply names a class that does not exist, so
the lookup now reports "not found" (NULL): the non-generic case returns NULL
directly, and bounds are validated silently up front, returning NULL on
violation before synthesis. The throwing enforcement stays on the `new` /
type-declaration paths (compile-time and zend_synthesize_monomorph), where a
bound violation is a genuine error — verified that `new Box::<int>()` and a
`Plain<int>` type position still fatal.

Signed-off-by: Robert Landers <landers.robert@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
…t happen to have the same name

Signed-off-by: Robert Landers <landers.robert@gmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
… also make monomorphs share static props

Signed-off-by: Robert Landers <landers.robert@gmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
Signed-off-by: Robert Landers <landers.robert@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2edef910-1d36-4c34-ac32-a2d85e68ba43

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@henderkes henderkes changed the base branch from master to reify June 20, 2026 13:04
@henderkes henderkes force-pushed the reify branch 7 times, most recently from 3335ce1 to 10d21f7 Compare June 21, 2026 13:06
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.

3 participants