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, liftM4)
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 N.Name Index.ZeroBased Int Can.CtorOpts
84 = Index Index.ZeroBased Path
91-- ACTUALLY BUILD DECISION TREES
97 , _patterns :: [(Path, Can.Pattern)]
101toDecisionTree :: [Branch] -> DecisionTree
102toDecisionTree rawBranches =
105 map flattenPatterns rawBranches
107 case checkForMatch branches of
117 gatherEdges branches path
120 map (second toDecisionTree) edges
122 case (decisionEdges, fallback) of
123 ([(_tag, decisionTree)], []) ->
127 Decision path decisionEdges Nothing
130 toDecisionTree fallback
133 Decision path decisionEdges (Just (toDecisionTree fallback))
136isComplete :: [Test] -> Bool
139 IsCtor _ _ numAlts _ ->
140 numAlts == length tests
156{-| Flatten type aliases and use the VariantDict to figure out when a tag is
157the only variant so we can skip doing any tests on it.
159flattenPatterns :: Branch -> Branch
160flattenPatterns (Branch goal pathPatterns) =
161 Branch goal (concatMap flatten pathPatterns)
164flatten :: (Path, Can.Pattern) -> [(Path, Can.Pattern)]
165flatten pathPattern@(path, A.At region pattern) =
173 Can.PTuple a b Nothing ->
174 concatMap flatten (subPositions path [a,b])
176 Can.PTuple a b (Just c) ->
177 concatMap flatten (subPositions path [a,b,c])
179 Can.PCtor _ _ (Can.Union _ _ 1 _) _ _ ctorArgs ->
180 case map dearg ctorArgs of
182 flatten (Unbox path, arg)
185 concatMap flatten (subPositions path args)
187 Can.PCtor _ _ _ _ _ _ ->
190 Can.PAlias realPattern alias ->
191 (path, A.At region (Can.PVar alias)) : flatten (path, realPattern)
215subPositions :: Path -> [Can.Pattern] -> [(Path, Can.Pattern)]
216subPositions path patterns =
217 Index.indexedMap (\index pattern -> (Index index path, pattern)) patterns
220dearg :: Can.PatternCtorArg -> Can.Pattern
221dearg (Can.PatternCtorArg _ _ pattern) =
229{-| If the first branch has no more "decision points" we can finally take that
230path. If that is the case we give the resulting label and a mapping from free
231variables to "how to get their value". So a pattern like (Just (x,_)) will give
232us something like ("x" => value.0.0)
234checkForMatch :: [Branch] -> Maybe Int
235checkForMatch branches =
237 Branch goal patterns : _ | all (not . needsTests . snd) patterns ->
245-- GATHER OUTGOING EDGES
248gatherEdges :: [Branch] -> Path -> ([(Test, [Branch])], [Branch])
249gatherEdges branches path =
252 testsAtPath path branches
255 map (edgesFor path branches) relevantTests
258 if isComplete relevantTests then
261 filter (isIrrelevantTo path) branches
263 ( allEdges, fallbacks )
267-- FIND RELEVANT TESTS
270testsAtPath :: Path -> [Branch] -> [Test]
271testsAtPath selectedPath branches =
274 Maybe.mapMaybe (testAtPath selectedPath) branches
276 skipVisited test curr@(uniqueTests, visitedTests) =
277 if Set.member test visitedTests then
281 , Set.insert test visitedTests
284 fst (foldr skipVisited ([], Set.empty) allTests)
287testAtPath :: Path -> Branch -> Maybe Test
288testAtPath selectedPath (Branch _ pathPatterns) =
289 case List.lookup selectedPath pathPatterns of
293 Just (A.At _ pattern) ->
295 Can.PCtor _ _ (Can.Union _ _ numAlts opts) name index _ ->
296 Just (IsCtor name index numAlts opts)
320 error "aliases should never reach 'testAtPath' function"
339edgesFor :: Path -> [Branch] -> Test -> (Test, [Branch])
340edgesFor path branches test =
342 , Maybe.mapMaybe (toRelevantBranch test path) branches
346toRelevantBranch :: Test -> Path -> Branch -> Maybe Branch
347toRelevantBranch test path branch@(Branch goal pathPatterns) =
348 case extract path pathPatterns of
349 Just (start, A.At region pattern, end) ->
351 Can.PCtor _ _ (Can.Union _ _ numAlts _) name _ ctorArgs ->
353 IsCtor testName _ _ _ | name == testName ->
355 case map dearg ctorArgs of
356 [arg] | numAlts == 1 ->
357 start ++ [(Unbox path, arg)] ++ end
360 start ++ subPositions path args ++ end
368 Just (Branch goal (start ++ end))
376 let tl' = A.At region (Can.PList tl) in
377 Just (Branch goal (start ++ subPositions path [ hd, tl' ] ++ end))
385 Just (Branch goal (start ++ subPositions path [hd,tl] ++ end))
392 IsChr testChr | chr == testChr ->
393 Just (Branch goal (start ++ end))
399 IsStr testStr | str == testStr ->
400 Just (Branch goal (start ++ end))
407 IsInt testInt | int == testInt ->
408 Just (Branch goal (start ++ end))
422 -> [(Path, Can.Pattern)]
423 -> Maybe ([(Path, Can.Pattern)], Can.Pattern, [(Path, Can.Pattern)])
424extract selectedPath pathPatterns =
429 first@(path, pattern) : rest ->
430 if path == selectedPath then
431 Just ([], pattern, rest)
434 case extract selectedPath rest of
438 Just (start, foundPattern, end) ->
439 Just (first : start, foundPattern, end)
443-- FIND IRRELEVANT BRANCHES
446isIrrelevantTo :: Path -> Branch -> Bool
447isIrrelevantTo selectedPath (Branch _ pathPatterns) =
448 case List.lookup selectedPath pathPatterns of
453 not (needsTests pattern)
456needsTests :: Can.Pattern -> Bool
457needsTests (A.At _ pattern) =
466 error "aliases should never reach 'isIrrelevantTo' function"
471 Can.PCtor _ _ _ _ _ _ ->
500pickPath :: [Branch] -> Path
504 Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
506 case bests (addWeights (smallDefaults branches) allPaths) of
511 head (bests (addWeights (smallBranchingFactor branches) tiedPaths))
514isChoicePath :: (Path, Can.Pattern) -> Maybe Path
515isChoicePath (path, pattern) =
516 if needsTests pattern then
522addWeights :: (Path -> Int) -> [Path] -> [(Path, Int)]
523addWeights toWeight paths =
524 map (\path -> (path, toWeight path)) paths
527bests :: [(Path, Int)] -> [Path]
531 error "Cannot choose the best of zero paths. This should never happen."
533 (headPath, headWeight) : weightedPaths ->
535 gatherMinimum acc@(minWeight, paths) (path, weight) =
536 if weight == minWeight then
537 (minWeight, path : paths)
539 else if weight < minWeight then
545 snd (List.foldl' gatherMinimum (headWeight, [headPath]) weightedPaths)
549-- PATH PICKING HEURISTICS
552smallDefaults :: [Branch] -> Path -> Int
553smallDefaults branches path =
554 length (filter (isIrrelevantTo path) branches)
557smallBranchingFactor :: [Branch] -> Path -> Int
558smallBranchingFactor branches path =
561 gatherEdges branches path
563 length edges + (if null fallback then 0 else 1)
570instance Binary Test where
573 IsCtor a b c d -> putWord8 0 >> put a >> put b >> put c >> put d
576 IsChr a -> putWord8 3 >> put a
577 IsStr a -> putWord8 4 >> put a
578 IsInt a -> putWord8 5 >> put a
583 0 -> liftM4 IsCtor get 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 Unbox a -> putWord8 1 >> put a
602 0 -> liftM2 Index get get
605 _ -> 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