Skip to content

fix(Compiler/Expr): replace caseInteger list-indexing fallback with equalsInteger chain#7693

Open
Unisay wants to merge 6 commits intomasterfrom
yura/fix-caseInteger-sop-equalsInteger
Open

fix(Compiler/Expr): replace caseInteger list-indexing fallback with equalsInteger chain#7693
Unisay wants to merge 6 commits intomasterfrom
yura/fix-caseInteger-sop-equalsInteger

Conversation

@Unisay
Copy link
Copy Markdown
Contributor

@Unisay Unisay commented Mar 26, 2026

Summary

Fixes the execution cost regression documented in #7691.

When compiling caseInteger in the default SumsOfProducts mode, the fallback used PlutusTx.List.!!, which built a linked list at runtime and indexed into it with a Z-combinator. This caused a 3-5x cost increase for unsafeFromBuiltinData on multi-constructor types.

This PR replaces the fallback with a lazy equalsInteger/ifThenElse chain in PIR.
Uses the (all dead. resTy) / (/\dead -> branch) encoding to avoid evaluating non-matching branches. No runtime allocation, no Y-combinator, no linked list.

How it works

The non-BuiltinCasing branch in Compiler/Expr.hs now generates PIR equivalent to:

ifThenElse {all dead. resTy} (equalsInteger scrut 0) (/\dead -> b0) (/\dead -> ifThenElse {all dead. resTy} (equalsInteger scrut 1) (/\dead -> b1) (/\dead -> b2) {resTy}) {resTy} 

Which erases to delay/force in UPLC, matching the pre-regression pattern.
The BuiltinCasing branch is unchanged (native case on the integer).

Budget comparison

data ABC = A Integer | B Integer | C Integer, same source compiled in both modes:

SumsOfProducts (default, the regressed mode)

Test Metric Baseline (if False) This PR Regressed (master)
decode A (index 0) CPU 1,244,851 1,292,851 2,214,190
Memory 4,662 4,962 9,964
decode C (index 2) CPU 2,050,823 1,746,441 4,054,578
Memory 7,468 6,366 17,974

Baseline is generated by bypassing caseInteger (if False in unsafeFromDataClause),
which falls back to the old tuple-pattern-matching TH code. The small remaining difference
(~4% for index 0) is from casePair continuation vs fst/snd let-bindings in the
pair extraction, which is inherent to the caseInteger TH code path.

BuiltinCasing (unaffected by this change)

Test Metric Budget
decode A (index 0) CPU 512,582
Memory 2,596
decode C (index 2) CPU 677,790
Memory 2,998
Unisay added 4 commits March 26, 2026 13:52
Adds a minimal test that compiles unsafeFromBuiltinData on a 3-constructor type in the default SumsOfProducts mode (no BuiltinCasing pragma). This captures the caseInteger fallback regression for comparison.
…disabled Temporarily set `if False` in unsafeFromDataClause to bypass caseInteger and generate golden files from the old equalsInteger/ifThenElse code path. These serve as the equivalence target for the Expr.hs fix.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

Execution Budget Golden Diff

4b90cc2 (master) vs 3f451f2

output

This comment will get updated when changes are made.

@Unisay Unisay self-assigned this Mar 26, 2026
@Unisay Unisay marked this pull request as draft March 26, 2026 13:34
@Unisay Unisay force-pushed the yura/fix-caseInteger-sop-equalsInteger branch 2 times, most recently from 43759bb to f2f4424 Compare March 26, 2026 14:29
@Unisay Unisay marked this pull request as ready for review March 26, 2026 14:33
@Unisay Unisay requested a review from zliu41 March 26, 2026 14:37
@Unisay Unisay requested a review from SeungheonOh March 26, 2026 14:37
@Unisay Unisay force-pushed the yura/fix-caseInteger-sop-equalsInteger branch from f2f4424 to 8a53491 Compare March 26, 2026 18:41
Copy link
Copy Markdown
Member

@zliu41 zliu41 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be backported to the version the Hydra team is using.

Copy link
Copy Markdown
Collaborator

@SeungheonOh SeungheonOh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me

@Unisay Unisay force-pushed the yura/fix-caseInteger-sop-equalsInteger branch 3 times, most recently from a298e64 to 759d2f5 Compare March 27, 2026 15:34
Unisay added 2 commits March 27, 2026 17:05
…qualsInteger chain When compiling caseInteger in non-BuiltinCasing mode (SumsOfProducts), the fallback used PlutusTx.List.!! which built a linked list of branches at runtime and indexed into it with a Y-combinator. This caused a 3-5x execution cost regression for unsafeFromBuiltinData on multi-constructor types (reported by the Hydra team, see #7691). Replace the fallback with mkEqualsIntegerChain, which generates a flat chain of equalsInteger/ifThenElse comparisons in PIR. This produces UPLC equivalent to the pre-caseInteger code path with no runtime allocation. See Note [caseInteger non-BuiltinCasing fallback] in Expr.hs.
@Unisay Unisay force-pushed the yura/fix-caseInteger-sop-equalsInteger branch from 759d2f5 to 3f451f2 Compare March 27, 2026 16:05
@Unisay Unisay enabled auto-merge (squash) March 27, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants