packages feed

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

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

{-# OPTIONS_HADDOCK not-home #-}

-- | Optimizer rule and ruleset definitions.
module Morley.Michelson.Optimizer.Internal.Ruleset
  ( module Morley.Michelson.Optimizer.Internal.Ruleset
  ) where

import Prelude

import Data.Default (Default(def))
import Data.Map qualified as Map
import Fmt (Buildable(..), (+|), (|+))

import Morley.Michelson.Typed.Instr

-- | Type of a single rewrite rule, wrapped in `newtype`. It takes an
-- instruction and tries to optimize its head (first few instructions). If
-- optimization succeeds, it returns `Just` the optimized instruction, otherwise
-- it returns `Nothing`.
newtype Rule = Rule {unRule :: forall inp out. Instr inp out -> Maybe (Instr inp out)}

-- | Optimization stages. Stages are run in first to last order, each stage has
-- an 'Int' argument, which allows splitting each stage into sub-stages, which
-- will run lowest index to highest. All default rules use sub-stage @0@.
data OptimizationStage
  = OptimizationStagePrepare Int
  | OptimizationStageMain Int
    -- ^ Main optimisation stage, except rules that would interfere with other
    -- rules.
  | OptimizationStageMainExtended Int
    -- ^ All main stage rules.
  | OptimizationStageFixup Int
    -- ^ Post main stage fixups.
  | OptimizationStageRollAdjacent Int
    -- ^ Main stage rules unroll @DROP n@, @PAIR n@, etc into their primitive
    -- counterparts to simplify some optimisations. This stages coalesces them
    -- back.
  deriving stock (Eq, Ord)

instance Buildable OptimizationStage where
  build = \case
    OptimizationStagePrepare n -> "prepare " +| n |+ ""
    OptimizationStageMain n -> "main " +| n |+ ""
    OptimizationStageMainExtended n -> "main extended " +| n |+ ""
    OptimizationStageFixup n -> "fixup " +| n |+ ""
    OptimizationStageRollAdjacent n -> "roll adjacent " +| n |+ ""

-- | A set of optimization stages. Rules at the same sub-stage are applied in
-- arbitrary order. See 'OptimizationStage' for explanation of sub-stages.
--
-- 'Default' ruleset is empty.
newtype Ruleset = Ruleset { unRuleset :: Map OptimizationStage (NonEmpty Rule) }
  deriving newtype Default

instance Semigroup Ruleset where
  Ruleset l <> Ruleset r = Ruleset $ Map.unionWith (<>) l r

instance Monoid Ruleset where
  mempty = def

-- | Get rules for a given priority as a list.
rulesAtPrio :: OptimizationStage -> Ruleset -> [Rule]
rulesAtPrio prio = maybe [] toList . Map.lookup prio . unRuleset

-- | Insert a single rule at a given priority without touching other rules.
insertRuleAtPrio :: OptimizationStage -> Rule -> Ruleset -> Ruleset
insertRuleAtPrio = flip $ alterRulesAtPrio . (:)

-- | Remove the stage with the given priority.
clearRulesAtPrio :: OptimizationStage -> Ruleset -> Ruleset
clearRulesAtPrio = alterRulesAtPrio (const [])

-- | Alter all stage rules for a given priority.
alterRulesAtPrio :: ([Rule] -> [Rule]) -> OptimizationStage -> Ruleset -> Ruleset
alterRulesAtPrio f prio = Ruleset . Map.alter (nonEmpty . f . maybe [] toList) prio . unRuleset