packages feed

morley-1.19.2: src/Morley/Michelson/Optimizer.hs

-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

-- | Optimizer for typed instructions.
--
-- It's quite experimental and incomplete.
module Morley.Michelson.Optimizer
  ( optimize
  , optimizeWithConf
  , optimizeVerboseWithConf
  , defaultOptimizerConf
  , defaultRules
  , defaultRulesAndPushPack
  , Rule (..)
  , OptimizerConf (..)
  , ocGotoValuesL
    -- * Ruleset manipulation
  , OptimizationStage (..)
  , Ruleset
  , rulesAtPrio
  , insertRuleAtPrio
  , clearRulesAtPrio
  , alterRulesAtPrio
  , OptimizerStageStats(..)
  ) where

import Prelude hiding (EQ, GT, LT)

import Control.Lens (makeLensesFor)
import Data.Default (Default(def))
import Fmt (Buildable(..), (+|), (|+))

import Morley.Michelson.Optimizer.Internal.Rules
import Morley.Michelson.Optimizer.Internal.Ruleset
import Morley.Michelson.Optimizer.Utils
import Morley.Michelson.Typed.ClassifiedInstr
import Morley.Michelson.Typed.Instr hiding ((:#))
import Morley.Michelson.Typed.Util (DfsSettings(..), dfsFoldInstr, dfsTraverseInstr)

data OptimizerConf = OptimizerConf
  { ocGotoValues :: Bool
  , ocRuleset    :: Ruleset
  , ocMaxIterations :: Word
  }

-- | Default config - all commonly useful rules will be applied to all the code.
defaultOptimizerConf :: OptimizerConf
defaultOptimizerConf = OptimizerConf
  { ocGotoValues = True
  , ocRuleset    = defaultRules
  , ocMaxIterations = 100
  }

instance Default OptimizerConf where
  def = defaultOptimizerConf

-- | Optimize a typed instruction by replacing some sequences of
-- instructions with smaller equivalent sequences.
-- Applies default set of rewrite rules.
optimize :: Instr inp out -> Instr inp out
optimize = optimizeWithConf def

-- | Optimize a typed instruction using a custom set of rules.
-- The set is divided into several stages, as applying
-- some rules can prevent others to be performed.
--
-- If any stage resulted in optimizations, we apply it again until we reach
-- fixpoint, but no more than 'ocMaxIterations' times.
optimizeWithConf :: OptimizerConf -> Instr inp out -> Instr inp out
optimizeWithConf = snd ... optimizeVerboseWithConf

data OptimizerStageStats = OptimizerStageStats
  { ossStage :: OptimizationStage
  , ossNumIterations :: Word
  , ossNumInstrs :: Word
  }

instance Buildable OptimizerStageStats where
  build OptimizerStageStats{..} =
    "Stage " +| ossStage
      |+ " finished after " +| ossNumIterations
      |+ " iterations. Instruction count: " +| ossNumInstrs |+ ""

-- | Returns some optimizer statistics in addition to optimized instruction.
-- Mostly useful for testing and debugging.
optimizeVerboseWithConf
  :: OptimizerConf
  -> Instr inp out
  -> ([OptimizerStageStats], Instr inp out)
optimizeVerboseWithConf OptimizerConf{..} instr =
  foldlM (performOneStage 1) instrRHS $ toPairs $ unRuleset ocRuleset
  where
    performOneStage n i stage@(stageName, stageRules)
      | not changed || n >= ocMaxIterations = ([OptimizerStageStats stageName n stats], res)
      | otherwise = performOneStage (succ n) res stage
      where
        stats = getSum $ instrCount res
        instrCount = dfsFoldInstr def { dsGoToValues = ocGotoValues } $ withClassifiedInstr \case
          SFromMichelson -> const 1
          _ -> const 0
        stageRule = fixpoint $ foldl orSimpleRule flattenSeqLHS stageRules
        (getAny -> changed, res) =
          dfsTraverseInstr def{ dsGoToValues = ocGotoValues, dsInstrStep = applyOnce stageRule } i
    instrRHS = snd $ applyOnce (fixpoint flattenSeqLHS) instr

makeLensesFor [("ocGotoValues", "ocGotoValuesL")] ''OptimizerConf