morley-1.19.2: src/Morley/Michelson/Optimizer/Internal/Rules.hs
-- SPDX-FileCopyrightText: 2023 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA
{-# OPTIONS_HADDOCK not-home #-}
-- NOTE this pragmas.
-- We disable some warnings for the sake of speed up.
-- Write code with care.
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
{-# OPTIONS_GHC -Wno-overlapping-patterns #-}
-- | Optimizer rewrite rules
module Morley.Michelson.Optimizer.Internal.Rules
( module Morley.Michelson.Optimizer.Internal.Rules
) where
import Prelude hiding (EQ, GT, LT)
import Data.Constraint (Dict(..), (\\))
import Data.Default (Default(def))
import Data.Singletons (sing)
import Data.Type.Equality ((:~:)(Refl))
import Morley.Michelson.Interpret.Pack (packValue')
import Morley.Michelson.Optimizer.Internal.Proofs
import Morley.Michelson.Optimizer.Internal.Ruleset
import Morley.Michelson.Optimizer.Utils
import Morley.Michelson.Typed.Aliases (Value)
import Morley.Michelson.Typed.Arith
import Morley.Michelson.Typed.Instr hiding ((:#))
import Morley.Michelson.Typed.Scope (ConstantScope, PackedValScope, checkScope)
import Morley.Michelson.Typed.Sing
import Morley.Michelson.Typed.T
import Morley.Michelson.Typed.Value
import Morley.Util.Peano
import Morley.Util.PeanoNatural
{-
Note [Writing optimizer rules]
------------------------------
We locally redefine (:#) to simplify handling instruction sequence tails.
Consider that a given instruction sequence can appear in the middle, and then @a
:# b :# tail@ will match, or at the end of the sequence, and then @a :# b@ will
match.
Local definition of (:#) makes it so we can always assume there's a tail.
However, we don't need it when matching on single instructions.
Thus, the rule of thumb is this: if you're matching on a single instruction,
everything is fine. If you're matching on a sequence, i.e. using (:#), then
always match on tail.
-}
-- | Default optimization rules.
defaultRules :: Ruleset
defaultRules = foldr ($) def $ uncurry (alterRulesAtPrio . const) <$>
-- NB: if adding more main stages, remember to check if
-- 'defaultRulesAndPushPack' needs updating.
[ (mainStageRules , OptimizationStageMain 0)
, (dipDrop2swapDropStageRules , OptimizationStageMainExtended 0)
, (fixupStageRules , OptimizationStageFixup 0)
, (glueAdjacentInstrsStageRules, OptimizationStageRollAdjacent 0)
]
where
dipDrop2swapDropStageRules = dipDrop2swapDrop : mainStageRules
fixupStageRules = [dipSwapDrop]
glueAdjacentInstrsStageRules =
[ adjacentDrops
, rollPairN
, rollUnpairN
, rollDips
]
-- | We do not enable 'pushPack' rule by default because it is potentially
-- dangerous. There are various code processing functions that may depend on
-- constants, e. g. string transformations.
defaultRulesAndPushPack :: Ruleset
defaultRulesAndPushPack = defaultRules
& insertRuleAtPrio (OptimizationStageMainExtended 0) pushPack
mainStageRules :: [Rule]
mainStageRules =
[ removeNesting
, removeExtStackType
, ifNopNop2Drop
, nopIsNeutralForSeq
, variousNops
, dupSwap2dup
, noDipNeeded
, branchShortCut
, compareWithZero
, internalNop
, simpleDups
, adjacentDips
, isSomeOnIf
, redundantIf
, emptyDip
, digDug
, specificPush
, pairUnpair
, pairMisc
, unpairMisc
, swapBeforeCommutative
, justDrops
, justDoubleDrops
, dig1AndDug1AreSwap
, notIf
, dropMeta
, dupDugDrop
-- strictly speaking, these rules below are de-optimisations, but they
-- expose opportunities for other optimisations, and they are undone in the
-- last stage.
, unrollPairN
, unrollUnpairN
, unrollDropN
, unrollDips
]
flattenSeqLHS :: Rule -> Rule
flattenSeqLHS toplevel = Rule $ \case
it@(Seq (Seq _ _) _) -> Just $ linearizeAndReapply toplevel it
_ -> Nothing
dropMeta :: Rule
dropMeta = Rule $ \case
Meta _ i -> Just i
WithLoc _ i -> Just i
_ -> Nothing
removeNesting :: Rule
removeNesting = Rule $ \case
Nested i -> Just i
_ -> Nothing
-- | STACKTYPE is currently a Nop and may safely be removed.
removeExtStackType :: Rule
removeExtStackType = Rule $ \case
Ext (STACKTYPE{}) -> Just Nop
_ -> Nothing
dipDrop2swapDrop :: Rule
dipDrop2swapDrop = Rule $ \case
DIP DROP -> Just $ SWAP :# DROP
_ -> Nothing
ifNopNop2Drop :: Rule
ifNopNop2Drop = Rule $ \case
IF Nop Nop -> Just DROP
_ -> Nothing
nopIsNeutralForSeq :: Rule
nopIsNeutralForSeq = Rule $ \case
-- NB: we're not using (:#) here because it'll always match when rhs is Nop
Seq Nop i -> Just i
Seq i Nop -> Just i
_ -> Nothing
variousNops :: Rule
variousNops = Rule $ \case
DUP :# DROP :# c -> Just c
DUPN _ :# DROP :# c -> Just c
SWAP :# SWAP :# c -> Just c
PUSH _ :# DROP :# c -> Just c
NONE :# DROP :# c -> Just c
UNIT :# DROP :# c -> Just c
NIL :# DROP :# c -> Just c
EMPTY_SET :# DROP :# c -> Just c
EMPTY_MAP :# DROP :# c -> Just c
EMPTY_BIG_MAP :# DROP :# c -> Just c
LAMBDA _ :# DROP :# c -> Just c
SELF _ :# DROP :# c -> Just c
NOW :# DROP :# c -> Just c
AMOUNT :# DROP :# c -> Just c
BALANCE :# DROP :# c -> Just c
TOTAL_VOTING_POWER :# DROP :# c -> Just c
SOURCE :# DROP :# c -> Just c
SENDER :# DROP :# c -> Just c
CHAIN_ID :# DROP :# c -> Just c
LEVEL :# DROP :# c -> Just c
SELF_ADDRESS :# DROP :# c -> Just c
READ_TICKET :# DROP :# c -> Just c
_ -> Nothing
dupSwap2dup :: Rule
dupSwap2dup = Rule $ \case
DUP :# SWAP :# c -> Just $ DUP :# c
_ -> Nothing
noDipNeeded :: Rule
noDipNeeded = Rule $ \case
-- If we put a constant value on stack and then do something under it,
-- we can do this "something" on original stack and then put that constant.
PUSH x :# DIP f :# c -> Just $ f :# PUSH x :# c
UNIT :# DIP f :# c -> Just $ f :# UNIT :# c
NOW :# DIP f :# c -> Just $ f :# NOW :# c
SENDER :# DIP f :# c -> Just $ f :# SENDER :# c
EMPTY_MAP :# DIP f :# c -> Just $ f :# EMPTY_MAP :# c
EMPTY_SET :# DIP f :# c -> Just $ f :# EMPTY_SET :# c
-- If we do something ignoring top of the stack and then immediately
-- drop top of the stack, we can drop that item in advance and
-- not use 'DIP' at all.
DIP f :# DROP :# c -> Just $ DROP :# f :# c
_ -> Nothing
unrollDips :: Rule
unrollDips = Rule go
where
go :: Instr inp out -> Maybe (Instr inp out)
go = \case
DIPN Zero c -> Just $ c
DIPN One c -> Just $ DIP c
DIPN (Succ n) c -> DIP <$> go (DIPN n c)
_ -> Nothing
rollDips :: Rule
rollDips = Rule $ \case
DIP (DIP c) -> Just $ DIPN Two c
DIP (DIPN n c) -> Just $ DIPN (Succ n) c
_ -> Nothing
branchShortCut :: Rule
branchShortCut = Rule $ \case
LEFT :# IF_LEFT f _ :# c -> Just $ f :# c
RIGHT :# IF_LEFT _ f :# c -> Just $ f :# c
CONS :# IF_CONS f _ :# c -> Just $ f :# c
NIL :# IF_CONS _ f :# c -> Just $ f :# c
NONE :# IF_NONE f _ :# c -> Just $ f :# c
SOME :# IF_NONE _ f :# c -> Just $ f :# c
PUSH vOr@(VOr eitherVal) :# IF_LEFT f g :# c -> case vOr of
(_ :: Value ('TOr l r)) -> case eitherVal of
Left val -> case checkScope @(ConstantScope l) of
Right Dict -> Just $ PUSH val :# f :# c
_ -> Nothing
Right val -> case checkScope @(ConstantScope r) of
Right Dict -> Just $ PUSH val :# g :# c
_ -> Nothing
PUSH (VList (x : xs)) :# IF_CONS f _ :# c -> Just $ PUSH (VList xs) :# PUSH x :# f :# c
PUSH (VList _) :# IF_CONS _ f :# c -> Just $ f :# c
PUSH (VOption Nothing) :# IF_NONE f _ :# c -> Just $ f :# c
PUSH (VOption (Just val)) :# IF_NONE _ f :# c -> Just $ PUSH val :# f :# c
PUSH (VBool True) :# IF f _ :# c -> Just $ f :# c
PUSH (VBool False) :# IF _ f :# c -> Just $ f :# c
_ -> Nothing
compareWithZero :: Rule
compareWithZero = Rule $ \case
PUSH (VInt 0) :# COMPARE :# EQ :# c -> Just $ EQ :# c
PUSH (VNat 0) :# COMPARE :# EQ :# c -> Just $ INT :# EQ :# c
_ -> Nothing
-- If an instruction takes another instruction as an argument and that
-- internal instruction is 'Nop', sometimes the whole instruction is
-- 'Nop'.
-- For now we do it only for 'DIP', but ideally we should do it for
-- 'MAP' as well (which is harder).
internalNop :: Rule
internalNop = Rule $ \case
DIP Nop -> Just Nop
_ -> Nothing
simpleDups :: Rule
simpleDups = Rule $ \case
-- DUP 1 is just DUP
DUPN One -> Just DUP
_ -> Nothing
adjacentDips :: Rule
adjacentDips = Rule $ \case
DIP f :# DIP g :# c -> Just $ DIP (f :# g) :# c
_ -> Nothing
redundantIf :: Rule
redundantIf = Rule \case
IF x y
| x == y
-> Just $ DROP :# x
_ -> Nothing
emptyDip :: Rule
emptyDip = Rule \case
DIP Nop -> Just Nop
DIPN _ Nop -> Just Nop
_ -> Nothing
digDug :: Rule
digDug = Rule \case
DIG x :# DUG y :# c | Just Refl <- eqPeanoNat x y -> Just c
_ -> Nothing
-- | Sequences of @DROP@s can be turned into single @DROP n@.
-- When @n@ is greater than 2 it saves size and gas.
-- When @n@ is 2 it saves gas only.
adjacentDrops :: Rule
adjacentDrops = Rule $ \case
DROP :# DROP :# c -> Just $ DROPN Two :# c
(DROPN n :: Instr inp out) :# (DROP :: Instr inp' out') :# c
-> Just $ DROPN (Succ n) :# c \\ dropNDropNProof @inp @out @out' n One
\\ commutativity (singPeanoNat n) (singPeanoNat One)
(DROP :: Instr inp out) :# (DROPN n :: Instr inp' out') :# c
-> Just $ DROPN (Succ n) :# c \\ dropNDropNProof @inp @out @out' One n
(DROPN n :: Instr inp out) :# (DROPN m :: Instr inp' out') :# c
-> Just $ DROPN (addPeanoNat n m) :# c \\ dropNDropNProof @inp @out @out' n m
_ -> Nothing
specificPush :: Rule
specificPush = Rule $ \case
push@PUSH{} :# c -> (:# c) <$> optimizePush push
_ -> Nothing
where
optimizePush :: Instr inp out -> Maybe (Instr inp out)
optimizePush = \case
PUSH v | _ :: Value v <- v -> case v of
VUnit -> Just UNIT
VMap m
| null m -> case sing @v of STMap{} -> Just EMPTY_MAP
VSet m
| null m -> case sing @v of STSet{} -> Just EMPTY_SET
_ -> Nothing
_ -> Nothing
isSomeOnIf :: Rule
isSomeOnIf = Rule $ \case
IF (PUSH (VOption Just{})) (PUSH (VOption Nothing))
:# IF_NONE (PUSH (VBool False)) (DROP :# PUSH (VBool True))
:# c
-> Just c
_ -> Nothing
pairUnpair :: Rule
pairUnpair = Rule $ \case
PAIR :# UNPAIR :# c -> Just c
UNPAIR :# PAIR :# c -> Just c
_ -> Nothing
pairMisc :: Rule
pairMisc = Rule $ \case
PAIR :# CDR :# c -> Just $ DROP :# c
PAIR :# CAR :# c -> Just $ (DIP DROP) :# c
_ -> Nothing
unpairMisc :: Rule
unpairMisc = Rule $ \case
DUP :# CAR :# DIP CDR :# c -> Just $ UNPAIR :# c
DUP :# CDR :# DIP CAR :# c -> Just $ UNPAIR :# SWAP :# c
UNPAIR :# DROP :# c -> Just $ CDR :# c
_ -> Nothing
unrollDropN :: Rule
unrollDropN = Rule go
where
go :: forall inp out. Instr inp out -> Maybe (Instr inp out)
go = \case
DROPN Zero -> Just Nop
DROPN (Succ n) -> do
dropn <- go $ DROPN n
Just $ DROP :# dropn
\\ unconsListProof @inp n
_ -> Nothing
unrollPairN :: Rule
unrollPairN = Rule go
where
go :: forall inp out. Instr inp out -> Maybe (Instr inp out)
go = \case
PAIRN Two -> Just PAIR \\ pairN2isPairProof @inp
PAIRN (Succ n@(Succ Succ{}) ) -> do
pairn <- go $ PAIRN n
Just $ DIP pairn :# PAIR
\\ unconsListProof @inp n
_ -> Nothing
unrollUnpairN :: Rule
unrollUnpairN = Rule go
where
go :: forall inp out. Instr inp out -> Maybe (Instr inp out)
go = \case
UNPAIRN Two -> Just UNPAIR \\ unpairN2isUnpairProof @inp @out
UNPAIRN (Succ n@(Succ Succ{})) -> do
unpairn <- go $ UNPAIRN n
Just $ UNPAIR :# DIP unpairn
\\ unpairNisUnpairDipUnpairNProof @inp @out n
_ -> Nothing
rollPairN :: Rule
rollPairN = Rule $ \case
DIP PAIR :# PAIR :# c -> Just $ PAIRN (Succ Two) :# c
(DIP (PAIRN n@(Succ Succ{})) :: Instr inp out) :# PAIR :# c -> Just $ PAIRN (Succ n) :# c
\\ dipPairNPairIsPairNProof @inp n
_ -> Nothing
rollUnpairN :: Rule
rollUnpairN = Rule $ \case
UNPAIR :# DIP UNPAIR :# c -> Just $ UNPAIRN (Succ Two) :# c
UNPAIR :# DIP (UNPAIRN n@(Succ Succ{})) :# c -> Just $ UNPAIRN (Succ n) :# c
_ -> Nothing
commuteArith ::
forall n m s out. Instr (n ': m ': s) out -> Maybe (Instr (m ': n ': s) out)
commuteArith = \case
ADD -> do Dict <- commutativityProof @Add @n @m; Just ADD
MUL -> do Dict <- commutativityProof @Mul @n @m; Just MUL
OR -> do Dict <- commutativityProof @Or @n @m; Just OR
AND -> do Dict <- commutativityProof @And @n @m; Just AND
XOR -> do Dict <- commutativityProof @Xor @n @m; Just XOR
_ -> Nothing
swapBeforeCommutative :: Rule
swapBeforeCommutative = Rule $ \case
SWAP :# i :# c -> (:# c) <$> commuteArith i
_ -> Nothing
pushPack :: Rule
pushPack = Rule $ \case
PUSH x :# PACK :# c -> Just $ pushPacked x :# c
_ -> Nothing
where
pushPacked :: PackedValScope t => Value t -> Instr s ('TBytes ': s)
pushPacked = PUSH . VBytes . packValue'
justDrops :: Rule
justDrops = Rule $ \case
CAR :# DROP :# c -> Just $ DROP :# c
CDR :# DROP :# c -> Just $ DROP :# c
SOME :# DROP :# c -> Just $ DROP :# c
LEFT :# DROP :# c -> Just $ DROP :# c
RIGHT :# DROP :# c -> Just $ DROP :# c
SIZE :# DROP :# c -> Just $ DROP :# c
GETN _ :# DROP :# c -> Just $ DROP :# c
CAST :# DROP :# c -> Just $ DROP :# c
RENAME :# DROP :# c -> Just $ DROP :# c
PACK :# DROP :# c -> Just $ DROP :# c
UNPACK :# DROP :# c -> Just $ DROP :# c
CONCAT' :# DROP :# c -> Just $ DROP :# c
ISNAT :# DROP :# c -> Just $ DROP :# c
ABS :# DROP :# c -> Just $ DROP :# c
NEG :# DROP :# c -> Just $ DROP :# c
NOT :# DROP :# c -> Just $ DROP :# c
EQ :# DROP :# c -> Just $ DROP :# c
NEQ :# DROP :# c -> Just $ DROP :# c
LT :# DROP :# c -> Just $ DROP :# c
GT :# DROP :# c -> Just $ DROP :# c
LE :# DROP :# c -> Just $ DROP :# c
GE :# DROP :# c -> Just $ DROP :# c
INT :# DROP :# c -> Just $ DROP :# c
CONTRACT _ :# DROP :# c -> Just $ DROP :# c
SET_DELEGATE :# DROP :# c -> Just $ DROP :# c
IMPLICIT_ACCOUNT :# DROP :# c -> Just $ DROP :# c
VOTING_POWER :# DROP :# c -> Just $ DROP :# c
SHA256 :# DROP :# c -> Just $ DROP :# c
SHA512 :# DROP :# c -> Just $ DROP :# c
BLAKE2B :# DROP :# c -> Just $ DROP :# c
SHA3 :# DROP :# c -> Just $ DROP :# c
KECCAK :# DROP :# c -> Just $ DROP :# c
HASH_KEY :# DROP :# c -> Just $ DROP :# c
PAIRING_CHECK :# DROP :# c -> Just $ DROP :# c
ADDRESS :# DROP :# c -> Just $ DROP :# c
JOIN_TICKETS :# DROP :# c -> Just $ DROP :# c
_ -> Nothing
justDoubleDrops :: Rule
justDoubleDrops = Rule $ \case
PAIR :# DROP :# c -> Just $ DROP :# DROP :# c
MEM :# DROP :# c -> Just $ DROP :# DROP :# c
GET :# DROP :# c -> Just $ DROP :# DROP :# c
APPLY :# DROP :# c -> Just $ DROP :# DROP :# c
CONCAT :# DROP :# c -> Just $ DROP :# DROP :# c
ADD :# DROP :# c -> Just $ DROP :# DROP :# c
SUB :# DROP :# c -> Just $ DROP :# DROP :# c
SUB_MUTEZ :# DROP :# c -> Just $ DROP :# DROP :# c
MUL :# DROP :# c -> Just $ DROP :# DROP :# c
EDIV :# DROP :# c -> Just $ DROP :# DROP :# c
OR :# DROP :# c -> Just $ DROP :# DROP :# c
AND :# DROP :# c -> Just $ DROP :# DROP :# c
XOR :# DROP :# c -> Just $ DROP :# DROP :# c
COMPARE :# DROP :# c -> Just $ DROP :# DROP :# c
TICKET :# DROP :# c -> Just $ DROP :# DROP :# c
SPLIT_TICKET :# DROP :# c -> Just $ DROP :# DROP :# c
SWAP :# DROP :# DROP :# c -> Just $ DROP :# DROP :# c
_ -> Nothing
dig1AndDug1AreSwap :: Rule
dig1AndDug1AreSwap = Rule \case
DUG One -> Just SWAP
DIG One -> Just SWAP
_ -> Nothing
notIf :: Rule
notIf = Rule \case
(NOT :: Instr inp out) :# IF a b :# c | STBool <- sing @(Head inp) -> Just $ IF b a :# c
_ -> Nothing
-- | NB: This rule MUST be applied AFTER all main stages, but BEFORE nested dips
-- are rolled up
dipSwapDrop :: Rule
dipSwapDrop = Rule \case
DIP (SWAP :# DROP) -> Just $ DIPN Two DROP
_ -> Nothing
dupDugDrop :: Rule
dupDugDrop = Rule \case
DUP :# DUG (Succ m) :# DROP :# xs -> Just $ DUG m :# xs
_ -> Nothing