Skip to content

Commit fb9d9a4

Browse files
effectfullyv0d1ch
authored andcommitted
[Builtins] Expose 'BuiltinResult' (IntersectMBO#5728)
1 parent 94d8976 commit fb9d9a4

File tree

29 files changed

+396
-297
lines changed

29 files changed

+396
-297
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Changed
2+
3+
- #5728 added `BuiltinResult` and leveraged in places where we used to use `Emitter (EvaluationResult Smth)`.

plutus-core/plutus-core.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ library
113113
PlutusCore.Default
114114
PlutusCore.Default.Builtins
115115
PlutusCore.Error
116+
PlutusCore.Evaluation.ErrorWithCause
116117
PlutusCore.Evaluation.Machine.BuiltinCostModel
117118
PlutusCore.Evaluation.Machine.Ck
118119
PlutusCore.Evaluation.Machine.CostingFun.Core
@@ -205,6 +206,7 @@ library
205206
PlutusCore.Builtin.KnownTypeAst
206207
PlutusCore.Builtin.Meaning
207208
PlutusCore.Builtin.Polymorphism
209+
PlutusCore.Builtin.Result
208210
PlutusCore.Builtin.Runtime
209211
PlutusCore.Builtin.TestKnown
210212
PlutusCore.Builtin.TypeScheme

plutus-core/plutus-core/examples/PlutusCore/Examples/Builtins.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import PlutusCore.Data
2424
import PlutusCore.Evaluation.Machine.BuiltinCostModel
2525
import PlutusCore.Evaluation.Machine.ExBudget
2626
import PlutusCore.Evaluation.Machine.ExBudgetStream
27-
import PlutusCore.Evaluation.Machine.Exception
2827
import PlutusCore.Pretty
2928

3029
import PlutusCore.StdLib.Data.ScottList qualified as Plc

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import PlutusCore.Builtin.KnownType as Export
1111
import PlutusCore.Builtin.KnownTypeAst as Export
1212
import PlutusCore.Builtin.Meaning as Export
1313
import PlutusCore.Builtin.Polymorphism as Export
14+
import PlutusCore.Builtin.Result as Export
1415
import PlutusCore.Builtin.Runtime as Export
1516
import PlutusCore.Builtin.TestKnown as Export
1617
import PlutusCore.Builtin.TypeScheme as Export

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ module PlutusCore.Builtin.Convert (
1313
byteStringToInteger
1414
) where
1515

16+
import PlutusCore.Builtin (BuiltinResult, emit)
17+
import PlutusCore.Evaluation.Result (evaluationFailure)
18+
1619
import ByteString.StrictBuilder (Builder)
1720
import ByteString.StrictBuilder qualified as Builder
1821
import Control.Monad (guard)
@@ -22,18 +25,15 @@ import Data.ByteString qualified as BS
2225
import Data.Text (pack)
2326
import Data.Word (Word64, Word8)
2427
import GHC.ByteOrder (ByteOrder (BigEndian, LittleEndian))
25-
import PlutusCore.Builtin.Emitter (Emitter, emit)
26-
import PlutusCore.Evaluation.Result (EvaluationResult (EvaluationFailure))
2728

2829
-- | Wrapper for 'integerToByteString' to make it more convenient to define as a builtin.
29-
integerToByteStringWrapper ::
30-
Bool -> Integer -> Integer -> Emitter (EvaluationResult ByteString)
30+
integerToByteStringWrapper :: Bool -> Integer -> Integer -> BuiltinResult ByteString
3131
integerToByteStringWrapper endiannessArg lengthArg input
3232
-- Check that we are within the Int range on the non-negative side.
3333
| lengthArg < 0 || lengthArg >= 536870912 = do
3434
emit "integerToByteString: inappropriate length argument"
3535
emit $ "Length requested: " <> (pack . show $ input)
36-
pure EvaluationFailure
36+
evaluationFailure
3737
-- As this builtin hasn't been costed yet, we have to impose a temporary limit of 10KiB on requested
3838
-- sizes via the padding argument. This shouldn't be necessary long-term, as once this function is
3939
-- costed, this won't be a problem.
@@ -42,7 +42,7 @@ integerToByteStringWrapper endiannessArg lengthArg input
4242
| lengthArg > 10240 = do
4343
emit "integerToByteString: padding argument too large"
4444
emit "If you are seeing this, it is a bug: please report this!"
45-
pure EvaluationFailure
45+
evaluationFailure
4646
| otherwise = let endianness = endiannessArgToByteOrder endiannessArg in
4747
-- We use fromIntegral here, despite advice to the contrary in general when defining builtin
4848
-- denotations. This is because, if we've made it this far, we know that overflow or truncation
@@ -54,15 +54,15 @@ integerToByteStringWrapper endiannessArg lengthArg input
5454
-- This does work proportional to the size of input. However, we're in a failing case
5555
-- anyway, and the user's paid for work proportional to this size in any case.
5656
emit $ "Input: " <> (pack . show $ input)
57-
pure EvaluationFailure
57+
evaluationFailure
5858
NotEnoughDigits -> do
5959
emit "integerToByteString: cannot represent Integer in given number of bytes"
6060
-- This does work proportional to the size of input. However, we're in a failing case
6161
-- anyway, and the user's paid for work proportional to this size in any case.
6262
emit $ "Input: " <> (pack . show $ input)
6363
emit $ "Bytes requested: " <> (pack . show $ lengthArg)
64-
pure EvaluationFailure
65-
Right result -> pure . pure $ result
64+
evaluationFailure
65+
Right result -> pure result
6666

6767
-- | Wrapper for 'byteStringToInteger' to make it more convenient to define as a builtin.
6868
byteStringToIntegerWrapper ::
@@ -82,8 +82,7 @@ data IntegerToByteStringError =
8282
--
8383
-- For performance and clarity, the endianness argument uses
8484
-- 'ByteOrder', and the length argument is an 'Int'.
85-
integerToByteString ::
86-
ByteOrder -> Int -> Integer -> Either IntegerToByteStringError ByteString
85+
integerToByteString :: ByteOrder -> Int -> Integer -> Either IntegerToByteStringError ByteString
8786
integerToByteString requestedByteOrder requestedLength input
8887
| input < 0 = Left NegativeInput
8988
| input == 0 = Right . BS.replicate requestedLength $ 0x00

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module PlutusCore.Builtin.Emitter
22
( Emitter (..)
33
, runEmitter
4-
, emit
4+
, MonadEmitter (..)
55
) where
66

77
import Control.Monad.Trans.Writer.Strict (Writer, runWriter, tell)
@@ -17,6 +17,10 @@ runEmitter :: Emitter a -> (a, DList Text)
1717
runEmitter = runWriter . unEmitter
1818
{-# INLINE runEmitter #-}
1919

20-
emit :: Text -> Emitter ()
21-
emit = Emitter . tell . pure
22-
{-# INLINE emit #-}
20+
-- | A type class for \"this monad supports logging\".
21+
class MonadEmitter m where
22+
emit :: Text -> m ()
23+
24+
instance MonadEmitter Emitter where
25+
emit = Emitter . tell . pure
26+
{-# INLINE emit #-}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
{-# LANGUAGE TypeOperators #-}
55

66
module PlutusCore.Builtin.HasConstant
7-
( KnownTypeError (..)
7+
( BuiltinError (..)
88
, throwNotAConstant
99
, HasConstant (..)
1010
, HasConstantIn
1111
, fromValueOf
1212
, fromValue
1313
) where
1414

15+
import PlutusCore.Builtin.Result
1516
import PlutusCore.Core
16-
import PlutusCore.Evaluation.Machine.Exception
1717
import PlutusCore.Name
1818

1919
import Universe
@@ -35,7 +35,7 @@ class HasConstant term where
3535
-- Switching from 'MonadError' to 'Either' here gave us a speedup of 2-4%.
3636
-- | Unwrap from a 'Constant'-like constructor throwing an 'UnliftingError' if the provided
3737
-- @term@ is not a wrapped Haskell value.
38-
asConstant :: term -> Either KnownTypeError (Some (ValueOf (UniOf term)))
38+
asConstant :: term -> Either BuiltinError (Some (ValueOf (UniOf term)))
3939

4040
-- | Wrap a Haskell value as a @term@.
4141
fromConstant :: Some (ValueOf (UniOf term)) -> term

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

Lines changed: 29 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
{-# LANGUAGE StrictData #-}
1717

1818
module PlutusCore.Builtin.KnownType
19-
( KnownTypeError
20-
, throwKnownTypeErrorWithCause
19+
( BuiltinError
20+
, throwBuiltinErrorWithCause
2121
, KnownBuiltinTypeIn
2222
, KnownBuiltinType
23-
, MakeKnownM (..)
23+
, BuiltinResult (..)
2424
, ReadKnownM
25+
, MakeKnownIn (..)
2526
, liftReadKnownM
2627
, readKnownConstant
27-
, MakeKnownIn (..)
2828
, MakeKnown
2929
, ReadKnownIn (..)
3030
, ReadKnown
@@ -37,17 +37,15 @@ import PlutusPrelude
3737
import PlutusCore.Builtin.Emitter
3838
import PlutusCore.Builtin.HasConstant
3939
import PlutusCore.Builtin.Polymorphism
40+
import PlutusCore.Builtin.Result
4041
import PlutusCore.Core
41-
import PlutusCore.Evaluation.Machine.Exception
42+
import PlutusCore.Evaluation.ErrorWithCause
4243
import PlutusCore.Evaluation.Result
4344
import PlutusCore.Pretty
4445

45-
import Control.Lens.TH (makeClassyPrisms)
4646
import Control.Monad.Except
47-
import Data.DList (DList)
4847
import Data.Either.Extras
4948
import Data.String
50-
import Data.Text (Text)
5149
import GHC.Exts (inline, oneShot)
5250
import GHC.TypeLits
5351
import Universe
@@ -67,7 +65,7 @@ It's critically important that 'readKnown' runs in the concrete 'Either' rather
6765
https://github.com/IntersectMBO/plutus/pull/4307
6866
6967
Replacing the @AsUnliftingError err, AsEvaluationFailure err@ constraints with the dedicated
70-
'KnownTypeError' data type gave us a speedup of up to 4%.
68+
'BuiltinError' data type gave us a speedup of up to 4%.
7169
7270
All the same considerations apply to 'makeKnown':
7371
https://github.com/IntersectMBO/plutus/pull/4421
@@ -241,16 +239,16 @@ Lifting is allowed to the following classes of types:
241239
one, and for another example define an instance for 'Void' in tests
242240
-}
243241

244-
-- | Attach a @cause@ to a 'KnownTypeError' and throw that.
242+
-- | Attach a @cause@ to a 'BuiltinError' and throw that.
245243
-- Note that an evaluator might require the cause to be computed lazily for best performance on the
246244
-- happy path, hence this function must not force its first argument.
247245
-- TODO: wrap @cause@ in 'Lazy' once we have it.
248-
throwKnownTypeErrorWithCause
246+
throwBuiltinErrorWithCause
249247
:: (MonadError (ErrorWithCause err cause) m, AsUnliftingError err, AsEvaluationFailure err)
250-
=> cause -> KnownTypeError -> m void
251-
throwKnownTypeErrorWithCause cause = \case
252-
KnownTypeUnliftingError unlErr -> throwingWithCause _UnliftingError unlErr $ Just cause
253-
KnownTypeEvaluationFailure -> throwingWithCause _EvaluationFailure () $ Just cause
248+
=> cause -> BuiltinError -> m void
249+
throwBuiltinErrorWithCause cause = \case
250+
BuiltinUnliftingError unlErr -> throwingWithCause _UnliftingError unlErr $ Just cause
251+
BuiltinEvaluationFailure -> throwingWithCause _EvaluationFailure () $ Just cause
254252

255253
typeMismatchError
256254
:: PrettyParens (SomeTypeIn uni)
@@ -266,89 +264,19 @@ typeMismatchError uniExp uniAct = fromString $ concat
266264
-- failure message and evaluation is about to be shut anyway.
267265
{-# NOINLINE typeMismatchError #-}
268266

269-
-- | The monad that 'makeKnown' runs in.
270-
-- Equivalent to @ExceptT KnownTypeError Emitter@, except optimized in two ways:
271-
--
272-
-- 1. everything is strict
273-
-- 2. has the 'MakeKnownSuccess' constructor that is used for returning a value with no logs
274-
-- attached, which is the most common case for us, so it helps a lot not to construct and
275-
-- deconstruct a redundant tuple
276-
--
277-
-- Moving from @ExceptT KnownTypeError Emitter@ to this data type gave us a speedup of 8% of total
278-
-- evaluation time.
279-
--
280-
-- Logs are represented as a 'DList', because we don't particularly care about the efficiency of
281-
-- logging, since there's no logging on the chain and builtins don't emit much anyway. Otherwise
282-
-- we'd have to use @text-builder@ or @text-builder-linear@ or something of this sort.
283-
data MakeKnownM a
284-
= MakeKnownFailure (DList Text) KnownTypeError
285-
| MakeKnownSuccess a
286-
| MakeKnownSuccessWithLogs (DList Text) a
287-
288-
makeClassyPrisms ''MakeKnownM
289-
290-
instance AsEvaluationFailure (MakeKnownM a) where
291-
_EvaluationFailure = _MakeKnownFailure . _EvaluationFailureVia (pure KnownTypeEvaluationFailure)
292-
{-# INLINE _EvaluationFailure #-}
293-
294-
-- | Prepend logs to a 'MakeKnownM' computation.
295-
withLogs :: DList Text -> MakeKnownM a -> MakeKnownM a
296-
withLogs logs1 = \case
297-
MakeKnownFailure logs2 err -> MakeKnownFailure (logs1 <> logs2) err
298-
MakeKnownSuccess x -> MakeKnownSuccessWithLogs logs1 x
299-
MakeKnownSuccessWithLogs logs2 x -> MakeKnownSuccessWithLogs (logs1 <> logs2) x
300-
{-# INLINE withLogs #-}
301-
302-
instance Functor MakeKnownM where
303-
fmap _ (MakeKnownFailure logs err) = MakeKnownFailure logs err
304-
fmap f (MakeKnownSuccess x) = MakeKnownSuccess (f x)
305-
fmap f (MakeKnownSuccessWithLogs logs x) = MakeKnownSuccessWithLogs logs (f x)
306-
{-# INLINE fmap #-}
307-
308-
-- Written out explicitly just in case (see @fmap@ above for what the case might be).
309-
_ <$ MakeKnownFailure logs err = MakeKnownFailure logs err
310-
x <$ MakeKnownSuccess _ = MakeKnownSuccess x
311-
x <$ MakeKnownSuccessWithLogs logs _ = MakeKnownSuccessWithLogs logs x
312-
{-# INLINE (<$) #-}
313-
314-
instance Applicative MakeKnownM where
315-
pure = MakeKnownSuccess
316-
{-# INLINE pure #-}
317-
318-
MakeKnownFailure logs err <*> _ = MakeKnownFailure logs err
319-
MakeKnownSuccess f <*> a = fmap f a
320-
MakeKnownSuccessWithLogs logs f <*> a = withLogs logs $ fmap f a
321-
{-# INLINE (<*>) #-}
322-
323-
-- Better than the default implementation, because the value in the 'MakeKnownSuccess' case
324-
-- doesn't need to be retained.
325-
MakeKnownFailure logs err *> _ = MakeKnownFailure logs err
326-
MakeKnownSuccess _ *> a = a
327-
MakeKnownSuccessWithLogs logs _ *> a = withLogs logs a
328-
{-# INLINE (*>) #-}
329-
330-
instance Monad MakeKnownM where
331-
MakeKnownFailure logs err >>= _ = MakeKnownFailure logs err
332-
MakeKnownSuccess x >>= f = f x
333-
MakeKnownSuccessWithLogs logs x >>= f = withLogs logs $ f x
334-
{-# INLINE (>>=) #-}
335-
336-
(>>) = (*>)
337-
{-# INLINE (>>) #-}
338-
339267
-- Normally it's a good idea for an exported abstraction not to be a type synonym, since a @newtype@
340268
-- is cheap, looks good in error messages and clearly emphasize an abstraction barrier. However we
341269
-- make 'ReadKnownM' a type synonym for convenience: that way we don't need to derive all the
342270
-- instances (and add new ones whenever we need them), wrap and unwrap all the time (including in
343271
-- user code), which can be non-trivial for such performance-sensitive code (see e.g. 'coerceVia'
344272
-- and 'coerceArg') and there is no abstraction barrier anyway.
345273
-- | The monad that 'readKnown' runs in.
346-
type ReadKnownM = Either KnownTypeError
274+
type ReadKnownM = Either BuiltinError
347275

348-
-- | Lift a 'ReadKnownM' computation into 'MakeKnownM'.
349-
liftReadKnownM :: ReadKnownM a -> MakeKnownM a
350-
liftReadKnownM (Left err) = MakeKnownFailure mempty err
351-
liftReadKnownM (Right x) = MakeKnownSuccess x
276+
-- | Lift a 'ReadKnownM' computation into 'BuiltinResult'.
277+
liftReadKnownM :: ReadKnownM a -> BuiltinResult a
278+
liftReadKnownM (Left err) = BuiltinFailure mempty err
279+
liftReadKnownM (Right x) = BuiltinSuccess x
352280
{-# INLINE liftReadKnownM #-}
353281

354282
-- See Note [Unlifting values of built-in types].
@@ -363,15 +291,15 @@ readKnownConstant val = asConstant val >>= oneShot \case
363291
-- optimize some of the matching away.
364292
case uniExp `geq` uniAct of
365293
Just Refl -> pure x
366-
Nothing -> Left . KnownTypeUnliftingError $ typeMismatchError uniExp uniAct
294+
Nothing -> throwing _UnliftingError $ typeMismatchError uniExp uniAct
367295
{-# INLINE readKnownConstant #-}
368296

369297
-- See Note [Performance of ReadKnownIn and MakeKnownIn instances].
370298
class uni ~ UniOf val => MakeKnownIn uni val a where
371299
-- | Convert a Haskell value to the corresponding PLC value.
372300
-- The inverse of 'readKnown'.
373-
makeKnown :: a -> MakeKnownM val
374-
default makeKnown :: KnownBuiltinType val a => a -> MakeKnownM val
301+
makeKnown :: a -> BuiltinResult val
302+
default makeKnown :: KnownBuiltinType val a => a -> BuiltinResult val
375303
-- Everything on evaluation path has to be strict in production, so in theory we don't need to
376304
-- force anything here. In practice however all kinds of weird things happen in tests and @val@
377305
-- can be non-strict enough to cause trouble here, so we're forcing the argument. Looking at the
@@ -400,9 +328,9 @@ type ReadKnown val = ReadKnownIn (UniOf val) val
400328
-- | Same as 'makeKnown', but allows for neither emitting nor storing the cause of a failure.
401329
makeKnownOrFail :: MakeKnownIn uni val a => a -> EvaluationResult val
402330
makeKnownOrFail x = case makeKnown x of
403-
MakeKnownFailure _ _ -> EvaluationFailure
404-
MakeKnownSuccess val -> EvaluationSuccess val
405-
MakeKnownSuccessWithLogs _ val -> EvaluationSuccess val
331+
BuiltinFailure _ _ -> EvaluationFailure
332+
BuiltinSuccess val -> EvaluationSuccess val
333+
BuiltinSuccessWithLogs _ val -> EvaluationSuccess val
406334
{-# INLINE makeKnownOrFail #-}
407335

408336
-- | Same as 'readKnown', but the cause of a potential failure is the provided term itself.
@@ -411,14 +339,18 @@ readKnownSelf
411339
, AsUnliftingError err, AsEvaluationFailure err
412340
)
413341
=> val -> Either (ErrorWithCause err val) a
414-
readKnownSelf val = fromRightM (throwKnownTypeErrorWithCause val) $ readKnown val
342+
readKnownSelf val = fromRightM (throwBuiltinErrorWithCause val) $ readKnown val
415343
{-# INLINE readKnownSelf #-}
416344

417345
instance MakeKnownIn uni val a => MakeKnownIn uni val (EvaluationResult a) where
418346
makeKnown EvaluationFailure = evaluationFailure
419347
makeKnown (EvaluationSuccess x) = makeKnown x
420348
{-# INLINE makeKnown #-}
421349

350+
instance MakeKnownIn uni val a => MakeKnownIn uni val (BuiltinResult a) where
351+
makeKnown res = res >>= makeKnown
352+
{-# INLINE makeKnown #-}
353+
422354
-- Catching 'EvaluationFailure' here would allow *not* to short-circuit when 'readKnown' fails
423355
-- to read a Haskell value of type @a@. Instead, in the denotation of the builtin function
424356
-- the programmer would be given an explicit 'EvaluationResult' value to handle, which means

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ module PlutusCore.Builtin.KnownTypeAst
3333
import PlutusCore.Builtin.Emitter
3434
import PlutusCore.Builtin.KnownKind
3535
import PlutusCore.Builtin.Polymorphism
36+
import PlutusCore.Builtin.Result
3637
import PlutusCore.Core
3738
import PlutusCore.Evaluation.Result
3839
import PlutusCore.Name
@@ -223,6 +224,13 @@ instance KnownTypeAst tyname uni a => KnownTypeAst tyname uni (EvaluationResult
223224
toTypeAst _ = toTypeAst $ Proxy @a
224225
{-# INLINE toTypeAst #-}
225226

227+
instance KnownTypeAst tyname uni a => KnownTypeAst tyname uni (BuiltinResult a) where
228+
type IsBuiltin _ (BuiltinResult a) = 'False
229+
type ToHoles _ (BuiltinResult a) = '[TypeHole a]
230+
type ToBinds uni acc (BuiltinResult a) = ToBinds uni acc a
231+
toTypeAst _ = toTypeAst $ Proxy @a
232+
{-# INLINE toTypeAst #-}
233+
226234
instance KnownTypeAst tyname uni a => KnownTypeAst tyname uni (Emitter a) where
227235
type IsBuiltin _ (Emitter a) = 'False
228236
type ToHoles _ (Emitter a) = '[TypeHole a]

0 commit comments

Comments
 (0)