Skip to content
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

Remove immediate unlifting #4879

Merged
merged 7 commits into from
Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,3 @@ The main documentation is located https://plutus.readthedocs.io/en/latest/[here]
You are free to copy, modify, and distribute this software under the terms of the Apache 2.0 license.

See the link:./LICENSE[LICENSE] and link:./NOTICE[NOTICE] files for details.



2 changes: 1 addition & 1 deletion plutus-core/cost-model/budgeting-bench/Benchmarks/Nops.hs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ nopCostModel =

nopCostParameters :: MachineParameters CekMachineCosts CekValue DefaultUni NopFun
nopCostParameters =
mkMachineParameters def defaultUnliftingMode $
mkMachineParameters def $
CostModel defaultCekMachineCosts nopCostModel

-- This is just to avoid some deeply nested case expressions for the NopNc
Expand Down
2 changes: 0 additions & 2 deletions plutus-core/plutus-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ library
PlutusCore.Evaluation.Machine.ExMemory
PlutusCore.Evaluation.Machine.MachineParameters
PlutusCore.Evaluation.Machine.MachineParameters.Default
PlutusCore.Evaluation.Machine.MachineParameters.DeferredMachineParameters
PlutusCore.Evaluation.Machine.MachineParameters.ImmediateMachineParameters
PlutusCore.Evaluation.Result
PlutusCore.Examples.Builtins
PlutusCore.Examples.Data.Data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ instance (ToBuiltinMeaning uni fun1, ToBuiltinMeaning uni fun2

data BuiltinVersion (Either fun1 fun2) = PairV (BuiltinVersion fun1) (BuiltinVersion fun2)
toBuiltinMeaning (PairV verL _) (Left fun) = case toBuiltinMeaning verL fun of
BuiltinMeaning tySch toF (BuiltinRuntimeOptions immF defF) ->
BuiltinMeaning tySch toF (BuiltinRuntimeOptions (immF . fst) (defF . fst))
BuiltinMeaning tySch toF denot ->
BuiltinMeaning tySch toF (denot . fst)
toBuiltinMeaning (PairV _ verR) (Right fun) = case toBuiltinMeaning verR fun of
BuiltinMeaning tySch toF (BuiltinRuntimeOptions immF defF) ->
BuiltinMeaning tySch toF (BuiltinRuntimeOptions (immF . snd) (defF . snd))
BuiltinMeaning tySch toF denot ->
BuiltinMeaning tySch toF (denot . snd)

instance (Default (BuiltinVersion fun1), Default (BuiltinVersion fun2))
=> Default (BuiltinVersion (Either fun1 fun2)) where
Expand Down
1 change: 0 additions & 1 deletion plutus-core/plutus-core/src/PlutusCore.hs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ module PlutusCore
, freshTyName
-- * Evaluation
, EvaluationResult (..)
, UnliftingMode (..)
-- * Combining programs
, applyProgram
-- * Benchmarking
Expand Down
104 changes: 32 additions & 72 deletions plutus-core/plutus-core/src/PlutusCore/Builtin/Meaning.hs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type family FoldArgs args res where
FoldArgs (arg ': args) res = arg -> FoldArgs args res

-- | The meaning of a built-in function consists of its type represented as a 'TypeScheme',
-- its Haskell denotation and a 'BuiltinRuntimeOptions'.
-- its Haskell denotation and a costing function.
--
-- The 'TypeScheme' of a built-in function is used for example for
--
Expand All @@ -64,7 +64,7 @@ data BuiltinMeaning val cost =
forall args res. BuiltinMeaning
(TypeScheme val args res)
~(FoldArgs args res)
(BuiltinRuntimeOptions val cost)
(cost -> BuiltinRuntime val)

-- | Constraints available when defining a built-in function.
type HasMeaningIn uni val = (Typeable val, ExMemoryUsage val, HasConstantIn uni val)
Expand Down Expand Up @@ -176,7 +176,7 @@ into a single 'BuiltinRuntime'.
-}

-- | A class that allows us to derive a monotype for a builtin.
-- We could've computed the runtime denotations ('toImmediateF' and 'toDeferredF') from the
-- We could've computed the runtime denotation from the
-- 'TypeScheme' and the denotation of the builtin, but not statically (due to unfolding not working
-- for recursive functions and 'TypeScheme' being recursive, i.e. requiring the conversion function
-- to be recursive), and so it would cause us to retain a lot of evaluation-irrelevant stuff in the
Expand All @@ -189,18 +189,11 @@ into a single 'BuiltinRuntime'.
class KnownMonotype val args res where
knownMonotype :: TypeScheme val args res

-- | Convert the denotation of a builtin to its runtime counterpart with immediate unlifting.
-- We use a tuple rather than two arguments for symmetry with 'toPolyDeferredF'. It all gets
-- inlined anyway.
toMonoImmediateF
:: (FoldArgs args res, FoldArgs args ExBudget)
-> BuiltinRuntime val

-- | Convert the denotation of a builtin to its runtime counterpart with deferred unlifting.
-- | Convert the denotation of a builtin to its runtime counterpart .
-- The argument is in 'ReadKnownM', because that's what deferred unlifting amounts to:
-- passing the action returning the builtin application around until full saturation, which is
-- when the action actually gets run.
toMonoDeferredF
toMonoF
:: ReadKnownM (FoldArgs args res, FoldArgs args ExBudget)
-> BuiltinRuntime val

Expand All @@ -210,12 +203,9 @@ instance (Typeable res, KnownTypeAst (UniOf val) res, MakeKnown val res) =>
KnownMonotype val '[] res where
knownMonotype = TypeSchemeResult

toMonoImmediateF (x, cost) = BuiltinResult cost $ makeKnown x
{-# INLINE toMonoImmediateF #-}

-- For deferred unlifting we need to lift the 'ReadKnownM' action into 'MakeKnownM',
-- We need to lift the 'ReadKnownM' action into 'MakeKnownM',
-- hence 'liftReadKnownM'.
toMonoDeferredF =
toMonoF =
either
-- Unlifting has failed and we don't care about costing at this point, since we're about
-- to terminate evaluation anyway, hence we put 'mempty' as the cost of the operation.
Expand All @@ -231,7 +221,7 @@ instance (Typeable res, KnownTypeAst (UniOf val) res, MakeKnown val res) =>
-- reasonable enough.
(BuiltinResult mempty . MakeKnownFailure mempty)
(\(x, cost) -> BuiltinResult cost $ makeKnown x)
{-# INLINE toMonoDeferredF #-}
{-# INLINE toMonoF #-}

{- Note [One-shotting runtime denotations]
In @KnownMonotype val (arg ': args) res@ we 'oneShot' the runtime denotations. Otherwise GHC creates
Expand Down Expand Up @@ -265,62 +255,41 @@ instance
) => KnownMonotype val (arg ': args) res where
knownMonotype = TypeSchemeArrow knownMonotype

-- See Note [One-shotting runtime denotations].
-- Unlift, then recurse.
toMonoImmediateF (f, exF) = BuiltinExpectArgument . oneShot $ \arg -> do
x <- readKnown arg
-- See Note [Strict application in runtime denotations].
let !exY = exF x
pure $ toMonoImmediateF @val @args @res (f x, exY)
{-# INLINE toMonoImmediateF #-}

-- See Note [One-shotting runtime denotations].
-- Grow the builtin application within the received action and recurse on the result.
toMonoDeferredF getBoth = BuiltinExpectArgument . oneShot $ \arg ->
toMonoF getBoth = BuiltinExpectArgument . oneShot $ \arg ->
-- Ironically computing the unlifted value strictly is the best way of doing deferred
-- unlifting. This means that while the resulting 'ReadKnownM' is only handled upon full
-- saturation and any evaluation failure is only registered when the whole builtin
-- application is evaluated, a Haskell exception will occur the same way as with immediate
-- unlifting. It shouldn't matter though, because a builtin is not supposed to throw an
-- exception at any stage, that would be a bug regardless of how unlifting is aligned.
--
-- 'pure' signifies that no failure can occur at this point.
pure . toMonoDeferredF @val @args @res $! do
-- application is evaluated.
-- It shouldn't matter though, because a builtin is not supposed to throw an
-- exception at any stage, that would be a bug regardless.
toMonoF @val @args @res $! do
(f, exF) <- getBoth
x <- readKnown arg
-- See Note [Strict application in runtime denotations].
let !exY = exF x
pure (f x, exY)
{-# INLINE toMonoDeferredF #-}
{-# INLINE toMonoF #-}

-- | A class that allows us to derive a polytype for a builtin.
class KnownMonotype val args res => KnownPolytype (binds :: [Some TyNameRep]) val args res where
knownPolytype :: TypeScheme val args res

-- | Convert the denotation of a builtin to its runtime counterpart with immediate unlifting.
-- We use a tuple rather than two arguments for symmetry with 'toPolyDeferredF'. It all gets
-- inlined anyway.
toPolyImmediateF
:: (FoldArgs args res, FoldArgs args ExBudget)
-> BuiltinRuntime val

-- | Convert the denotation of a builtin to its runtime counterpart with deferred unlifting.
-- The argument is in 'ReadKnownM', because that's what deferred unlifting amounts to:
-- | Convert the denotation of a builtin to its runtime counterpart.
-- The argument is in 'ReadKnownM', because that's what we need to do:
-- passing the action returning the builtin application around until full saturation, which is
-- when the action actually gets run.
toPolyDeferredF
toPolyF
:: ReadKnownM (FoldArgs args res, FoldArgs args ExBudget)
-> BuiltinRuntime val

-- | Once we've run out of type-level arguments, we start handling term-level ones.
instance KnownMonotype val args res => KnownPolytype '[] val args res where
knownPolytype = knownMonotype

toPolyImmediateF = toMonoImmediateF @val @args @res
{-# INLINE toPolyImmediateF #-}

toPolyDeferredF = toMonoDeferredF @val @args @res
{-# INLINE toPolyDeferredF #-}
toPolyF = toMonoF @val @args @res
{-# INLINE toPolyF #-}

-- Here we unpack an existentially packed @kind@ and constrain it afterwards!
-- So promoted existentials are true sigmas! If we were at the term level, we'd have to pack
Expand All @@ -331,11 +300,8 @@ instance (KnownSymbol name, KnownNat uniq, KnownKind kind, KnownPolytype binds v
KnownPolytype ('Some ('TyNameRep @kind name uniq) ': binds) val args res where
knownPolytype = TypeSchemeAll @name @uniq @kind Proxy $ knownPolytype @binds

toPolyImmediateF = BuiltinExpectForce . toPolyImmediateF @binds @val @args @res
{-# INLINE toPolyImmediateF #-}

toPolyDeferredF = BuiltinExpectForce . toPolyDeferredF @binds @val @args @res
{-# INLINE toPolyDeferredF #-}
toPolyF = BuiltinExpectForce . toPolyF @binds @val @args @res
{-# INLINE toPolyF #-}

-- | Ensure a built-in function is not nullary and throw a nice error otherwise.
type ThrowOnBothEmpty :: [Some TyNameRep] -> [GHC.Type] -> Bool -> GHC.Type -> GHC.Constraint
Expand Down Expand Up @@ -378,31 +344,25 @@ instance
) => MakeBuiltinMeaning a val where
makeBuiltinMeaning f toExF =
BuiltinMeaning (knownPolytype @binds @val @args @res) f $
BuiltinRuntimeOptions
-- See Note [Optimizations of runCostingFun*] for why we use strict @case@.
{ _broImmediateF =
\cost -> case toExF cost of
!exF -> toPolyImmediateF @binds @val @args @res (f, exF)
, _broDeferredF =
\cost -> case toExF cost of
!exF -> toPolyDeferredF @binds @val @args @res $ pure (f, exF)
}
-- See Note [Optimizations of runCostingFun*] for why we use strict @case@.
\cost ->
case toExF cost of
!exF -> toPolyF @binds @val @args @res $ pure (f, exF)
{-# INLINE makeBuiltinMeaning #-}

-- | Convert a 'BuiltinMeaning' to a 'BuiltinRuntime' given an 'UnliftingMode' and a cost model.
toBuiltinRuntime :: UnliftingMode -> cost -> BuiltinMeaning val cost -> BuiltinRuntime val
toBuiltinRuntime unlMode cost (BuiltinMeaning _ _ runtimeOpts) =
fromBuiltinRuntimeOptions unlMode cost runtimeOpts
-- | Convert a 'BuiltinMeaning' to a 'BuiltinRuntime' given a cost model.
toBuiltinRuntime :: cost -> BuiltinMeaning val cost -> BuiltinRuntime val
toBuiltinRuntime cost (BuiltinMeaning _ _ denot) = denot cost
{-# INLINE toBuiltinRuntime #-}

-- See Note [Inlining meanings of builtins].
-- | Calculate runtime info for all built-in functions given denotations of builtins,
-- an 'UnliftingMode' and a cost model.
-- and a cost model.
toBuiltinsRuntime
:: (cost ~ CostingPart uni fun, ToBuiltinMeaning uni fun, HasMeaningIn uni val)
=> BuiltinVersion fun -> UnliftingMode -> cost -> BuiltinsRuntime fun val
toBuiltinsRuntime ver unlMode cost =
let arr = tabulateArray $ toBuiltinRuntime unlMode cost . inline toBuiltinMeaning ver
=> BuiltinVersion fun -> cost -> BuiltinsRuntime fun val
toBuiltinsRuntime ver cost =
let arr = tabulateArray $ toBuiltinRuntime cost . inline toBuiltinMeaning ver
in -- Force array elements to WHNF
foldr seq (BuiltinsRuntime arr) arr
{-# INLINE toBuiltinsRuntime #-}
37 changes: 2 additions & 35 deletions plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import NoThunks.Class

-- | A 'BuiltinRuntime' represents a possibly partial builtin application.
-- We get an initial 'BuiltinRuntime' representing an empty builtin application (i.e. just the
-- builtin with no arguments) by instantiating (via 'fromBuiltinRuntimeOptions') a
-- 'BuiltinRuntimeOptions'.
-- builtin with no arguments) by instantiating.
--
-- Applying or type-instantiating a builtin peels off the corresponding constructor from its
-- 'BuiltinRuntime'.
Expand All @@ -34,11 +33,7 @@ import NoThunks.Class
-- the result of the builtin application unconditionally.
data BuiltinRuntime val
= BuiltinResult ExBudget ~(MakeKnownM val)
-- 'ReadKnownM' is required here only for immediate unlifting, because deferred unlifting
-- doesn't need the ability to fail in the middle of a builtin application, but having a uniform
-- interface for both the ways of doing unlifting is way too convenient, hence we decided to pay
-- the price (about 1-2% of total evaluation time) for now.
| BuiltinExpectArgument (val -> ReadKnownM (BuiltinRuntime val))
| BuiltinExpectArgument (val -> BuiltinRuntime val)
| BuiltinExpectForce (BuiltinRuntime val)

instance NoThunks (BuiltinRuntime val) where
Expand All @@ -55,34 +50,6 @@ instance NoThunks (BuiltinRuntime val) where

showTypeOf = const "PlutusCore.Builtin.Runtime.BuiltinRuntime"

-- | Determines how to unlift arguments. The difference is that with 'UnliftingImmediate' unlifting
-- is performed immediately after a builtin gets the argument and so can fail immediately too, while
-- with deferred unlifting all arguments are unlifted upon full saturation, hence no failure can
-- occur until that. The former makes it much harder to specify the behaviour of builtins and
-- so 'UnliftingDeferred' is the preferred mode.
data UnliftingMode
= UnliftingImmediate
| UnliftingDeferred

-- | A 'BuiltinRuntimeOptions' is a precursor to 'BuiltinRuntime'. One gets the latter from the
-- former by applying a function returning the runtime denotation of the builtin (either
-- '_broImmediateF' for immediate unlifting or '_broDeferredF' for deferred unlifting, see
-- 'UnliftingMode' for details) to a cost model.
data BuiltinRuntimeOptions val cost = BuiltinRuntimeOptions
{ _broImmediateF :: cost -> BuiltinRuntime val
, _broDeferredF :: cost -> BuiltinRuntime val
}

-- | Convert a 'BuiltinRuntimeOptions' to a 'BuiltinRuntime' given an 'UnliftingMode' and a cost
-- model.
fromBuiltinRuntimeOptions
:: UnliftingMode -> cost -> BuiltinRuntimeOptions val cost -> BuiltinRuntime val
fromBuiltinRuntimeOptions unlMode cost (BuiltinRuntimeOptions immF defF) =
case unlMode of
UnliftingImmediate -> immF cost
UnliftingDeferred -> defF cost
{-# INLINE fromBuiltinRuntimeOptions #-}

instance NFData (BuiltinRuntime val) where
-- 'BuiltinRuntime' is strict (verified by the 'NoThunks' tests), hence we only need to force
-- this to WHNF to get it forced to NF.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,9 @@ applyEvaluate stack (VBuiltin term runtime) arg = do
case runtime of
-- It's only possible to apply a builtin application if the builtin expects a term
-- argument next.
BuiltinExpectArgument f -> case f arg of
Left err -> throwKnownTypeErrorWithCause argTerm err
Right runtime' -> do
res <- evalBuiltinApp term' runtime'
stack <| res
BuiltinExpectArgument f -> do
res <- evalBuiltinApp term' $ f arg
stack <| res
_ ->
throwingWithCause _MachineError UnexpectedBuiltinTermArgumentMachineError (Just term')
applyEvaluate _ val _ =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ module PlutusCore.Evaluation.Machine.ExBudgetingDefaults
, defaultCekMachineCosts
, defaultCekParameters
, defaultCostModelParams
, defaultUnliftingMode
, defaultBuiltinCostModel
, unitCekMachineCosts
, unitCekParameters
Expand Down Expand Up @@ -75,21 +74,16 @@ defaultCekCostModel = CostModel defaultCekMachineCosts defaultBuiltinCostModel
defaultCostModelParams :: Maybe CostModelParams
defaultCostModelParams = extractCostModelParams defaultCekCostModel

-- | Before vasil HF, the unlifting was immediate,
-- Since vasil HF, the unlifiting is deferred.
defaultUnliftingMode :: UnliftingMode
defaultUnliftingMode = UnliftingDeferred

defaultCekParameters :: MachineParameters CekMachineCosts CekValue DefaultUni DefaultFun
defaultCekParameters = mkMachineParameters def defaultUnliftingMode defaultCekCostModel
defaultCekParameters = mkMachineParameters def defaultCekCostModel

unitCekParameters :: MachineParameters CekMachineCosts CekValue DefaultUni DefaultFun
unitCekParameters =
mkMachineParameters def defaultUnliftingMode $
mkMachineParameters def $
CostModel unitCekMachineCosts unitCostBuiltinCostModel

defaultBuiltinsRuntime :: HasMeaningIn DefaultUni term => BuiltinsRuntime DefaultFun term
defaultBuiltinsRuntime = toBuiltinsRuntime def defaultUnliftingMode defaultBuiltinCostModel
defaultBuiltinsRuntime = toBuiltinsRuntime def defaultBuiltinCostModel


-- A cost model with unit costs, so we can count how often each builtin is called
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,8 @@ mkMachineParameters ::
, ToBuiltinMeaning uni fun
)
=> BuiltinVersion fun
-> UnliftingMode
-> CostModel machinecosts builtincosts
-> MachineParameters machinecosts val uni fun
mkMachineParameters ver unlMode (CostModel mchnCosts builtinCosts) =
MachineParameters mchnCosts (inline toBuiltinsRuntime ver unlMode builtinCosts)
mkMachineParameters ver (CostModel mchnCosts builtinCosts) =
MachineParameters mchnCosts (inline toBuiltinsRuntime ver builtinCosts)
{-# INLINE mkMachineParameters #-}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ inlining).

mkMachineParametersFor :: (MonadError CostModelApplyError m)
=> BuiltinVersion DefaultFun
-> UnliftingMode
-> CostModelParams
-> m DefaultMachineParameters
mkMachineParametersFor ver unlMode newCMP =
inline mkMachineParameters ver unlMode <$>
mkMachineParametersFor ver newCMP =
inline mkMachineParameters ver <$>
applyCostModelParams defaultCekCostModel newCMP
{-# INLINE mkMachineParametersFor #-}
-- {-# INLINE mkMachineParametersFor #-} was removed because [benchmarking
-- results](https://github.com/input-output-hk/plutus/pull/4879#issuecomment-1301052379) show that
-- the pragma isn't helping anymore.
Loading