Skip to content

Resolve loose comparison of two strings or two integers as identical - #6985

Open
ondrejmirtes wants to merge 2 commits into
2.3.xfrom
loose-equal-same-type-fast-path
Open

ondrejmirtes wants to merge 2 commits into
2.3.xfrom
loose-equal-same-type-fast-path

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

resolveEqualType() now resolves == between two strings (at least one of them non-numeric) or two integers through resolveIdenticalType(). In those cases loose comparison behaves exactly like strict comparison, and IdenticalNarrowingHelper already narrows them this way. Since 9d55d96 ("Narrow loose comparisons by the values each side can loosely equal"), the expression type of every $string == 'const' computed getPossiblyLooselyEqualType() / getAlwaysLooselyEqualType() for both sides and for each representative PHP version. For plain string that is rebuilt from toValueSet() on every call. With the shortcut, resolveEqualType() time on the reproducers drops from ~0.4–0.5 s to ~2 ms.

Narrowing precision is unchanged. The inferred types of 47 == / != / in_array() / switch probes on strings, numeric strings, accessory strings, constant unions and int ranges are byte-identical to 2.3.x-dev, both for PHP 8.4 and for a phpVersion range of 7.4–8.5, with the turbo extension loaded and without it. The turbo-ext mirror in InitializerExprTypeResolver.cpp gets the same shortcut.

Also adds the regression bench tests/bench/data/loose-string-comparison.php: a 1000-case switch and 1000 if ($op == 'xN') checks.

Measurements

Best of 5, wall time and peak RSS, analyse -l 8 --debug, PHP 8.4, no turbo extension. 2.3.x-dev (8af6dd7) and this PR were compiled into PHARs with the repo's compiler, the same way as the releases.

2.1.34 2.3.0 2.3.x-dev this PR
1000-case switch 135.82 s / 136 MB 1.19 s / 180 MB 1.50 s / 185 MB 1.25 s / 179 MB
1000 if ($op == 'xN') 1.55 s / 140 MB 1.12 s / 174 MB 1.40 s / 178 MB 1.14 s / 173 MB
wordpress-develop class-wp-html-processor.php 20.85 s / 194 MB 2.96 s / 192 MB 3.05 s / 189 MB 2.93 s / 190 MB

On the trivial-file baseline, PHAR startup alone is ~1.06 s on this machine. From source with the turbo extension loaded: switch 0.80 → 0.70 s and if 0.78 → 0.66 s.

The switch case is still ~0.06 s above 2.3.0, but not because of 9d55d96. 9d55d96 with this change applied analyses it faster than its parent (0.90 s vs 0.96 s from source), so the remainder comes from other commits since 2.3.0.

Verified with make tests (with and without the turbo extension), make phpstan, make cs, turbo-ext/bin/side-by-side.php, turbo-ext/tests/smoke.php and turbo-ext/tests/signature-parity.php.

Two strings of which one is not numeric, and two integers, cannot coerce,
so their loose comparison is the identical one. resolveEqualType() now
answers it through resolveIdenticalType() like the narrowing in
IdenticalNarrowingHelper already does, instead of computing the values
each side can loosely equal for every PHP version in the configured
range. A switch over a string with many case labels or a long chain of
`$string == 'constant'` conditions spent most of its time there.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1b250-12b7-7206-9d98-c1f38e89010e
…arison

ConstantStringType::isNumericString() follows the PHP version running
PHPStan, so on PHP 7.4 '1 ' counted as non-numeric and '1 ' == '1' was
resolved as identical even when analysing for PHP 8, where both strings
are numeric and compare equal. The same-type shortcut now also requires
the constant strings to be non-numeric in each of the PHP versions the
comparison is resolved for.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1b250-12b7-7206-9d98-c1f38e89010e
@ondrejmirtes
ondrejmirtes force-pushed the loose-equal-same-type-fast-path branch from 57771fc to b9b8ff1 Compare November 6, 2026 20:51
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.

1 participant