diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs index d96e5c6562d..8adae67b535 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs @@ -20,6 +20,7 @@ import Control.Lens (ix, (^?)) import Control.Monad.Except import Data.Array import Data.Kind qualified as GHC (Type) +import GHC.Exts (inline) import PlutusCore.Builtin.KnownType -- | Same as 'TypeScheme' except this one doesn't contain any evaluation-irrelevant types stuff. @@ -90,13 +91,15 @@ toBuiltinRuntime :: cost -> BuiltinMeaning val cost -> BuiltinRuntime val toBuiltinRuntime cost (BuiltinMeaning sch f exF) = BuiltinRuntime (typeSchemeToRuntimeScheme sch) f (exF cost) +-- See Note [Inlining meanings of builtins]. -- | Calculate runtime info for all built-in functions given denotations of builtins -- and a cost model. toBuiltinsRuntime :: (cost ~ CostingPart uni fun, HasConstantIn uni val, ToBuiltinMeaning uni fun) => cost -> BuiltinsRuntime fun val toBuiltinsRuntime cost = - BuiltinsRuntime . tabulateArray $ toBuiltinRuntime cost . toBuiltinMeaning + BuiltinsRuntime . tabulateArray $ toBuiltinRuntime cost . inline toBuiltinMeaning +{-# INLINE toBuiltinsRuntime #-} -- | Look up the runtime info of a built-in function during evaluation. lookupBuiltin diff --git a/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs b/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs index 912d4d83699..d33f579f045 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs @@ -994,6 +994,8 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where makeBuiltinMeaning (\() -> [] @(Data,Data)) (runCostingFunOneArgument . paramMkNilPairData) + -- See Note [Inlining meanings of builtins]. + {-# INLINE toBuiltinMeaning #-} -- It's set deliberately to give us "extra room" in the binary format to add things without running -- out of space for tags (expanding the space would change the binary format for people who're diff --git a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/MachineParameters.hs b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/MachineParameters.hs index dc68c5fc5ff..2cd619c076b 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/MachineParameters.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/MachineParameters.hs @@ -12,6 +12,7 @@ import PlutusCore.Builtin import PlutusCore.Evaluation.Machine.ExBudget () import Control.DeepSeq +import GHC.Exts (inline) import GHC.Generics import GHC.Types (Type) @@ -42,6 +43,7 @@ data MachineParameters machinecosts term (uni :: Type -> Type) (fun :: Type) = deriving stock Generic deriving anyclass NFData +-- See Note [Inlining meanings of builtins]. {-| This just uses 'toBuiltinsRuntime' function to convert a BuiltinCostModel to a BuiltinsRuntime. -} mkMachineParameters :: ( -- In Cek.Internal we have `type instance UniOf (CekValue uni fun) = uni`, but we don't know that here. @@ -52,4 +54,5 @@ mkMachineParameters :: => CostModel machinecosts builtincosts -> MachineParameters machinecosts val uni fun mkMachineParameters (CostModel mchnCosts builtinCosts) = - MachineParameters mchnCosts (toBuiltinsRuntime builtinCosts) + MachineParameters mchnCosts (inline toBuiltinsRuntime builtinCosts) +{-# INLINE mkMachineParameters #-} diff --git a/plutus-ledger-api/src/Plutus/V1/Ledger/EvaluationContext.hs b/plutus-ledger-api/src/Plutus/V1/Ledger/EvaluationContext.hs index 341b3f8caa3..5cdf16c2d9f 100644 --- a/plutus-ledger-api/src/Plutus/V1/Ledger/EvaluationContext.hs +++ b/plutus-ledger-api/src/Plutus/V1/Ledger/EvaluationContext.hs @@ -19,6 +19,7 @@ import Data.Map as Map import Data.Maybe import Data.Set as Set import Data.Text qualified as Text +import GHC.Exts (inline) -- | An opaque type that contains all the static parameters that the evaluator needs to evaluate a script. -- This is so that they can be computed once and cached, rather than recomputed on every evaluation. @@ -26,13 +27,36 @@ newtype EvaluationContext = EvaluationContext { toMachineParameters :: Plutus.MachineParameters CekMachineCosts CekValue DefaultUni DefaultFun } deriving newtype NFData +{- Note [Inlining meanings of builtins] +It's vitally important to inline the 'toBuiltinMeaning' method of a set of built-in functions as +that allows GHC to look under lambdas and completely optimize multiple abstractions away. + +There are two ways of doing that: by relying on 'INLINE' pragmas all the way up from the +'ToBuiltinMeaning' instance for the default set of builtins or by ensuring that 'toBuiltinsRuntime' +is compiled efficient by turning it into a one-method class (see +https://github.com/input-output-hk/plutus/pull/4419 for how that looks like). We chose the former, +because it's simpler. Although it's also less reliable: machine parameters are computed in +multiple places and we need to make sure that benchmarking, cost model calculations and the actual +production path have builtins compiled in the same way, 'cause otherwise performance analysis and +cost predictions can be wrong by a big margin without us knowing. Because of this danger in addition +to putting @INLINE@ pragmas on every relevant definition, we also stick a call to 'inline' at the +call site. We also do not attempt to only compile things efficiently where we need that and instead +inline the meanins of builtins everywhere. Just to be sure. + +Note that a combination of @INLINABLE@ + 'inline' does not result in proper inlining for whatever +reason. It has to be @INLINE@ (and we add 'inline' on top of that for some additional reliability +as we did have cases where sticking 'inline' on something that already had @INLINE@ would fix +inlining). +-} + +-- See Note [Inlining meanings of builtins]. -- | Build the 'EvaluationContext'. -- -- The input is a `Map` of strings to cost integer values (aka `Plutus.CostModelParams`, `Alonzo.CostModel`) mkEvaluationContext :: Plutus.CostModelParams -> Maybe EvaluationContext mkEvaluationContext newCMP = - EvaluationContext . Plutus.mkMachineParameters <$> Plutus.applyCostModelParams Plutus.defaultCekCostModel newCMP + EvaluationContext . inline Plutus.mkMachineParameters <$> Plutus.applyCostModelParams Plutus.defaultCekCostModel newCMP -- | Comparably expensive to `mkEvaluationContext`, so it should only be used sparingly. isCostModelParamsWellFormed :: Plutus.CostModelParams -> Bool