[php-src] PR #24370: Skip the parent-chain walk in instanceof for a final or enum RHS

From: Date: Fri, 16 Oct 2026 08:54:30 +0000
Subject: [php-src] PR #24370: Skip the parent-chain walk in instanceof for a final or enum RHS
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-39499@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/24370 Author: GromNaN $x instanceof FinalClass walks the whole parent chain of $x before it can return false. Because a final class cannot be extended, and an enum is always final, such a right hand side can never be an ancestor of the left hand side, so the walk can stop immediately. The check is added in instanceof_function_slow(), in the branch that walks the parent chain. The interface branch is left untouched, since interfaces can never be final. The mask tests ZEND_ACC_FINAL|ZEND_ACC_ENUM because internal enums created by zend_register_internal_enum() set only ZEND_ACC_ENUM, for example RoundingMode and SortDirection. The inline helper instanceof_function() was not used for the check, because ZEND_ACC_FINAL is defined in zend_compile.h, which zend_operators.h does not include, and adding it there would create an include cycle through zend_globals.h. Putting the check in the ZEND_INSTANCEOF opcode handler was also tried, but it slows the shared handler on the non final paths by about 6 to 8 percent. Benchmarks, PHP 8.7.0-dev on macOS arm64, opcache CLI on, JIT off, inline loops, 30M iterations per case, median of 12 paired runs: | case | baseline | patched | ratio | | --- | --- | --- | --- | | final hit | 104.4 Mops/s | 103.6 Mops/s | 0.997x | | final miss, depth 1 | 96.5 | 100.1 | 1.023x | | final miss, depth 3 | 92.7 | 99.9 | 1.078x | | final miss, depth 6 | 87.1 | 100.7 | 1.147x | | enum miss | 97.9 | 99.8 | 1.027x | | interface miss | 96.9 | 95.3 | 0.998x | | plain hit | 103.3 | 103.2 | 1.003x | | plain miss | 97.2 | 95.9 | 0.983x | The final misses become flat at about 100 Mops/s whatever the depth, close to the hit cost. The noise floor, measured with the same protocol on the baseline against itself, is about 2 percent. The depth 1 case only gains about 2 percent, because at depth 1 the only saving is a single pointer load. The win grows with the depth of the left hand side hierarchy, which is the realistic case in frameworks. Under JIT tracing the same direction holds, but that mode is much noisier and has no dedicated ZEND_INSTANCEOF codegen. The new test Zend/tests/instanceof_final.phpt covers a final class hit and miss, a final class that implements an interface, a userland enum, an internal enum, a readonly final class, non object left operands, an undefined right operand, a variable right operand, and self, parent and static. It produces the same output on the patched and unpatched binaries. The whole Zend/tests suite passes with 0 failures.

« previous php.git-pulls (#39499) next »