1{-# OPTIONS_GHC -Wall #-}
2{-# LANGUAGE OverloadedStrings #-}
3module Optimize.DecisionTree
12{- To learn more about how this works, definitely read through:
14 "When Do Match-Compilation Heuristics Matter?"
16by Kevin Scott and Norman Ramsey. The rough idea is that we start with a simple
17list of patterns and expressions, and then turn that into a "decision tree"
18that requires as few tests as possible to make it to a leaf. Read the paper, it
19explains this extraordinarily well! We are currently using the same heuristics
20as SML/NJ to get nice trees.
23import Control.Arrow (second)
24import Control.Monad (liftM, liftM2, liftM3)
26import qualified Data.List as List
27import qualified Data.Maybe as Maybe
28import qualified Data.Set as Set
29import Data.Text (Text)
31import qualified AST.Canonical as Can
32import qualified Data.Index as Index
33import qualified Elm.Name as N
34import qualified Reporting.Annotation as A
41{-| Users of this module will mainly interact with this function. It takes
42some normal branches and gives out a decision tree that has "labels" at all
43the leafs and a dictionary that maps these "labels" to the code that should
46If 2 or more leaves point to the same label, we need to do some tricks in JS to
47make that work nicely. When is JS getting goto?! ;) That is outside the scope
50compile :: [(Can.Pattern, Int)] -> DecisionTree
53 format (pattern, index) =
54 Branch index [(Empty, pattern)]
56 toDecisionTree (map format rawBranches)
67 , _edges :: [(Test, DecisionTree)]
68 , _default :: Maybe DecisionTree
74 = IsCtor Int N.Name Index.ZeroBased
93add :: Path -> Path -> Path
100 error "nothing should be added to an alias path"
102 Index index subpath ->
103 Index index (add subpath finalLink)
105 Field name subpath ->
106 Field name (add subpath finalLink)
109subPositions :: Path -> [Can.Pattern] -> [(Path, Can.Pattern)]
110subPositions path patterns =
112 (\index pattern -> (add path (Index index Empty), pattern))
118-- ACTUALLY BUILD DECISION TREES
124 , _patterns :: [(Path, Can.Pattern)]
128toDecisionTree :: [Branch] -> DecisionTree
129toDecisionTree rawBranches =
132 map flattenPatterns rawBranches
134 case checkForMatch branches of
144 gatherEdges branches path
147 map (second toDecisionTree) edges
149 case (decisionEdges, fallback) of
150 ([(_tag, decisionTree)], []) ->
154 Decision path decisionEdges Nothing
157 toDecisionTree fallback
160 Decision path decisionEdges (Just (toDecisionTree fallback))
163isComplete :: [Test] -> Bool
177{-| Flatten type aliases and use the VariantDict to figure out when a tag is
178the only variant so we can skip doing any tests on it.
180flattenPatterns :: Branch -> Branch
181flattenPatterns (Branch goal pathPatterns) =
182 Branch goal (concatMap flatten pathPatterns)
185flatten :: (Path, Can.Pattern) -> [(Path, Can.Pattern)]
186flatten pathPattern@(path, A.At region pattern) =
194 Can.PAlias realPattern alias ->
195 (add path Alias, A.At region (Can.PVar alias))
197 flatten (path, realPattern)
205 Can.PTuple a b Nothing ->
206 concatMap flatten (subPositions path [a,b])
208 Can.PTuple a b (Just c) ->
209 concatMap flatten (subPositions path [a,b,c])
211 Can.PCtor _ _ _ (Can.CtorAlts numAlts _) _ _ args ->
213 concatMap flatten (subPositions path (map dearg args))
233dearg :: Can.PatternCtorArg -> Can.Pattern
234dearg (Can.PatternCtorArg _ _ pattern) =
242{-| If the first branch has no more "decision points" we can finally take that
243path. If that is the case we give the resulting label and a mapping from free
244variables to "how to get their value". So a pattern like (Just (x,_)) will give
245us something like ("x" => value.0.0)
247checkForMatch :: [Branch] -> Maybe Int
248checkForMatch branches =
250 Branch goal patterns : _ | all (not . needsTests . snd) patterns ->
258-- GATHER OUTGOING EDGES
261gatherEdges :: [Branch] -> Path -> ([(Test, [Branch])], [Branch])
262gatherEdges branches path =
265 testsAtPath path branches
268 map (edgesFor path branches) relevantTests
271 if isComplete relevantTests then
274 filter (isIrrelevantTo path) branches
276 ( allEdges, fallbacks )
280-- FIND RELEVANT TESTS
283testsAtPath :: Path -> [Branch] -> [Test]
284testsAtPath selectedPath branches =
287 Maybe.mapMaybe (testAtPath selectedPath) branches
289 skipVisited test curr@(uniqueTests, visitedTests) =
290 if Set.member test visitedTests then
294 , Set.insert test visitedTests
297 fst (foldr skipVisited ([], Set.empty) allTests)
300testAtPath :: Path -> Branch -> Maybe Test
301testAtPath selectedPath (Branch _ pathPatterns) =
302 case List.lookup selectedPath pathPatterns of
306 Just (A.At _ pattern) ->
308 Can.PCtor _ _ _ (Can.CtorAlts numAlts _) name index _ ->
309 Just (IsCtor numAlts name index)
333 error "aliases should never reach 'testAtPath' function"
348{-# NOINLINE nilTest #-}
350nilTest = IsCtor 2 "[]" (Index.unsafe (-1))
353{-# NOINLINE consTest #-}
355consTest = IsCtor 2 "::" (Index.unsafe (-2))
362edgesFor :: Path -> [Branch] -> Test -> (Test, [Branch])
363edgesFor path branches test =
365 , Maybe.mapMaybe (toRelevantBranch test path) branches
369toRelevantBranch :: Test -> Path -> Branch -> Maybe Branch
370toRelevantBranch test path branch@(Branch goal pathPatterns) =
371 case extract path pathPatterns of
372 Just (start, A.At region pattern, end) ->
374 Can.PCtor _ _ _ (Can.CtorAlts numAlts _) name index args ->
375 if test == IsCtor numAlts name index then
376 Just (Branch goal (start ++ subPositions path (map dearg args) ++ end))
381 if test == nilTest then
382 Just (Branch goal (start ++ end))
387 if test == consTest then
388 let tl' = A.At region (Can.PList tl) in
389 Just (Branch goal (start ++ subPositions path [ hd, tl' ] ++ end))
394 if test == consTest then
395 Just (Branch goal (start ++ subPositions path [hd,tl] ++ end))
400 if test == IsChr chr then
401 Just (Branch goal (start ++ end))
406 if test == IsStr str then
407 Just (Branch goal (start ++ end))
412 if test == IsInt int then
413 Just (Branch goal (start ++ end))
426 -> [(Path, Can.Pattern)]
427 -> Maybe ([(Path, Can.Pattern)], Can.Pattern, [(Path, Can.Pattern)])
428extract selectedPath pathPatterns =
433 first@(path, pattern) : rest ->
434 if path == selectedPath then
435 Just ([], pattern, rest)
438 case extract selectedPath rest of
442 Just (start, foundPattern, end) ->
443 Just (first : start, foundPattern, end)
447-- FIND IRRELEVANT BRANCHES
450isIrrelevantTo :: Path -> Branch -> Bool
451isIrrelevantTo selectedPath (Branch _ pathPatterns) =
452 case List.lookup selectedPath pathPatterns of
457 not (needsTests pattern)
460needsTests :: Can.Pattern -> Bool
461needsTests (A.At _ pattern) =
470 error "aliases should never reach 'isIrrelevantTo' function"
475 Can.PCtor _ _ _ _ _ _ _ ->
504pickPath :: [Branch] -> Path
508 Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
510 case bests (addWeights (smallDefaults branches) allPaths) of
515 head (bests (addWeights (smallBranchingFactor branches) tiedPaths))
518isChoicePath :: (Path, Can.Pattern) -> Maybe Path
519isChoicePath (path, pattern) =
520 if needsTests pattern then
526addWeights :: (Path -> Int) -> [Path] -> [(Path, Int)]
527addWeights toWeight paths =
528 map (\path -> (path, toWeight path)) paths
531bests :: [(Path, Int)] -> [Path]
535 error "Cannot choose the best of zero paths. This should never happen."
537 (headPath, headWeight) : weightedPaths ->
539 gatherMinimum acc@(minWeight, paths) (path, weight) =
540 if weight == minWeight then
541 (minWeight, path : paths)
543 else if weight < minWeight then
549 snd (List.foldl' gatherMinimum (headWeight, [headPath]) weightedPaths)
553-- PATH PICKING HEURISTICS
556smallDefaults :: [Branch] -> Path -> Int
557smallDefaults branches path =
558 length (filter (isIrrelevantTo path) branches)
561smallBranchingFactor :: [Branch] -> Path -> Int
562smallBranchingFactor branches path =
565 gatherEdges branches path
567 length edges + (if null fallback then 0 else 1)
574instance Binary Test where
577 IsCtor a b c -> putWord8 0 >> put a >> put b >> put c
578 IsChr a -> putWord8 1 >> put a
579 IsStr a -> putWord8 2 >> put a
580 IsInt a -> putWord8 3 >> put a
585 0 -> liftM3 IsCtor get get get
589 _ -> error "problem getting DecisionTree.Test binary"
592instance Binary Path where
595 Index a b -> putWord8 0 >> put a >> put b
596 Field a b -> putWord8 1 >> put a >> put b
603 0 -> liftM2 Index get get
604 1 -> liftM2 Field get get
607 _ -> error "problem getting DecisionTree.Path binary"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list