Skip to content

Commit fb11a0e

Browse files
authored
[Builtins] Remove 'HeadSpine' from 'BuiltinRuntime' (#7211)
This removes `HeadSpine` from `BuiltinRuntime` now that we don't need it for pattern matching builtins (because they're removed).
1 parent 2aca705 commit fb11a0e

File tree

14 files changed

+53
-201
lines changed

14 files changed

+53
-201
lines changed

plutus-core/plutus-core/src/PlutusCore/Builtin/KnownType.hs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,8 @@ deriving via PrettyCommon (HeadSpine a b)
346346
class uni ~ UniOf val => MakeKnownIn uni val a where
347347
-- | Convert a Haskell value to the corresponding PLC value.
348348
-- The inverse of 'readKnown'.
349-
makeKnown :: a -> BuiltinResult (HeadSpine val val)
350-
default makeKnown :: KnownBuiltinType val a => a -> BuiltinResult (HeadSpine val val)
349+
makeKnown :: a -> BuiltinResult val
350+
default makeKnown :: KnownBuiltinType val a => a -> BuiltinResult val
351351
-- Everything on evaluation path has to be strict in production, so in theory we don't need to
352352
-- force anything here. In practice however all kinds of weird things happen in tests and @val@
353353
-- can be non-strict enough to cause trouble here, so we're forcing the argument. Looking at the
@@ -356,7 +356,7 @@ class uni ~ UniOf val => MakeKnownIn uni val a where
356356
--
357357
-- Note that the value is only forced to WHNF, so care must be taken to ensure that every value
358358
-- of a type from the universe gets forced to NF whenever it's forced to WHNF.
359-
makeKnown x = pure . HeadOnly . fromValue $! x
359+
makeKnown x = pure . fromValue $! x
360360
{-# INLINE makeKnown #-}
361361

362362
type MakeKnown val = MakeKnownIn (UniOf val) val
@@ -374,7 +374,7 @@ class uni ~ UniOf val => ReadKnownIn uni val a where
374374
type ReadKnown val = ReadKnownIn (UniOf val) val
375375

376376
-- | Same as 'makeKnown', but allows for neither emitting nor storing the cause of a failure.
377-
makeKnownOrFail :: MakeKnownIn uni val a => a -> EvaluationResult (HeadSpine val val)
377+
makeKnownOrFail :: MakeKnownIn uni val a => a -> EvaluationResult val
378378
makeKnownOrFail x = case makeKnown x of
379379
BuiltinSuccess val -> EvaluationSuccess val
380380
BuiltinSuccessWithLogs _ val -> EvaluationSuccess val
@@ -420,21 +420,17 @@ instance
420420
{-# INLINE readKnown #-}
421421

422422
instance HasConstantIn uni val => MakeKnownIn uni val (SomeConstant uni rep) where
423-
makeKnown = coerceArg $ pure . HeadOnly . fromConstant
423+
makeKnown = coerceArg $ pure . fromConstant
424424
{-# INLINE makeKnown #-}
425425

426426
instance HasConstantIn uni val => ReadKnownIn uni val (SomeConstant uni rep) where
427427
readKnown = coerceVia (fmap SomeConstant .) asConstant
428428
{-# INLINE readKnown #-}
429429

430430
instance uni ~ UniOf val => MakeKnownIn uni val (Opaque val rep) where
431-
makeKnown = coerceArg $ pure . HeadOnly
431+
makeKnown = coerceArg pure
432432
{-# INLINE makeKnown #-}
433433

434434
instance uni ~ UniOf val => ReadKnownIn uni val (Opaque val rep) where
435435
readKnown = coerceArg pure
436436
{-# INLINE readKnown #-}
437-
438-
instance uni ~ UniOf val => MakeKnownIn uni val (Opaque (HeadSpine val val) rep) where
439-
makeKnown = coerceArg pure
440-
{-# INLINE makeKnown #-}

plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import NoThunks.Class
3030
-- Evaluators that ignore the entire concept of costing (e.g. the CK machine) may of course force
3131
-- the result of the builtin application unconditionally.
3232
data BuiltinRuntime val
33-
= BuiltinCostedResult ExBudgetStream ~(BuiltinResult (HeadSpine val val))
33+
= BuiltinCostedResult ExBudgetStream ~(BuiltinResult val)
3434
| BuiltinExpectArgument (val -> BuiltinRuntime val)
3535
| BuiltinExpectForce (BuiltinRuntime val)
3636

plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs

Lines changed: 4 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ import GHC.Types (Int (..))
5656
import NoThunks.Class (NoThunks)
5757
import Prettyprinter (viaShow)
5858

59-
-- See Note [Pattern matching on built-in types].
6059
-- TODO: should we have the commonest built-in functions at the front to have more compact encoding?
6160
-- | Default built-in functions.
6261
--
@@ -111,7 +110,7 @@ data DefaultFun
111110
| TailList
112111
| NullList
113112
-- Data
114-
-- See Note [Pattern matching on built-in types].
113+
-- See Note [Legacy pattern matching on built-in types].
115114
-- It is convenient to have a "choosing" function for a data type that has more than two
116115
-- constructors to get pattern matching over it and we may end up having multiple such data
117116
-- types, hence we include the name of the data type as a suffix.
@@ -941,82 +940,10 @@ goes without saying that this is not supposed to be done.
941940
942941
So overall one needs to be very careful when defining built-in functions that have explicit
943942
'Opaque' and 'SomeConstant' arguments. Expressiveness doesn't come for free.
944-
945-
Read Note [Pattern matching on built-in types] next.
946-
-}
947-
948-
{- Note [Pattern matching on built-in types]
949-
Pattern matching over an enumeration built-in type ('Void', 'Unit', 'Bool' etc) is trivially
950-
implementable, see the 'IfThenElse' example in Note [How to add a built-in function: simple cases].
951-
Not so much for algebraic data types with at least one constructor carrying some kind of content.
952-
For example the @(:)@ constructor of @[a]@ has two arguments (an @a@ and a @[a]@) and all
953-
constructors of 'Data' carry something (e.g. 'I' carries an 'Integer' and 'Constr' carries an
954-
@Integer@ and a @[Data]@).
955-
956-
In Haskell we'd represent the pattern matching function for lists as follows:
957-
958-
caseList f z xs0 = case xs0 of
959-
[] -> z
960-
x:xs -> f x xs
961-
962-
but in the denotation of a built-in function all those @f@, @z@ and @xs0@ are of type @val@, i.e.
963-
the type of values that the given evaluator uses (e.g. 'CkValue' for the CK machine or 'CekValue'
964-
for the CEK machine) and we don't know to apply one @val@ to another. We could try to constrain
965-
@val@ to implement some kind of "apply" function or try to somehow "parse" it into Haskell so that
966-
it becomes @val -> val@, but even if there was a way of doing either thing, it would be very
967-
complex and, more importantly, it's just not the job of the builtins machinery to evaluate such
968-
applications, it's what the actual evaluator is supposed to do.
969-
970-
Hence we employ a very simple strategy: whenever we want to return an iterated application of a
971-
@val@ to a bunch of @val@s from a built-in function, we just construct it as is without trying to
972-
evaluate it and let the evaluator perform all the necessary reductions.
973-
974-
So if you want to return an application from a built-in function, you need to use 'HeadSpine' at the
975-
type level and 'headSpine' at the term level, where the latter has the following signature:
976-
977-
headSpineOpaque :: Opaque val asToB -> [val] -> Opaque (MonoHeadSpine val) b
978-
979-
'headSpine' takes the head of the application, i.e. a function from @a0@, @a1@ ... @an@ to @b@, and
980-
applies it to a list of values of respective types, returning a `b`. Whether types match or not is
981-
not checked, since that would be hard and largely pointless, so don't make mistakes.
982-
983-
Back to the pattern matcher for lists:
984-
985-
caseList f z xs0 = case xs0 of
986-
[] -> z
987-
x:xs -> f x xs
988-
989-
Here's how we can define it as a built-in function using 'headSpine':
990-
991-
toBuiltinMeaning _ver CaseList =
992-
let caseListDenotation
993-
:: Opaque val b
994-
-> Opaque val (a -> [a] -> b)
995-
-> SomeConstant uni [a]
996-
-> BuiltinResult (Opaque (MonoHeadSpine val) b)
997-
caseListDenotation z f (SomeConstant (Some (ValueOf uniListA xs0))) = do
998-
case uniListA of
999-
DefaultUniList uniA -> pure $ case xs0 of
1000-
[] -> headSpineOpaque z [] -- [1]
1001-
x : xs -> headSpineOpaque f [fromValueOf uniA x, fromValueOf uniListA xs] -- [2]
1002-
_ ->
1003-
throwError $ structuralUnliftingError "Expected a list but got something else"
1004-
{-# INLINE caseListDenotation #-}
1005-
in makeBuiltinMeaning
1006-
caseListDenotation
1007-
<costingFunction>
1008-
1009-
All the unlifting logic is the same as with, say, 'NullList' from
1010-
Note [How to add a built-in function: complicated cases], the only things that are different are [1]
1011-
and [2]. In [2] we have an iterated application of the given function @f@ to the head of the list
1012-
@x@ (lifted from a constant value to @val@ via 'fromValueOf') and the tail of the list @xs@ (lifted
1013-
to @val@ the same way). In [1] we return the given @z@, but since we need to return a 'HeadSpine'
1014-
(required by [2]), we have to use 'headSpine' just like in [2] except with an empty spine, since @z@
1015-
isn't applied to anything.
1016943
-}
1017944

1018945
{- Note [Representable built-in functions over polymorphic built-in types]
1019-
In Note [Pattern matching on built-in types] we discussed how general higher-order polymorphic
946+
Note [Legacy pattern matching on built-in types] discusses how general higher-order polymorphic
1020947
built-in functions are troubling, but polymorphic built-in functions can be troubling even in
1021948
the first-order case. In a Plutus program we always pair constants of built-in types with their
1022949
tags from the universe, which means that in order to produce a constant embedded into a program
@@ -1103,17 +1030,6 @@ throw an "operational" evaluation error). Please respect the distinction when ad
11031030
functions.
11041031
-}
11051032

1106-
-- | Take a function and a list of arguments and apply the former to the latter.
1107-
headSpineOpaque :: Opaque val asToB -> [val] -> Opaque (MonoHeadSpine val) b
1108-
headSpineOpaque (Opaque f) = Opaque . \case
1109-
[] -> HeadOnly f
1110-
x0 : xs ->
1111-
-- It's critical to use 'foldr' here, so that deforestation kicks in.
1112-
-- See Note [Definition of foldl'] in "GHC.List" and related Notes around for an explanation
1113-
-- of the trick.
1114-
HeadSpine f $ foldr (\x2 r x1 -> SpineCons x1 $ r x2) SpineLast xs x0
1115-
{-# INLINE headSpineOpaque #-}
1116-
11171033
instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where
11181034
type CostingPart uni DefaultFun = BuiltinCostModel
11191035

@@ -2346,7 +2262,7 @@ instance Flat DefaultFun where
23462262

23472263
{- Note [Legacy pattern matching on built-in types]
23482264
We used to only support direct pattern matching on enumeration types: 'Void', 'Unit', 'Bool'
2349-
etc. This is because it was impossible to return an iterated application from a built-in function.
2265+
etc. This is because it was impossible to 'Case' on a value of a built-in type.
23502266
23512267
So e.g. if we wanted to add the following data type:
23522268
@@ -2413,5 +2329,5 @@ concerns are omitted for clarity):
24132329
which, for example, evaluates to @fMap es@ when @d@ is @Map es@
24142330
24152331
We decided to handle lists the same way by using @chooseList@ rather than @null@ for consistency,
2416-
before introduction of pattern matching builtins.
2332+
before introduction of casing on values of built-in types.
24172333
-}

plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Ck.hs

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -251,27 +251,9 @@ FrameCase cs : stack <| e = case e of
251251
Right (HeadSpine f xs) -> transferConstantSpine xs stack |> f
252252
_ -> throwErrorWithCause (StructuralError NonConstrScrutinizedMachineError) $ ckValueToTerm e
253253

254-
-- | Transfers a 'Spine' onto the stack. The first argument will be at the top of the stack.
255-
--
256-
-- >>> import PlutusCore.Default
257-
-- >>> import PlutusCore.Builtin
258-
-- >>> transferValueSpine (SpineCons (fromValue (1 :: Integer)) (SpineLast (fromValue (2 :: Integer)))) [FrameUnwrap :: Frame DefaultUni DefaultFun]
259-
-- [FrameAwaitFunValue (VCon (Some (ValueOf DefaultUniInteger 1))),FrameAwaitFunValue (VCon (Some (ValueOf DefaultUniInteger 2))),FrameUnwrap]
260-
transferValueSpine :: Spine (CkValue uni fun) -> Context uni fun -> Context uni fun
261-
transferValueSpine args ctx = foldr ((:) . FrameAwaitFunValue) ctx args
262-
263254
transferConstantSpine :: Spine (Some (ValueOf uni)) -> Context uni fun -> Context uni fun
264255
transferConstantSpine args ctx = foldr ((:) . FrameAwaitFunValue . VCon) ctx args
265256

266-
-- | Evaluate a 'HeadSpine' by pushing the arguments (if any) onto the stack and proceeding with
267-
-- the returning phase of the CK machine, i.e. @<|@.
268-
returnCkHeadSpine
269-
:: Context uni fun
270-
-> HeadSpine (CkValue uni fun) (CkValue uni fun)
271-
-> CkM uni fun s (Term TyName Name uni fun ())
272-
returnCkHeadSpine stack (HeadOnly x) = stack <| x
273-
returnCkHeadSpine stack (HeadSpine f xs) = transferValueSpine xs stack <| f
274-
275257
-- | Take a possibly partial builtin application and
276258
--
277259
-- - either create a 'CkValue' by evaluating the application if it's saturated (emitting logs, if
@@ -286,9 +268,9 @@ evalBuiltinApp
286268
-> CkM uni fun s (Term TyName Name uni fun ())
287269
evalBuiltinApp stack term runtime = case runtime of
288270
BuiltinCostedResult _ getFXs -> case getFXs of
289-
BuiltinSuccess fXs -> returnCkHeadSpine stack fXs
290-
BuiltinSuccessWithLogs logs fXs -> emitCkM logs *> returnCkHeadSpine stack fXs
291-
BuiltinFailure logs err -> emitCkM logs *> throwBuiltinErrorWithCause term err
271+
BuiltinSuccess y -> stack <| y
272+
BuiltinSuccessWithLogs logs y -> emitCkM logs *> (stack <| y)
273+
BuiltinFailure logs err -> emitCkM logs *> throwBuiltinErrorWithCause term err
292274
_ -> stack <| VBuiltin term runtime
293275

294276
-- | Instantiate a term with a type and proceed.

plutus-core/plutus-core/stdlib/PlutusCore/StdLib/Data/List.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import PlutusCore.StdLib.Data.Unit
3131
list :: uni `HasTypeLevel` [] => Type tyname uni ()
3232
list = mkTyBuiltin @_ @[] ()
3333

34-
-- See Note [Pattern matching on built-in types].
3534
-- | Pattern matching on built-in lists. @matchList {a} xs@ on built-in lists is
3635
-- equivalent to @unwrap xs@ on lists defined in PLC itself (hence why we bind @r@ after @xs@).
3736
--

plutus-core/plutus-core/test/Evaluation/Spec.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ prop_builtinEvaluation ::
123123
-- outcome, and decides whether to pass or fail the property.
124124
(fun ->
125125
[Term uni fun] ->
126-
Either SomeException (BuiltinResult (MonoHeadSpine (Term uni fun))) ->
126+
Either SomeException (BuiltinResult (Term uni fun)) ->
127127
PropertyT IO ()) ->
128128
Property
129129
prop_builtinEvaluation runtimes bn mkGen f = property $ do
@@ -132,9 +132,9 @@ prop_builtinEvaluation runtimes bn mkGen f = property $ do
132132
eval ::
133133
[Term uni fun] ->
134134
BuiltinRuntime (Term uni fun) ->
135-
BuiltinResult (MonoHeadSpine (Term uni fun))
136-
eval [] (BuiltinCostedResult _ getFxs) =
137-
getFxs
135+
BuiltinResult (Term uni fun)
136+
eval [] (BuiltinCostedResult _ y) =
137+
y
138138
eval (arg : args) (BuiltinExpectArgument toRuntime) =
139139
eval args (toRuntime arg)
140140
eval args (BuiltinExpectForce runtime) =

plutus-core/plutus-ir/src/PlutusIR/Transform/EvaluateBuiltins.hs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ module PlutusIR.Transform.EvaluateBuiltins
1111
) where
1212

1313
import PlutusCore.Builtin
14-
import PlutusCore.MkPlc (headSpineToTermNoAnn)
1514
import PlutusIR.Contexts
1615
import PlutusIR.Core
1716

@@ -56,19 +55,18 @@ evaluateBuiltins preserveLogging binfo costModel = transformOf termSubterms proc
5655
-> Maybe (Term tyname name uni fun ())
5756
eval (BuiltinCostedResult _ getFXs) AppContextEnd =
5857
case getFXs of
59-
BuiltinSuccess fXs -> Just $ headSpineToTermNoAnn fXs
58+
BuiltinSuccess y -> Just y
6059
-- Evaluates successfully, but does logging. If we're being conservative
6160
-- then we should leave these in, so we don't remove people's logging!
6261
-- Otherwise `trace "hello" x` is a prime candidate for evaluation!
63-
BuiltinSuccessWithLogs _ fXs ->
64-
if preserveLogging then Nothing else Just $ headSpineToTermNoAnn fXs
62+
BuiltinSuccessWithLogs _ y -> if preserveLogging then Nothing else Just y
6563
-- Evaluation failure. This can mean that the evaluation legitimately
6664
-- failed (e.g. `divideInteger 1 0`), or that it failed because the
6765
-- argument terms are not currently in the right form (because they're
6866
-- not evaluated, we're in the middle of a term here!). Since we can't
6967
-- distinguish these, we have to assume it's the latter case and just leave
7068
-- things alone.
71-
BuiltinFailure{} -> Nothing
69+
BuiltinFailure{} -> Nothing
7270
eval (BuiltinExpectArgument toRuntime) (TermAppContext arg _ ctx) =
7371
-- Builtin evaluation does not work with annotations, so we have to throw
7472
-- the argument annotation away here

plutus-core/testlib/PlutusCore/Generators/Hedgehog/Test.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,5 @@ propEvaluate eval genTermOfTbv = withTests 200 . property $ do
115115
Left (TypeEvalCheckErrorException err) -> fail err
116116
Left (TypeEvalCheckErrorIllEvaled expected actual) ->
117117
-- Ditto.
118-
ShowPretty expected === ShowPretty (fmap HeadOnly actual)
118+
ShowPretty expected === ShowPretty actual
119119
Right _ -> return ()

plutus-core/testlib/PlutusCore/Generators/Hedgehog/TypeEvalCheck.hs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ data TypeEvalCheckError uni fun
5151
!(Normalized (Type TyName uni ()))
5252
| TypeEvalCheckErrorException !String
5353
| TypeEvalCheckErrorIllEvaled
54-
!(EvaluationResult (MonoHeadSpine (Term TyName Name uni fun ())))
54+
!(EvaluationResult (Term TyName Name uni fun ()))
5555
!(EvaluationResult (Term TyName Name uni fun ()))
5656
-- ^ The former is an expected result of evaluation, the latter -- is an actual one.
5757

@@ -65,7 +65,6 @@ data TypeEvalCheckResult uni fun = TypeEvalCheckResult
6565

6666
instance ( PrettyBy config (Type TyName uni ())
6767
, PrettyBy config (Term TyName Name uni fun ())
68-
, PrettyBy config (MonoHeadSpine (Term TyName Name uni fun ()))
6968
, PrettyBy config (Error uni fun ())
7069
) => PrettyBy config (TypeEvalCheckError uni fun) where
7170
prettyBy config (TypeEvalCheckErrorIllFormed err) =
@@ -105,7 +104,7 @@ typeEvalCheckBy eval (TermOf term (x :: a)) = TermOf term <$> do
105104
if tyExpected == tyActual
106105
then case splitStructuralOperational $ eval term of
107106
Right valActual ->
108-
if valExpected == fmap HeadOnly valActual
107+
if valExpected == valActual
109108
then return $ TypeEvalCheckResult tyExpected valActual
110109
else throwError $ TypeEvalCheckErrorIllEvaled valExpected valActual
111110
Left exc -> throwError $ TypeEvalCheckErrorException $ show exc

plutus-core/testlib/PlutusCore/Generators/Hedgehog/TypedBuiltinGen.hs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ attachCoercedTerm a = do
6767
[ "Got 'EvaluationFailure' when generating a value of a built-in type: "
6868
, render $ prettyConst botRenderContext x
6969
]
70-
EvaluationSuccess res -> case res of
71-
HeadOnly v -> pure $ TermOf v x
72-
_ -> fail "Iterated application is not supported"
70+
EvaluationSuccess res -> pure $ TermOf res x
7371

7472
-- | Update a typed built-ins generator by overwriting the generator for a certain built-in.
7573
updateTypedBuiltinGen

0 commit comments

Comments
 (0)