Skip to content

Commit 3a5d186

Browse files
committed
Split ANF optimizations into their own file. Improve affine handlers.
Inlining has been rewritten and extended. Some 'tail only' inlinings are now recognized, which help to rewrite nicer affine handlers into the originally recognized form. Some 'peephole' optimizations have been added, which further rewrite handlers into nicer forms, though they may apply in other scenarios (improving performance slightly). Now, two sorts of affine handlers that were previously unrecognized are. One is handlers that require data values in the handler block: handle k () with h These were previously blocked by havin an opaque function in the runtime version of the 'handle' call. Next, handlers based around 'thunks' are now supported, e.g. f th = handle !th with cases { eff <vs> -> k } -> ... f do k <us> These are optimized into the more direct form, so should perform almost identically.
1 parent d1a2ba9 commit 3a5d186

File tree

5 files changed

+663
-314
lines changed

5 files changed

+663
-314
lines changed

unison-runtime/src/Unison/Runtime/ANF.hs

Lines changed: 0 additions & 312 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,6 @@ module Unison.Runtime.ANF
8080
valueTermLinks,
8181
valueLinks,
8282
groupTermLinks,
83-
buildInlineMap,
84-
inline,
85-
optimizeHandler,
8683
replaceConstructors,
8784
replaceFunctions,
8885
foldGroup,
@@ -102,13 +99,11 @@ import Control.Exception (throw)
10299
import Control.Lens (snoc, unsnoc)
103100
import Control.Monad.Reader (ReaderT (..), ask, local)
104101
import Control.Monad.State (MonadState (..), State, gets, modify, runState)
105-
import Control.Monad.Writer (WriterT (..), tell)
106102
import Data.Bifoldable (Bifoldable (..))
107103
import Data.Bitraversable (Bitraversable (..))
108104
import Data.Functor.Compose (Compose (..))
109105
import Data.List hiding (and, or)
110106
import Data.Map qualified as Map
111-
import Data.Monoid (Any (..))
112107
import Data.Set qualified as Set
113108
import Data.Text qualified as Data.Text
114109
import GHC.Stack (CallStack, callStack)
@@ -139,7 +134,6 @@ import Unison.Util.Text qualified as Util.Text
139134
import Unison.Var (Var, typed)
140135
import Unison.Var qualified as Var
141136
import Prelude hiding (abs, and, or, seq)
142-
import Prelude qualified
143137

144138
-- For internal errors
145139
data CompileExn = CE CallStack (Pretty.Pretty Pretty.ColorText)
@@ -664,46 +658,6 @@ saturate dat = ABT.visitPure $ \case
664658
fvs = foldMap freeVars args
665659
args' = saturate dat <$> args
666660

667-
-- Performs inlining on a supergroup using the inlining information
668-
-- in the map. The map can be created from typical SuperGroup data
669-
-- using the `buildInlineMap` function.
670-
inline ::
671-
(Var v) =>
672-
Map Reference (Int, ANormal v) ->
673-
SuperGroup v ->
674-
SuperGroup v
675-
inline inls (Rec bs entry) = Rec (fmap go0 <$> bs) (go0 entry)
676-
where
677-
go0 (Lambda ccs body) = Lambda ccs $ go (30 :: Int) body
678-
-- Note: number argument bails out in recursive inlining cases
679-
go n | n <= 0 = id
680-
go n = ABTN.visitPure \case
681-
TApp (FComb r) args
682-
| Just (arity, expr) <- Map.lookup r inls ->
683-
go (n - 1) <$> tweak expr args arity
684-
TName nv (Left r) args body
685-
| Just (arity, expr) <- Map.lookup r inls ->
686-
tweak expr args arity >>= \case
687-
TCom r args ->
688-
Just . go (n-1) $ TName nv (Left r) args body
689-
TApv v args ->
690-
Just . go (n-1) $ TName nv (Right v) args body
691-
_ -> Nothing
692-
_ -> Nothing
693-
694-
tweak (ABTN.TAbss vs body) args arity
695-
-- exactly saturated
696-
| length args == arity,
697-
rn <- Map.fromList (zip vs args) =
698-
Just $ ABTN.renames rn body
699-
-- oversaturated, only makes sense if body is a call
700-
| length args > arity,
701-
(pre, post) <- splitAt arity args,
702-
rn <- Map.fromList (zip vs pre),
703-
TApp f pre <- ABTN.renames rn body =
704-
Just $ TApp f (pre ++ post)
705-
| otherwise = Nothing
706-
707661
replaceConstructors ::
708662
(Var v) =>
709663
Map Reference (Map CTag ForeignFunc) ->
@@ -1593,269 +1547,6 @@ arity (Lambda ccs _) = length ccs
15931547
arities :: SuperGroup v -> [Int]
15941548
arities (Rec bs e) = arity e : fmap (arity . snd) bs
15951549

1596-
-- Checks the body of a SuperGroup makes it eligible for inlining.
1597-
-- See below for the discussion.
1598-
isInlinable :: (Var v) => Reference -> ANormal v -> Bool
1599-
isInlinable r (TApp (FComb s) _) = r /= s
1600-
isInlinable _ TApp {} = True
1601-
isInlinable _ TBLit {} = True
1602-
isInlinable _ TVar {} = True
1603-
isInlinable _ _ = False
1604-
1605-
-- Checks a SuperGroup makes it eligible to be inlined.
1606-
-- Unfortunately we need to be quite conservative about this.
1607-
--
1608-
-- The heuristic implemented below is as follows:
1609-
--
1610-
-- 1. There are no local bindings, so only the 'entry point'
1611-
-- matters.
1612-
-- 2. The entry point body is just a single expression, that is,
1613-
-- an application, variable or literal.
1614-
--
1615-
-- The first condition ensures that there isn't any need to jump
1616-
-- into a non-entrypoint from outside a group. These should be rare
1617-
-- anyway, because the local bindings are no longer used for
1618-
-- (unison-level) local function definitions (those are lifted
1619-
-- out). The second condition ensures that inlining the body should
1620-
-- have no effect on the runtime stack of of the function we're
1621-
-- inlining into, because the combinator is just a wrapper around
1622-
-- the simple expression.
1623-
--
1624-
-- Fortunately, it should be possible to make _most_ builtins have
1625-
-- this form, so that their instructions can be inlined directly
1626-
-- into the call sites when saturated.
1627-
--
1628-
-- The result of this function is the information necessary to
1629-
-- inline the combinator—an arity and the body expression with
1630-
-- bound variables. This should allow checking if the call is
1631-
-- saturated and make it possible to locally substitute for an
1632-
-- inlined expression.
1633-
--
1634-
-- The `Reference` argument allows us to check if the body is a
1635-
-- direct recursive call to the same function, which would result
1636-
-- in infinite inlining. This isn't the only such scenario, but
1637-
-- it's one we can opportunistically rule out.
1638-
inlineInfo :: (Var v) => Reference -> SuperGroup v -> Maybe (Int, ANormal v)
1639-
inlineInfo r (Rec [] (Lambda ccs body@(ABTN.TAbss _ e)))
1640-
| isInlinable r e = Just (length ccs, body)
1641-
inlineInfo _ _ = Nothing
1642-
1643-
-- Builds inlining information from a collection of SuperGroups.
1644-
-- They are all tested for inlinability, and the result map
1645-
-- contains only the information for groups that are able to be
1646-
-- inlined.
1647-
buildInlineMap ::
1648-
(Var v) =>
1649-
Map Reference (SuperGroup v) ->
1650-
Map Reference (Int, ANormal v)
1651-
buildInlineMap =
1652-
runIdentity
1653-
. Map.traverseMaybeWithKey (\r g -> Identity $ inlineInfo r g)
1654-
1655-
-- If the provided SuperGroup is recognized as a handler, applies
1656-
-- optimizations to improve it, like adding better code for affine
1657-
-- handlers.
1658-
optimizeHandler :: Var v => Reference -> SuperGroup v -> SuperGroup v
1659-
optimizeHandler self group =
1660-
fromMaybe group $ augmentHandler self group
1661-
1662-
-- moves the last value of a list to the start, for easier matching
1663-
shiftArgs :: [v] -> [v]
1664-
shiftArgs vs = case reverse vs of
1665-
v : vs -> v : reverse vs
1666-
[] -> []
1667-
1668-
-- Checks if the group represents a handler, and if so, tries to add
1669-
-- optimized affine code.
1670-
augmentHandler ::
1671-
(Var v) => Reference -> SuperGroup v -> Maybe (SuperGroup v)
1672-
augmentHandler self group
1673-
| Rec [(mv0, matcher)] entry <- group,
1674-
Lambda ccs (ABTN.TAbss args body) <- entry,
1675-
thunk : vs <- shiftArgs args,
1676-
Just body <- augmentHandlerEntry vs thunk mv0 ah body,
1677-
Just amatcher <- translateHandlerMatch self ah matcher =
1678-
Just .
1679-
Rec [(mv0, matcher), (ah, amatcher)] .
1680-
Lambda ccs $
1681-
ABTN.TAbss args body
1682-
1683-
| otherwise = Nothing
1684-
where
1685-
ah = freshAff 0
1686-
1687-
-- Recognizes the matching portion of a handler, and produces an
1688-
-- optimized affine version if possible.
1689-
translateHandlerMatch
1690-
:: Var v => Reference -> v -> SuperNormal v -> Maybe (SuperNormal v)
1691-
translateHandlerMatch self ah (Lambda ccs (ABTN.TAbss args body))
1692-
| v : vs <- shiftArgs args,
1693-
TMatch u branches <- body, u == v,
1694-
MatchRequest cs df <- branches,
1695-
args <- vs ++ [ar, v],
1696-
ccs <- ccs ++ [BX] =
1697-
Lambda ccs . ABTN.TAbss args . TMatch u . flip MatchRequest df <$>
1698-
traverse3 (affineHandlerCase self vs ah) cs
1699-
1700-
| otherwise = Nothing
1701-
1702-
where
1703-
ar = freshAff 2
1704-
traverse3 = traverse . traverse . traverse
1705-
1706-
-- Recognizes the entry combinator of a compiled handler. If it is
1707-
-- one, then the result is a modified version with an affine handler
1708-
-- filled in.
1709-
augmentHandlerEntry ::
1710-
Var v => [v] -> v -> v -> v -> ANormal v -> Maybe (ANormal v)
1711-
augmentHandlerEntry vs thunk0 mv0 ah body
1712-
| TName hv (Right mv1) us body <- body,
1713-
THnd rs nh Nothing (TFrc thunk1) <- body,
1714-
mv0 == mv1, nh == hv, thunk0 == thunk1,
1715-
Prelude.and (zipWith (==) us vs) =
1716-
Just .
1717-
TName hv (Right mv1) us .
1718-
TName ahp (Right ah) us $
1719-
THnd rs nh (Just ahp) (TFrc thunk1)
1720-
1721-
| otherwise = Nothing
1722-
where
1723-
ahp = freshAff 1
1724-
1725-
-- Recognizes an affine handler case, yielding a translated efficient
1726-
-- version if it is one.
1727-
affineHandlerCase ::
1728-
Var v => Reference -> [v] -> v -> ANormal v -> Maybe (ANormal v)
1729-
affineHandlerCase self vs rec br
1730-
| ABTN.TAbss us body <- br,
1731-
TShift _ kf0 body <- body,
1732-
TName kf (Left (Builtin "jumpCont")) [kf1] body <- body,
1733-
kf0 == kf1 =
1734-
ABTN.TAbss us <$>
1735-
affinePreBranch self Set.empty vs rec ar kf body
1736-
1737-
| otherwise = Nothing
1738-
where
1739-
ar = freshAff 2
1740-
1741-
-- Allows for having multiple branches that differ in the exact type
1742-
-- of affine handler recognized.
1743-
--
1744-
-- If the entire term doesn't use the continuation, then an irrelevant
1745-
-- handler is generated.
1746-
--
1747-
-- If the immediate term is a match, then we delay the choice of which
1748-
-- type of handler to generate into each branch.
1749-
--
1750-
-- If neither of the above cases hold, then we look for a linear case.
1751-
affinePreBranch ::
1752-
Var v =>
1753-
Reference ->
1754-
Set v ->
1755-
[v] ->
1756-
v ->
1757-
v ->
1758-
v ->
1759-
ANormal v ->
1760-
Maybe (ANormal v)
1761-
affinePreBranch self bound vs rec ar kf bd
1762-
| Just it <- irrelevantTail ar kf bd = Just it
1763-
1764-
| TMatch v bs <- bd =
1765-
TMatch v <$>
1766-
for bs \case
1767-
ABTN.TAbss us bd ->
1768-
ABTN.TAbss us <$>
1769-
affinePreBranch self bound' vs rec ar kf bd
1770-
where
1771-
bound' = Set.union (Set.fromList us) bound
1772-
1773-
| otherwise =
1774-
localize <$>
1775-
runWriterT (translateLinear self bound vs rec ar kf bd)
1776-
where
1777-
localize (tm, Any True) = TLocal ar tm
1778-
localize (tm, Any False) = tm
1779-
1780-
translateLinear ::
1781-
Var v =>
1782-
Reference ->
1783-
Set v ->
1784-
[v] ->
1785-
v ->
1786-
v ->
1787-
v ->
1788-
ANormal v ->
1789-
WriterT Any Maybe (ANormal v)
1790-
translateLinear self bound0 vs rec ar kf = go bound0
1791-
where
1792-
go bound body
1793-
| Just lt <- linearTail self vs bound rec ar kf body =
1794-
lt <$ tell (Any True)
1795-
1796-
| Just it <- irrelevantTail ar kf body = pure it
1797-
1798-
| TLet d v cc e body <- body,
1799-
kf `Set.notMember` ABTN.freeVars e =
1800-
TLet d v cc e <$> go (Set.insert v bound) body
1801-
1802-
| TName v f us body <- body,
1803-
all (kf /=) us =
1804-
TName v f us <$> go (Set.insert v bound) body
1805-
1806-
| TMatch v bs <- body =
1807-
TMatch v <$>
1808-
for bs \case
1809-
ABTN.TAbss us bd ->
1810-
ABTN.TAbss us <$>
1811-
go (Set.fromList us `Set.union` bound) bd
1812-
1813-
| otherwise = mzero
1814-
1815-
-- Recognizes the tail of a linear handler case, where the
1816-
-- continuation is called once in tail position. Returns a transformed
1817-
-- version if a match is found.
1818-
--
1819-
-- Arguments:
1820-
-- self: Reference to handler combinator
1821-
-- bound: arguments bound since header
1822-
-- vs: arguments to handler combinator
1823-
-- rec: local variable for affine handler
1824-
-- ar: argument variable for affine handler info
1825-
-- kf0: continuation variable
1826-
-- tm: term to transform
1827-
--
1828-
-- Note: this relies on inlining into the thunked continuation call to
1829-
-- avoid see exactly what the `k result` call is, rather than it
1830-
-- having multiple forms depending on the variable order.
1831-
linearTail ::
1832-
Var v => Reference -> [v] -> Set v -> v -> v -> v -> ANormal v -> Maybe (ANormal v)
1833-
linearTail self vs bound rec ar kf0 tm
1834-
| TLet _ hr0 _ (TCom r us) tm <- tm, -- recursive handler call
1835-
TName thunk0 (Right kf1) [result] tm <- tm, -- lazy cont resume
1836-
TApv hr1 [thunk1] <- tm, -- apply handler to thunk
1837-
r == self, kf0 == kf1, thunk0 == thunk1, hr0 == hr1 =
1838-
Just . update hr0 us $ TVar result
1839-
1840-
| otherwise = Nothing
1841-
1842-
where
1843-
update huv us
1844-
-- recursive call with identical, non-shadowed variables;
1845-
-- no need to update
1846-
| Prelude.and (zipWith (==) us vs),
1847-
all (`Set.notMember` bound) us = id
1848-
-- repurpose hr0 variable for update call
1849-
| otherwise =
1850-
TName huv (Right rec) (us ++ [ar]) .
1851-
TLets Direct [] [] (TUpdate ar huv)
1852-
1853-
irrelevantTail :: Var v => v -> v -> ANormal v -> Maybe (ANormal v)
1854-
irrelevantTail ar kf tm
1855-
| kf `Set.notMember` ABTN.freeVars tm =
1856-
Just $ TLets Direct [] [] (TDiscard ar) tm
1857-
| otherwise = Nothing
1858-
18591550
-- Checks if two SuperGroups are equivalent up to renaming. The rest
18601551
-- of the structure must match on the nose. If the two groups are not
18611552
-- equivalent, an example of conflicting structure is returned.
@@ -1967,9 +1658,6 @@ bindLocal vs = local (Set.\\ Set.fromList vs)
19671658
freshANF :: (Var v) => Word64 -> v
19681659
freshANF fr = Var.freshenId fr $ typed Var.ANFBlank
19691660

1970-
freshAff :: (Var v) => Word64 -> v
1971-
freshAff fr = Var.freshenId fr $ typed Var.AffBlank
1972-
19731661
fresh :: (Var v) => ANFM v v
19741662
fresh = state $ \(fr, bnd, cs) -> (freshANF fr, (fr + 1, bnd, cs))
19751663

0 commit comments

Comments
 (0)