[php-src] PR #23305: Elide provably-passing return type checks at compile time
| From: | staabm | Date: | Sun, 16 Aug 2026 08:19:42 +0000 |
| Subject: | [php-src] PR #23305: Elide provably-passing return type checks at compile time | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-37911@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/23305
Author: staabm
disclaimer: this change was generated by claude opus. I have little experience with php-src
development
----
Without the opcache optimizer every 'return <expr>;' in a typed function executes
ZEND_VERIFY_RETURN_TYPE. Skip emitting it when the check provably always passes:
- expressions whose opcode always produces a specific type accepted by the return type: comparisons,
instanceof, isset/empty and boolean operators produce bool; concatenation produces string;
- 'return $this->prop;' where the property is declared in the class being compiled and
its type is a subtype of the return type. Property types are invariant under inheritance, and typed
property reads always yield conforming values: uninitialized reads throw before the return, and
__get()/get-hook results are verified against the property type. Only same-class declarations are
visible during method compilation, which keeps the proof local.
Closures are excluded (Closure::bind can rebind scopes), as are by-ref returns, generators,
union/intersection property types and context-dependent return types (static, callable). Coercion
semantics are preserved by requiring the value type set to be a subset of the return type set (e.g.
an int property returned as float still emits the check).
Getter-style code drops one opcode per return; 'return $x === null;' style predicates drop
the check entirely.
----
before PR
```
➜ php-src git:(elide-return) ✗ ./sapi/cli/php_old -n -dopcache.enable_cli=0
return_type_check_elision_repro.php
PHP 8.6.0-dev
iterations=5000000 rounds=7 calls_per_round=40000000
case best(s) avg(s) sink
--------------------------------------------------
getter-direct 0.358463 0.359450 280000000
getter-control 0.409889 0.412215 280000000
cmp-direct 0.462745 0.465084 39999990
cmp-control 0.529274 0.532243 39999990
Direct/control ratio inside each pair (best time, lower is better):
getter 0.8745x
cmp 0.8743x
```
after PR
```
➜ php-src git:(elide-return) ✗ ./sapi/cli/php -n -dopcache.enable_cli=0
return_type_check_elision_repro.php
PHP 8.6.0-dev
iterations=5000000 rounds=7 calls_per_round=40000000
case best(s) avg(s) sink
--------------------------------------------------
getter-direct 0.337137 0.337611 280000000
getter-control 0.415257 0.416750 280000000
cmp-direct 0.448156 0.451637 39999990
cmp-control 0.536600 0.539734 39999990
Direct/control ratio inside each pair (best time, lower is better):
getter 0.8119x
cmp 0.8352x
```
using
[return_type_check_elision_repro.php](https://gist.github.com/staabm/127abcf5aafc47d0b178eda39e525ad2)