@@ -1,266 +1,411 @@
1−module Optimize.Cases (toMatch, Match (..), Clause (..), matchSubst, newVar) where
1+module Optimize.Cases where
2+{- To learn more about how this works, definitely read through:
3−import Control.Applicative ((<$>))
4+ "When Do Match-Compilation Heuristics Matter?"
6+by Kevin Scott and Norman Ramsey. The rough idea is that we start with a simple
7+list of patterns and expressions, and then turn that into a "decision tree"
8+that requires as few tests as possible to make it to a leaf. Read the paper, it
9+explains this extraordinarily well! We are currently using the same heuristics
10+as SML/NJ to get nice trees.
12+Users of this module will mainly interact with the 'compile' function which
13+takes some normal branches and gives out a decision tree that has "labels" at
14+all the leafs and a dictionary that maps these "labels" to the code that
17+If 2 or more leaves point to the same label, we need to do some tricks in JS to
18+make that work nicely. When is JS getting goto?! ;)
4−import Control.Arrow (first)
22+import Control.Arrow (second)
5−import Control.Monad.State (State)
23+import Data.Function (on)
6−import qualified Control.Monad.State as State
7import qualified Data.List as List
24import qualified Data.List as List
8−import Data.Maybe (fromMaybe)
25+import qualified Data.Map as Map
27+import qualified Data.Maybe as Maybe
10−import qualified AST.Expression.General as Expr
29+import qualified AST.Expression.Optimized as Opt
11−import qualified AST.Expression.Optimized as Optimized
12−import qualified AST.Literal as Literal
30+import qualified AST.Literal as L
13import qualified AST.Pattern as P
31import qualified AST.Pattern as P
14import qualified AST.Variable as Var
32import qualified AST.Variable as Var
15−import qualified Optimize.Substitute as S
16import qualified Reporting.Annotation as A
33import qualified Reporting.Annotation as A
19−-- MATCHES
36+-- COMPILE CASES
40+ -> [(OPattern, Opt.Expr)]
41+ -> (DecisionTree Int, Map.Map Int Opt.Expr)
42+compile variantDict rawBranches =
44+ format index (pattern, expr) =
45+ ( Branch index [(Empty, pattern)]
49+ (branches, resultList) =
50+ unzip (zipWith format [0..] rawBranches)
52+ ( toDecisionTree variantDict branches
53+ , Map.fromList resultList
62+ , _edges :: [(Tag, DecisionTree a)]
63+ , _default :: Maybe (DecisionTree a)
67+ , substitutions :: Map.Map String Path
74+ = Constructor Var.Canonical
21−data Match
89+add :: Path -> Path -> Path
22− = Match String [Clause] Match
90+add path finalLink =
23− | Break
91+ case path of
25− | Other Optimized.Expr
93+ finalLink
96+ error "nothing should be added to an alias path"
30−data Clause =
98+ Position index subpath ->
31− Clause (Either Var.Canonical Literal.Literal) [String] Match
99+ Position index (add subpath finalLink)
101+ Field name subpath ->
102+ Field name (add subpath finalLink)
36− ([P.Optimized], Optimized.Expr)
105+subPositions :: Path -> [OPattern] -> [(Path, OPattern)]
106+subPositions path patterns =
108+ (\index pattern -> (add path (Position index Empty), pattern))
41−toMatch :: [(P.Optimized, Optimized.Expr)] -> State Int (String, Match)
113+-- CREATE DECISION TREES
44− (,) v <$> buildMatch [v] (map (first (:[])) patterns) Fail
47−newVar :: State Int String
116+data Branch =
49− do n <- State.get
118+ { _goal :: Int
50− State.modify (+1)
119+ , _patterns :: [(Path, OPattern)]
51− return $ "_v" ++ show n
120+ }
54−matchSubst :: [(String,String)] -> Match -> Match
123+type OPattern = P.Optimized
55−matchSubst pairs match =
61− Seq ms ->
126+type VariantDict = Map.Map Var.Canonical Int
62− Seq (map (matchSubst pairs) ms)
64− Other (A.A ann expr) ->
128+
129+-- BUILD A DECISION TREE
131+toDecisionTree :: VariantDict -> [Branch] -> DecisionTree Int
132+toDecisionTree variantDict rawBranches =
135+ map (skipUselessPatterns variantDict) rawBranches
137+ case isAllValiables branches of
138+ Just (goal, substitutions) ->
139+ Match goal substitutions
66− substitutions =
143+ path =
67− map (\(x,y) -> S.subst x (Expr.localVar y)) pairs
144+ smallDefaults branches
147+ tagsAtPath path branches
150+ map (edgesFor path branches) relevantTags
152+ (rawEdges, unexplored) =
153+ if isComplete variantDict relevantTags then
155+ , snd (last allRawEdges)
160+ , filter (isIrrelevantTo path) branches
164+ map (second (toDecisionTree variantDict)) rawEdges
69− Other $ A.A ann $ foldr ($) expr substitutions
166+ case (edges, unexplored) of
167+ ([(_tag, decisionTree)], []) ->
71− Match n cs m ->
170+ (_, []) ->
72− Match (varSubst n) (map clauseSubst cs) (matchSubst pairs m)
171+ Test path edges Nothing
74− varSubst v = fromMaybe v (lookup v pairs)
75− clauseSubst (Clause c vs m) =
76− Clause c (map varSubst vs) (matchSubst pairs m)
174+ Test path edges (Just (toDecisionTree variantDict unexplored))
81−buildMatch :: [String] -> [Branch] -> Match -> State Int Match
177+isComplete :: VariantDict -> [Tag] -> Bool
82−buildMatch variables branches match =
178+isComplete variantDict tags =
83− case (variables, branches, match) of
179+ case head tags of
84− ([], [], _) ->
180+ Constructor name ->
85− return match
181+ (variantDict ! name) == length tags
87− ([], [([], expr)], Fail) ->
183+ Literal (L.Boolean _) ->
88− return $ Other expr
184+ length tags == 2
90− ([], [([], expr)], Break) ->
186+ _ ->
91− return $ Other expr
187+ False
94− return $ Seq (map (Other . snd) branches ++ [match])
97− | all isVar branches' -> matchVar variables branches' match
98− | all isCon branches' -> matchCon variables branches' match
99− | otherwise -> matchMix variables branches' match
101− branches' = map (dealias var) branches
191+-- SKIP USELESS TESTS
104−dealias :: String -> Branch -> Branch
193+skipUselessPatterns :: VariantDict -> Branch -> Branch
105−dealias _ ([], _) = noMatch "dealias"
194+skipUselessPatterns variantDict (Branch goal pathPatterns) =
106−dealias var branch@(p:ps, A.A ann e) =
195+ Branch goal (concatMap (skipper variantDict) pathPatterns)
108− A.A _ (P.Alias alias pattern) ->
110− , A.A ann (S.subst alias (Expr.localVar var) e)
198+skipper :: VariantDict -> (Path, OPattern) -> [(Path, OPattern)]
199+skipper variantDict pathPattern@(path, A.A ann pattern) =
117−isCon :: ([P.Pattern ann var], expr) -> Bool
204+ P.Anything ->
118−isCon ([], _) = noMatch "isCon"
205+ [pathPattern]
119−isCon (A.A _ pattern : _, _) =
207+ P.Alias alias realPattern ->
208+ [ (add path Alias, A.A ann (P.Var alias))
209+ , (path, realPattern)
126−isVar :: ([P.Pattern ann var], expr) -> Bool
212+ P.Record _ ->
127−isVar p =
213+ [pathPattern]
215+ P.Data tag patterns ->
216+ if variantDict ! tag == 1 then
217+ concatMap (skipper variantDict) (subPositions path patterns)
131−-- VARIABLE MATCHES
219+ else
133−matchVar :: [String] -> [Branch] -> Match -> State Int Match
222+ P.Literal _ ->
134−matchVar [] _ _ = noMatch "matchVar"
223+ [pathPattern]
135−matchVar (var:vars) branches match =
224+
136− buildMatch vars (map subVar branches) match
225+
137− where
226+-- VARIABLE SUBSTITUTIONS
138− subVar ([], _) = noMatch "matchVar.subVar"
227+
139− subVar (A.A _ pattern : patterns, (A.A ann expr)) =
228+isAllValiables :: [Branch] -> Maybe (Int, Map.Map String Path)
140− ( patterns
229+isAllValiables branches =
141− , A.A ann (subOnePattern pattern expr)
230+ case branches of
142− )
231+ Branch goal patterns : _ ->
143− where
232+ do subs <- concat `fmap` mapM getSubstitution patterns
144− subOnePattern pattern expr =
233+ return (goal, Map.fromList subs)
239+getSubstitution :: (Path, OPattern) -> Maybe [(String, Path)]
240+getSubstitution (path, A.A _ pattern) =
145− case pattern of
241+ case pattern of
146− P.Anything ->
245+ P.Anything ->
149− P.Var x ->
248+ P.Alias _ _ ->
150− S.subst x (Expr.localVar var) expr
249+ error "aliases should never reach 'getSubstitution' function"
152− P.Record fields ->
251+ P.Record fields ->
153− let access = Expr.Access (A.A ann (Expr.localVar var))
252+ Just (map (\name -> (name, add path (Field name Empty))) fields)
155− foldr (\x -> S.subst x (access x)) expr fields
158− noMatch "matchVar.subVar"
161−-- CONSTRUCTOR MATCHES
163−matchCon :: [String] -> [Branch] -> Match -> State Int Match
164−matchCon variables branches match =
166− [] -> noMatch "matchCon"
169− flip (Match var) match <$> mapM toClause caseGroups
171− caseGroups :: [[Branch]]
173− List.groupBy branchEq (List.sortBy branchCmp branches)
175− toClause :: [Branch] -> State Int Clause
177− case head branches of
178− (A.A _ (P.Data name _) : _, _) ->
254+ P.Data _ _ ->
179− matchClause (Left name) variables branches Break
255+ Nothing
181− (A.A _ (P.Literal lit) : _, _) ->
257+ P.Literal _ ->
182− matchClause (Right lit) variables branches Break
258+ Nothing
184− _ -> noMatch "matchCon"
260+
185−
261+-- FIND RELEVANT TAGS
188− :: Either Var.Canonical Literal.Literal
190− -> [Branch]
263+tagsAtPath :: Path -> [Branch] -> [Tag]
191− -> Match
264+tagsAtPath selectedPath branches =
192− -> State Int Clause
265+ List.nub $ Maybe.mapMaybe (tagAtPath selectedPath) branches
193−matchClause _ [] _ _ = noMatch "matchClause"
266+
194−matchClause c (_:vs) branches match =
267+
195− do vs' <- getVars
268+tagAtPath :: Path -> Branch -> Maybe Tag
196− Clause c vs' <$> buildMatch (vs' ++ vs) (map flatten branches) match
269+tagAtPath selectedPath (Branch _ pathPatterns) =
197− where
270+ case List.lookup selectedPath pathPatterns of
198− flatten ([], _) = noMatch "matchClause.flatten"
271+ Nothing ->
199− flatten (A.A _ pattern : patterns, expr) =
274+ Just (A.A _ pattern) ->
200 case pattern of
275 case pattern of
201− P.Data _ argPatterns ->
276+ P.Data name _ ->
202− (argPatterns ++ patterns, expr)
277+ Just (Constructor name)
204− P.Literal _ ->
279+ P.Literal lit ->
205− (patterns, expr)
280+ Just (Literal lit)
286+ error "aliases should never reach 'tagAtPath' function"
297+edgesFor :: Path -> [Branch] -> Tag -> (Tag, [Branch])
298+edgesFor path branches tag =
300+ , Maybe.mapMaybe (subBranch tag path) branches
208− noMatch "matchClause.flatten"
210− getVars :: State Int [String]
304+subBranch :: Tag -> Path -> Branch -> Maybe Branch
211− getVars =
305+subBranch tag path branch@(Branch goal pathPatterns) =
212− case head branches of
306+ case extract path pathPatterns of
213− (A.A _ (P.Data _ argPatterns) : _, _) ->
307+ Just (start, A.A _ pattern, end) ->
214− State.forM argPatterns (const newVar)
308+ case pattern of
309+ P.Data name patterns | tag == Constructor name ->
310+ Just (Branch goal (start ++ subPositions path patterns ++ end))
216− (A.A _ (P.Literal _) : _, _) ->
312+ P.Literal lit | tag == Literal lit ->
217− return []
313+ Just (Branch goal (start ++ end))
220− noMatch "matchClause.getVars"
316+ Nothing
324+ -> [(Path, OPattern)]
325+ -> Maybe ([(Path, OPattern)], OPattern, [(Path, OPattern)])
326+extract selectedPath pathPatterns =
327+ case pathPatterns of
331+ first@(path, pattern) : rest ->
332+ if path == selectedPath then
333+ Just ([], pattern, rest)
336+ case extract selectedPath rest of
340+ Just (start, foundPattern, end) ->
341+ Just (first : start, foundPattern, end)
344+-- FIND IRRELEVANT BRANCHES
346+isIrrelevantTo :: Path -> Branch -> Bool
347+isIrrelevantTo selectedPath (Branch _ pathPatterns) =
348+ case List.lookup selectedPath pathPatterns of
352+ Just (A.A _ pattern) ->
361+ error "aliases should never reach 'isIrrelevantTo' function"
223−branchEq :: Branch -> Branch -> Bool
373+-- PATH PICKING HEURISTICS
224−branchEq (A.A _ pattern : _, _) (A.A _ pattern' : _, _) =
225− case (pattern, pattern') of
226− (P.Data name _, P.Data name' _) ->
229− (P.Literal lit, P.Literal lit') ->
375+smallDefaults :: [Branch] -> Path
230− lit == lit'
376+smallDefaults branches =
379+ Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
232− (_, _) ->
381+ weightedPaths =
233− noMatch "matchCon.branchEq"
382+ map (\path -> (path, length (filter (isIrrelevantTo path) branches))) allPaths
384+ fst (List.minimumBy (compare `on` snd) weightedPaths)
235−branchEq _ _ = noMatch "matchCon.branchEq"
387+isChoicePath :: (Path, OPattern) -> Maybe Path
388+isChoicePath (path, A.A _ pattern) =
238−branchCmp :: Branch -> Branch -> Ordering
393+ P.Anything ->
239−branchCmp (A.A _ pattern : _, _) (A.A _ pattern' : _, _) =
394+ Nothing
240− case (pattern, pattern') of
241− (P.Data name _, P.Data name' _) ->
244− (P.Literal lit, P.Literal lit') ->
396+ P.Alias _ _ ->
245− compare lit lit'
397+ error "aliases should never reach 'isChoicePath' function"
247− (_, _) ->
399+ P.Record _ ->
248− noMatch "matchCon.branchCmp"
400+ Nothing
250−branchCmp _ _ = noMatch "matchCon.branchCmp"
402+ P.Data _ _ ->
255−matchMix :: [String] -> [Branch] -> Match -> State Int Match
409+-- smallBranchingFactor :: [Branch] -> Path
256−matchMix vs branches def =
257− State.foldM (flip $ buildMatch vs) def (reverse branchGroups)
260− List.groupBy (\p1 p2 -> isCon p1 == isCon p2) branches
263−noMatch :: String -> a
265− error $ "unexpected pattern in `" ++ name ++
266− "` function. Report a compiler issue to <https://github.com/elm-lang/Elm>."