oplang-0.5.0.0: src/Language/OpLang/Optimizer.hs
module Language.OpLang.Optimizer(optimize) where
import Data.IntMap.Strict(IntMap)
import Data.IntMap.Strict qualified as I
import Data.IntSet qualified as S
import Language.OpLang.Syntax
-- Models partial information about a memory cell, used during partial evaluation.
data Cell
= UnknownPlus Val -- Unknown starting value + a known constant.
| Known Bool Val -- Fully-known constant value. The 'Bool' tracks whether it should be committed unconditionally.
deriving stock Eq
isKnown :: Cell -> Bool
isKnown UnknownPlus{} = False
isKnown _ = True
getVal :: Cell -> Val
getVal (UnknownPlus val) = val
getVal (Known _ val) = val
-- 'isKnown' and 'getVal' could be made into lenses, which would make this definition redundant,
-- but adding a dependency on 'lens' just for this would be overkill.
mapVal :: (Val -> Val) -> Cell -> Cell
mapVal f (UnknownPlus val) = UnknownPlus $ f val
mapVal f (Known c val) = Known c $ f val
instance Semigroup Cell where
(<>) :: Cell -> Cell -> Cell
UnknownPlus a <> UnknownPlus b = UnknownPlus (a + b)
UnknownPlus a <> Known c b = Known c (a + b)
Known c a <> _ = Known c a
instance Monoid Cell where
mempty :: Cell
mempty = UnknownPlus 0
-- Models partial information about a segment of the memory tape.
-- Represented as a mapping from memory offsets to Cells.
type Tape = IntMap Cell
-- Commit a cell update instruction.
commitCell :: Offset -> Cell -> [Instr] -> [Instr]
commitCell offset cell acc =
case cell of
UnknownPlus 0 -> acc
UnknownPlus val -> Add offset val : acc
Known False 0 -> acc
Known _ val -> Set offset val : acc
-- Commit a Move instruction.
commitMove :: Offset -> [Instr] -> [Instr]
commitMove 0 acc = acc
commitMove offset acc = Move offset : acc
-- Commit cell update instructions for all the cells in the specified tape segment.
commitCells :: Tape -> [Instr] -> [Instr]
commitCells tape acc = I.foldrWithKey' commitCell acc tape
-- Commit a set of AddMul instructions, resulting from a for-like loop.
commitAddMuls :: Offset -> Val -> IntMap Val -> [Instr] -> [Instr]
commitAddMuls offset diff vals acc = I.foldrWithKey' (commitAddMul offset diff) acc vals
where
commitAddMul :: Offset -> Val -> Offset -> Val -> [Instr] -> [Instr]
commitAddMul _ _ _ 0 acc = acc
commitAddMul initialOffset diff offset val acc = AddMul offset initialOffset (val * -diff) : acc
-- Partially evaluate a program, producing an optimized sequence of instructions.
pEval :: Offset -> Tape -> [Instr] -> [Op] -> (Offset, Tape, [Instr])
pEval offset tape acc [] = (offset, tape, acc)
pEval offset tape acc (op : ops) =
case op of
Incr -> pEval offset (I.insertWith (<>) offset (UnknownPlus 1) tape) acc ops
Decr -> pEval offset (I.insertWith (<>) offset (UnknownPlus -1) tape) acc ops
Read' -> pEval offset (I.delete offset tape) (Read offset : acc) ops
Pop' -> pEval offset (I.delete offset tape) (Pop offset : acc) ops
MoveL -> pEval (offset - 1) tape acc ops
MoveR -> pEval (offset + 1) tape acc ops
Write' ->
case I.findWithDefault mempty offset tape of
Known _ val -> pEval offset tape (WriteKnown val : acc) ops
cell -> pEval offset (I.delete offset tape) (Write offset : commitCell offset cell acc) ops
Push' ->
case I.findWithDefault mempty offset tape of
Known _ val -> pEval offset tape (PushKnown val : acc) ops
cell -> pEval offset (I.delete offset tape) (Push offset : commitCell offset cell acc) ops
Call' c -> pEval offset tape (Call c : acc) ops
Loop' l ->
case I.findWithDefault mempty offset tape of
Known _ 0 -> pEval offset tape acc ops
cell ->
case pEval 0 I.empty [] l of
(0, I.mapKeys (offset +) -> tape', []) | Just (UnknownPlus diff) <- tape' I.!? offset, abs diff == 1, not $ any isKnown tape' ->
case cell of
Known _ val -> pEval offset (I.insert offset (Known True 0) $ I.unionWith (<>) (mapVal ((val * -diff) *) <$> tape') tape) acc ops
_ ->
let
modified = I.intersection tape tape'
addMuls = getVal <$> I.delete offset tape'
remaining = tape I.\\ modified
in
pEval offset (I.insert offset (Known True 0) remaining) (commitAddMuls offset diff addMuls $ commitCells modified acc) ops
(offset', tape', acc') ->
let l' = Loop $ reverse $ commitMove offset' $ commitCells tape' acc'
in pEval 0 (I.singleton 0 $ Known False 0) (l' : commitMove offset (commitCells tape acc)) ops
optimizeOps :: Word -> [Op] -> [Instr]
optimizeOps tapeSize ops = reverse acc
where
initialTape = I.fromSet (const $ Known False 0) $ S.fromRange (0, fromEnum tapeSize - 1)
(_, _, acc) = pEval 0 initialTape [] ops
optimize :: Word -> Program Op -> Program Instr
optimize tapeSize Program{..} =
Program
{ opDefs = optimizeOps tapeSize <$> opDefs
, topLevel = optimizeOps tapeSize topLevel
}