Repository navigation
Conversation
Changelog💅 Polish
Semantic Diff2 commits · Full page Check large switches for unused cases without comparing every pair5d69ca2 · cknitt + Claude · Nov 07, 2026, 10:11 UTC
Rows are indexed by the constant or tag at their head, which a row can only share with rows of the same head or of a head without one, such as a wildcard or a range: the earlier rows compatible with a case, the groups of Review
✨ Skip the row index for matches with one case063dd71 · cknitt + Claude · Nov 07, 2026, 19:07 UTC · new The benchmarks showed small matches, such as function parameters and let patterns, allocating up to 0.6% more for the index. get_mins_pats now returns fewer than two rows as they are, and the unused-case check doesn't index the last case, so such matches allocate less than before. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #9261 +/- ##
=======================================
Coverage 81.74% 81.74%
=======================================
Files 457 457
Lines 65713 65775 +62
=======================================
+ Hits 53715 53766 +51
- Misses 11998 12009 +11
🚀 New features to boost your workflow:
|
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
|
Developer playground preview: https://rescript-lang.github.io/rescript/dev-playground/?version=pr-9261 |
Why:
The unused-case check compared each case with every earlier one and
retyped part of each case, and its usefulness checks grouped rows by
scanning all groups, so a switch with n constant or polymorphic
variant cases cost O(n²) in several places: 6,000 int cases took
1.65 s and allocated 430M words, 1,600 polymorphic variant cases
1.31 s and 242M. They now take 0.56 s with 74M words, and 0.74 s with
128M.
Rows are indexed by the constant or tag at their head, which a row can
only share with rows of the same head or of a head without one, such as a
wildcard or a range: the earlier rows compatible with a case, the groups
of `filter_all` and the most general rows of `get_mins_pats` are found
through that index. A case is retyped to find GADT cases that can't match
only when its pattern has a GADT constructor, as no other pattern can fail
to type. Warnings are the same for the test suites and 2,600 generated
switches.
Review: What remains superlinear? The match compiler (`Matching.divide`
and the switch clustering) for constant switches: 6,000 int cases allocate
69M words with the warnings off, 3.4× the words for 3,000.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gK1dcjbe97yY1wRMgs
Signed-off-by: Christoph Knittel <ck@cca.io>
5cf0013 to
5d69ca2
Compare
🎉 Compiler benchmarksAllocated wordsIn total: 456,989,425 → 426,589,061 (-6.65%). 🎉 Improvement: 35 benchmarks allocate more than 1% less.
50 more changes
InstructionsIn total: 10,226,962,100 → 8,709,795,247 (-14.83%). 🎉 Improvement: 4 benchmarks execute more than 1% fewer instructions.
Pull request 063dd71 compared with base bf35764 in this run. |
The benchmarks showed small matches, such as function parameters and let patterns, allocating up to 0.6% more for the index. get_mins_pats now returns fewer than two rows as they are, and the unused-case check doesn't index the last case, so such matches allocate less than before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kbf3gK1dcjbe97yY1wRMgs Signed-off-by: Christoph Knittel <ck@cca.io>
Part of #9256 (unused-case check of large switches).
Since #8900 removed the hash tables, the unused-case check of a switch with n constant or polymorphic variant cases is quadratic in several places:
check_unusedfilters all earlier rows for each case;get_minscompares all pairs of the rows it is given;filter_allfinds a row's group by scanning all groups (this dominated: the last| _row is checked against all n cases);Rows are now indexed by the constant or tag at their head, in a
Mapordered byconst_compare, so no hashing key needs to agree with it. Two such heads are compatible, and one includes the other, only when they are equal, so a row with one is only compared with the rows of the same head and the rows whose head has none (wildcards, ranges, or-patterns, constructors). A range with equal bounds can share a group with a constant, sofilter_allalso looks through the groups without a key.get_mins_patsreplacesget_mins le_patsin all three callers, including the exhaustiveness check andMatching.as_matrix. The GADT check now runs only when the pattern has a GADT constructor, since without one the pattern built from the case's own subpatterns always types.Measured with
bscon linux-arm64, allocated words and wall time:Warnings are unchanged for the test suites and 2,630 generated switches over ints, chars, floats, strings, options, tuples, polymorphic variants and GADTs, with ranges, or-patterns, aliases and guards.
What remains: the match compiler (
Matching.divide,Matching.add, the switch clustering) for constant switches, which grows 3.4× per doubling with the warnings off.New test:
warning_11_unused_constantscovers a constant after an equal-bounds range, constants inside or-patterns, strings and polymorphic variants with payloads.🤖 Generated with Claude Code
https://claude.ai/code/session_01Kbf3gK1dcjbe97yY1wRMgs