From d6508baf3d68c3daaee448a73c624867d7aee3c3 Mon Sep 17 00:00:00 2001 From: Piotr Paradzinski Date: Wed, 18 Mar 2026 14:33:57 +0100 Subject: [PATCH 1/2] add golden tests for ifTenElseDiscardedFail --- .../Transform/EvaluateBuiltins/ifThenElseDiscardedFail | 5 +++++ .../EvaluateBuiltins/ifThenElseDiscardedFail.golden | 1 + 2 files changed, 6 insertions(+) create mode 100644 plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail create mode 100644 plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail.golden diff --git a/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail b/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail new file mode 100644 index 00000000000..bc26725acb7 --- /dev/null +++ b/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail @@ -0,0 +1,5 @@ +[ { (builtin ifThenElse) (con integer) } + (con bool True) + (con integer 1) + [ [ (builtin divideInteger) (con integer 1) ] (con integer 0) ] +] diff --git a/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail.golden b/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail.golden new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail.golden @@ -0,0 +1 @@ +1 \ No newline at end of file From 8c2b7f5d2d6482df65e7707f5f9f63bedd7b93ec Mon Sep 17 00:00:00 2001 From: Piotr Paradzinski Date: Wed, 18 Mar 2026 14:35:36 +0100 Subject: [PATCH 2/2] Conservatively fold matcher-like builtins in EvaluateBuiltins --- .../PlutusIR/Transform/EvaluateBuiltins.hs | 61 ++++++++++++++++++- .../Transform/EvaluateBuiltins/Tests.hs | 1 + .../ifThenElseDiscardedFail.golden | 2 +- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/plutus-core/plutus-ir/src/PlutusIR/Transform/EvaluateBuiltins.hs b/plutus-core/plutus-ir/src/PlutusIR/Transform/EvaluateBuiltins.hs index 36434f20664..9858930a3c7 100644 --- a/plutus-core/plutus-ir/src/PlutusIR/Transform/EvaluateBuiltins.hs +++ b/plutus-core/plutus-ir/src/PlutusIR/Transform/EvaluateBuiltins.hs @@ -15,8 +15,9 @@ import PlutusCore.Builtin import PlutusIR.Contexts import PlutusIR.Core -import Control.Lens (transformOf, (^.)) +import Control.Lens (preview, transformOf, (^.)) import Data.Functor (void) +import Data.Map qualified as Map import PlutusCore qualified as PLC import PlutusIR.Analysis.Builtins import PlutusIR.Pass @@ -90,6 +91,62 @@ evaluateBuiltins preserveLogging binfo costModel = transformOf termSubterms proc -- with the annotation that was on the `ifThenElse` node. But we can't -- easily do better. -- See Note [Unserializable constants] - Just t' | termIsSerializable binfo t' -> x <$ t' + Just t' + | termIsSerializable binfo t' + , matcherLikeBranchesAreValues binfo bn argCtx -> + x <$ t' _ -> t processTerm t = t + + -- Note [Conservative folding for matcher-like builtins] + -- `evaluateBuiltins` may replace a builtin application with its result only when + -- this preserves operational behaviour. + -- Matcher-like builtins (e.g. `ifThenElse`) select one branch and discard others. + -- Replacing the whole application with the selected branch is not conservative if + -- a discarded branch is not already a value. + -- So we only fold matcher-like builtins when all branches are values. + -- See issue #6167. + matcherLikeBranchesAreValues + :: BuiltinsInfo uni fun + -> fun + -> AppContext tyname name uni fun a + -> Bool + matcherLikeBranchesAreValues info bn argCtx = + case Map.lookup bn (info ^. biMatcherLike) of + Nothing -> True + -- Non-matcher-like builtins - do not discard branches + Just (BuiltinMatcherLike splitPrism _) -> + case preview splitPrism argCtx of + Nothing -> False -- be conservative + Just smc -> allBranchesAreValues smc + where + allBranchesAreValues = + all isTermValueLike . extractTermAppArgs . smBranches + + -- Extract only term arguments from an application context, ignoring type arguments. + extractTermAppArgs :: AppContext tyname name uni fun a -> [Term tyname name uni fun a] + extractTermAppArgs = go [] + where + go acc = \case + TermAppContext arg _ ctx -> go (arg : acc) ctx + TypeAppContext _ _ ctx -> go acc ctx + AppContextEnd -> reverse acc + + -- A conservative PIR-side approximation of "already a value". + -- Used only to decide whether it is safe to discard an unchosen branch + -- of a matcher-like builtin during constant folding. + isTermValueLike :: Term tyname name uni fun a -> Bool + isTermValueLike = \case + TyAbs {} -> True + LamAbs {} -> True + Constant {} -> True + Constr _ _ _ xs -> all isTermValueLike xs + IWrap _ _ _ t -> isTermValueLike t + Var {} -> False + Error {} -> False + Let {} -> False + Apply {} -> False + TyInst {} -> False + Unwrap {} -> False + Builtin {} -> False + Case {} -> False diff --git a/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/Tests.hs b/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/Tests.hs index 1595e1d3c3d..3a123ba07a3 100644 --- a/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/Tests.hs +++ b/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/Tests.hs @@ -26,6 +26,7 @@ test_evaluateBuiltins = ) [ "addInteger" , "ifThenElse" + , "ifThenElseDiscardedFail" , "traceConservative" , "failingBuiltin" , "nonConstantArg" diff --git a/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail.golden b/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail.golden index 56a6051ca2b..d2b478c5ed1 100644 --- a/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail.golden +++ b/plutus-core/plutus-ir/test/PlutusIR/Transform/EvaluateBuiltins/ifThenElseDiscardedFail.golden @@ -1 +1 @@ -1 \ No newline at end of file +ifThenElse {integer} True 1 (divideInteger 1 0) \ No newline at end of file