-- | Defines the type of default machine parameters and a function for creating a value of the type.
-- We keep them separate, because the function unfolds into multiple thousands of lines of Core that
-- we need to be able to visually inspect, hence we dedicate a separate file to it.
module PlutusCore.Evaluation.Machine.MachineParameters.Default where

import PlutusPrelude

import PlutusCore.Builtin
import PlutusCore.Default
import PlutusCore.Evaluation.Machine.CostModelInterface
import PlutusCore.Evaluation.Machine.ExBudgetingDefaults
import PlutusCore.Evaluation.Machine.MachineParameters
import UntypedPlutusCore.Evaluation.Machine.Cek

import Control.DeepSeq (force)
import Control.Monad.Except
import GHC.Exts (inline)

-- | 'MachineParameters' instantiated at CEK-machine-specific types and default builtins.
-- Encompasses everything we need for evaluating a UPLC program with default builtins using the CEK
-- machine.
type DefaultMachineParameters =
    MachineParameters CekMachineCosts DefaultFun (CekValue DefaultUni DefaultFun ())

{- 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/IntersectMBO/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 meanings 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).
-}

-- | Produce a 'DefaultMachineParameters' for each of the given semantics variants.
-- The 'CostModelParams' argument is used to update the costing parameters returned by
-- 'cekCostModelForVariant' for each of the semantics variants.
--
-- Whenever you need to evaluate UPLC in a performance-sensitive manner (e.g. in the production,
-- for benchmarking, for cost calibration etc), you MUST use this definition for creating a
-- 'DefaultMachineParameters' and not any other. Changing this definition in absolutely any way,
-- however trivial, requires running the benchmarks and making sure that the resulting GHC Core is
-- still sensible. E.g. you switch the order of arguments -- you run the benchmarks and check the
-- Core; you move this definition as-is to a different file -- you run the benchmarks and check the
-- Core; you change how it's exported (implicitly as a part of a whole-module export or explicitly
-- as a single definition) -- you get the idea.
--
-- This function is very expensive, so its result needs to be cached if it's going to be used
-- multiple times.
mkMachineParametersFor
    :: MonadError CostModelApplyError m
    => [BuiltinSemanticsVariant DefaultFun]
    -> CostModelParams
    -> m [(BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters)]
mkMachineParametersFor :: forall (m :: * -> *).
MonadError CostModelApplyError m =>
[BuiltinSemanticsVariant DefaultFun]
-> CostModelParams
-> m [(BuiltinSemanticsVariant DefaultFun,
       DefaultMachineParameters)]
mkMachineParametersFor [BuiltinSemanticsVariant DefaultFun]
semVars CostModelParams
newCMP = do
    [(BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters)]
res <- [BuiltinSemanticsVariant DefaultFun]
-> (BuiltinSemanticsVariant DefaultFun
    -> m (BuiltinSemanticsVariant DefaultFun,
          DefaultMachineParameters))
-> m [(BuiltinSemanticsVariant DefaultFun,
       DefaultMachineParameters)]
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
t a -> (a -> f b) -> f (t b)
for [BuiltinSemanticsVariant DefaultFun]
semVars ((BuiltinSemanticsVariant DefaultFun
  -> m (BuiltinSemanticsVariant DefaultFun,
        DefaultMachineParameters))
 -> m [(BuiltinSemanticsVariant DefaultFun,
        DefaultMachineParameters)])
-> (BuiltinSemanticsVariant DefaultFun
    -> m (BuiltinSemanticsVariant DefaultFun,
          DefaultMachineParameters))
-> m [(BuiltinSemanticsVariant DefaultFun,
       DefaultMachineParameters)]
forall a b. (a -> b) -> a -> b
$ \BuiltinSemanticsVariant DefaultFun
semVar ->
        -- See Note [Inlining meanings of builtins].
        (,) BuiltinSemanticsVariant DefaultFun
semVar (DefaultMachineParameters
 -> (BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters))
-> (CostModel CekMachineCosts BuiltinCostModel
    -> DefaultMachineParameters)
-> CostModel CekMachineCosts BuiltinCostModel
-> (BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (BuiltinSemanticsVariant DefaultFun
 -> CostModel CekMachineCosts BuiltinCostModel
 -> DefaultMachineParameters)
-> BuiltinSemanticsVariant DefaultFun
-> CostModel CekMachineCosts BuiltinCostModel
-> DefaultMachineParameters
forall a. a -> a
inline BuiltinSemanticsVariant DefaultFun
-> CostModel CekMachineCosts BuiltinCostModel
-> DefaultMachineParameters
forall (uni :: * -> *) fun builtincosts val machinecosts.
(CostingPart uni fun ~ builtincosts, HasMeaningIn uni val,
 ToBuiltinMeaning uni fun) =>
BuiltinSemanticsVariant fun
-> CostModel machinecosts builtincosts
-> MachineParameters machinecosts fun val
mkMachineParameters BuiltinSemanticsVariant DefaultFun
semVar (CostModel CekMachineCosts BuiltinCostModel
 -> (BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters))
-> m (CostModel CekMachineCosts BuiltinCostModel)
-> m (BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
            CostModel CekMachineCosts BuiltinCostModel
-> CostModelParams
-> m (CostModel CekMachineCosts BuiltinCostModel)
forall evaluatorcosts builtincosts (m :: * -> *).
(FromJSON evaluatorcosts, FromJSON builtincosts,
 ToJSON evaluatorcosts, ToJSON builtincosts,
 MonadError CostModelApplyError m) =>
CostModel evaluatorcosts builtincosts
-> CostModelParams -> m (CostModel evaluatorcosts builtincosts)
applyCostModelParams (BuiltinSemanticsVariant DefaultFun
-> CostModel CekMachineCosts BuiltinCostModel
cekCostModelForVariant BuiltinSemanticsVariant DefaultFun
semVar) CostModelParams
newCMP
    -- Force all thunks to pay the cost of creating machine parameters upfront. Doing it here saves
    -- us from doing that in every single benchmark runner.
    [(BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters)]
-> m [(BuiltinSemanticsVariant DefaultFun,
       DefaultMachineParameters)]
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([(BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters)]
 -> m [(BuiltinSemanticsVariant DefaultFun,
        DefaultMachineParameters)])
-> [(BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters)]
-> m [(BuiltinSemanticsVariant DefaultFun,
       DefaultMachineParameters)]
forall a b. (a -> b) -> a -> b
$! [(BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters)]
-> [(BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters)]
forall a. NFData a => a -> a
force [(BuiltinSemanticsVariant DefaultFun, DefaultMachineParameters)]
res
-- Not marking this function with @INLINE@, since at this point everything we wanted to be inlined
-- is inlined and there's zero reason to duplicate thousands and thousands of lines of Core down
-- the line.