-
Notifications
You must be signed in to change notification settings - Fork 479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Builtins] Replace 'EvaluationResult' with 'BuiltinResult' #5926
Changes from all commits
dcd62b0
14431d1
756d477
06a7b66
d85e2ae
7b04a16
c1ae873
5ec73ef
00bbf5a
a8934a3
afc5acc
abb3824
77f3aea
fa1e81f
c1eb33d
f2478c9
6806fe9
3fa4ed0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
### Changed | ||
|
||
- Forbade using `EvaluationResult` in the builtins code in favor of `BuiltinResult` in #5926, so that builtins throw errors with more helpful messages. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -248,9 +248,8 @@ typeMismatchError uniExp uniAct = | |
, "expected: " ++ displayBy botRenderContext (SomeTypeIn uniExp) | ||
, "; actual: " ++ displayBy botRenderContext (SomeTypeIn uniAct) | ||
] | ||
-- Just for tidier Core to get generated, we don't care about performance here, since it's just a | ||
-- failure message and evaluation is about to be shut anyway. | ||
{-# NOINLINE typeMismatchError #-} | ||
-- See Note [INLINE and OPAQUE on error-related definitions]. | ||
{-# OPAQUE typeMismatchError #-} | ||
|
||
-- Normally it's a good idea for an exported abstraction not to be a type synonym, since a @newtype@ | ||
-- is cheap, looks good in error messages and clearly emphasize an abstraction barrier. However we | ||
|
@@ -322,11 +321,6 @@ readKnownSelf | |
readKnownSelf val = fromRightM (throwBuiltinErrorWithCause val) $ readKnown val | ||
{-# INLINE readKnownSelf #-} | ||
|
||
instance MakeKnownIn uni val a => MakeKnownIn uni val (EvaluationResult a) where | ||
makeKnown EvaluationFailure = evaluationFailure | ||
makeKnown (EvaluationSuccess x) = makeKnown x | ||
{-# INLINE makeKnown #-} | ||
|
||
instance MakeKnownIn uni val a => MakeKnownIn uni val (BuiltinResult a) where | ||
makeKnown res = res >>= makeKnown | ||
{-# INLINE makeKnown #-} | ||
|
@@ -338,24 +332,38 @@ instance MakeKnownIn uni val a => MakeKnownIn uni val (BuiltinResult a) where | |
-- I.e. it would essentially allow us to catch errors and handle them in a programmable way. | ||
-- We forbid this, because it complicates code and isn't supported by evaluation engines anyway. | ||
instance | ||
( TypeError ('Text "‘EvaluationResult’ cannot appear in the type of an argument") | ||
( TypeError ('Text "‘BuiltinResult’ cannot appear in the type of an argument") | ||
, uni ~ UniOf val | ||
) => ReadKnownIn uni val (BuiltinResult a) where | ||
readKnown _ = throwUnderTypeError | ||
{-# INLINE readKnown #-} | ||
|
||
instance | ||
( TypeError ('Text "Use ‘BuiltinResult’ instead of ‘EvaluationResult’") | ||
, uni ~ UniOf val | ||
) => MakeKnownIn uni val (EvaluationResult a) where | ||
makeKnown _ = throwUnderTypeError | ||
{-# INLINE makeKnown #-} | ||
|
||
instance | ||
( TypeError ('Text "Use ‘BuiltinResult’ instead of ‘EvaluationResult’") | ||
, uni ~ UniOf val | ||
) => ReadKnownIn uni val (EvaluationResult a) where | ||
readKnown _ = throwing _StructuralUnliftingError "Panic: 'TypeError' was bypassed" | ||
-- Just for 'readKnown' not to appear in the generated Core. | ||
readKnown _ = throwUnderTypeError | ||
{-# INLINE readKnown #-} | ||
|
||
instance MakeKnownIn uni val a => MakeKnownIn uni val (Emitter a) where | ||
makeKnown a = case runEmitter a of | ||
(x, logs) -> withLogs logs $ makeKnown x | ||
instance | ||
( TypeError ('Text "Use ‘BuiltinResult’ instead of ‘Emitter’") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly, we tell the user to use So now we tell the user to use a single monad for both logging and failing, which I believe is a simplification. |
||
, uni ~ UniOf val | ||
) => MakeKnownIn uni val (Emitter a) where | ||
makeKnown _ = throwUnderTypeError | ||
{-# INLINE makeKnown #-} | ||
|
||
instance | ||
( TypeError ('Text "‘Emitter’ cannot appear in the type of an argument") | ||
( TypeError ('Text "Use ‘BuiltinResult’ instead of ‘Emitter’") | ||
, uni ~ UniOf val | ||
) => ReadKnownIn uni val (Emitter a) where | ||
readKnown _ = throwing _StructuralUnliftingError "Panic: 'TypeError' was bypassed" | ||
-- Just for 'readKnown' not to appear in the generated Core. | ||
readKnown _ = throwUnderTypeError | ||
{-# INLINE readKnown #-} | ||
|
||
instance HasConstantIn uni val => MakeKnownIn uni val (SomeConstant uni rep) where | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
-- editorconfig-checker-disable-file | ||
{-# LANGUAGE FlexibleInstances #-} | ||
{-# LANGUAGE FunctionalDependencies #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
{-# LANGUAGE MultiParamTypeClasses #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE RankNTypes #-} | ||
{-# LANGUAGE StrictData #-} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoops! I moved I do prefer to enable |
||
{-# LANGUAGE TemplateHaskell #-} | ||
|
||
module PlutusCore.Builtin.Result | ||
|
@@ -21,6 +23,7 @@ module PlutusCore.Builtin.Result | |
, _StructuralUnliftingError | ||
, _OperationalUnliftingError | ||
, throwNotAConstant | ||
, throwUnderTypeError | ||
, withLogs | ||
, throwing | ||
, throwing_ | ||
|
@@ -39,13 +42,14 @@ import Data.Bitraversable | |
import Data.DList (DList) | ||
import Data.String (IsString) | ||
import Data.Text (Text) | ||
import Data.Text qualified as Text | ||
import Prettyprinter | ||
|
||
-- | The error message part of an 'UnliftingEvaluationError'. | ||
newtype UnliftingError = MkUnliftingError | ||
{ unUnliftingError :: Text | ||
} deriving stock (Show, Eq) | ||
deriving newtype (IsString, Semigroup, NFData) | ||
deriving newtype (IsString, Semigroup, Monoid, NFData) | ||
|
||
-- | When unlifting of a PLC term into a Haskell value fails, this error is thrown. | ||
newtype UnliftingEvaluationError = MkUnliftingEvaluationError | ||
|
@@ -55,7 +59,7 @@ newtype UnliftingEvaluationError = MkUnliftingEvaluationError | |
|
||
-- | The type of errors that 'readKnown' and 'makeKnown' can return. | ||
data BuiltinError | ||
= BuiltinUnliftingEvaluationError !UnliftingEvaluationError | ||
= BuiltinUnliftingEvaluationError UnliftingEvaluationError | ||
| BuiltinEvaluationFailure | ||
deriving stock (Show, Eq) | ||
|
||
|
@@ -143,6 +147,10 @@ instance MonadEmitter BuiltinResult where | |
emit txt = BuiltinSuccessWithLogs (pure txt) () | ||
{-# INLINE emit #-} | ||
|
||
instance MonadFail BuiltinResult where | ||
fail err = BuiltinFailure (pure $ Text.pack err) BuiltinEvaluationFailure | ||
{-# INLINE fail #-} | ||
|
||
instance Pretty UnliftingError where | ||
pretty (MkUnliftingError err) = fold | ||
[ "Could not unlift a value:", hardline | ||
|
@@ -155,6 +163,21 @@ instance Pretty BuiltinError where | |
pretty (BuiltinUnliftingEvaluationError err) = "Builtin evaluation failure:" <+> pretty err | ||
pretty BuiltinEvaluationFailure = "Builtin evaluation failure" | ||
|
||
{- Note [INLINE and OPAQUE on error-related definitions] | ||
We mark error-related definitions such as prisms like '_StructuralUnliftingError' and regular | ||
functions like 'throwNotAConstant' with @INLINE@, because this produces significantly less cluttered | ||
GHC Core. Not doing so results in 20+% larger Core for builtins. | ||
|
||
However in a few specific cases we use @OPAQUE@ instead to get tighter Core. @OPAQUE@ is the same as | ||
@NOINLINE@ except the former _actually_ prevents GHC from inlining the definition unlike the latter. | ||
See this for details: https://github.com/ghc-proposals/ghc-proposals/blob/5577fd008924de8d89cfa9855fa454512e7dcc75/proposals/0415-opaque-pragma.rst | ||
|
||
It's hard to predict where @OPAQUE@ instead of @INLINE@ will help to make GHC Core tidier, so it's | ||
mostly just looking into the Core and seeing where there's obvious duplication that can be removed. | ||
Such cases tend to be functions returning a value of a concrete error type (as opposed to a type | ||
variable). | ||
-} | ||
|
||
-- See Note [Ignoring context in OperationalEvaluationError]. | ||
-- | Construct a prism focusing on the @*EvaluationFailure@ part of @err@ by taking | ||
-- that @*EvaluationFailure@ and | ||
|
@@ -181,6 +204,10 @@ throwNotAConstant :: MonadError BuiltinError m => m void | |
throwNotAConstant = throwing _StructuralUnliftingError "Not a constant" | ||
{-# INLINE throwNotAConstant #-} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does inlining things like this make any difference to general performance? Presumably this is something that's only going to happen once, so is optimising it helpful? I may well be missing something important though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, it can make it worse 🙃 I'm doing it, because
I'll turn this into a Note, thanks for the question. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turned out to be quite a rabbit hole. I've added this Note:
If we use I'll also bring that |
||
|
||
throwUnderTypeError :: MonadError BuiltinError m => m void | ||
throwUnderTypeError = throwing _StructuralUnliftingError "Panic: 'TypeError' was bypassed" | ||
{-# INLINE throwUnderTypeError #-} | ||
|
||
-- | Prepend logs to a 'BuiltinResult' computation. | ||
withLogs :: DList Text -> BuiltinResult a -> BuiltinResult a | ||
withLogs logs1 = \case | ||
|
@@ -242,6 +269,7 @@ instance MonadError BuiltinError BuiltinResult where | |
(OperationalEvaluationError | ||
(MkUnliftingError operationalErr))) -> pure operationalErr | ||
_ -> mempty | ||
{-# INLINE throwError #-} | ||
|
||
-- Throwing logs out is lame, but embedding them into the error would be weird, since that | ||
-- would change the error. Not that any of that matters, we only implement this because it's a | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now recommend using
BuiltinResult
instead ofEvaluationResult
for all builtins. The latter doesn't allow for attaching any error messages to errors, so it simply shouldn't be used (it's now a type error to useEvaluationResult
in a builtin).