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
85 = Index Index.ZeroBased Path
92-- ACTUALLY BUILD DECISION TREES
98 , _patterns :: [(Path, Can.Pattern)]
102toDecisionTree :: [Branch] -> DecisionTree
103toDecisionTree rawBranches =
106 map flattenPatterns rawBranches
108 case checkForMatch branches of
118 gatherEdges branches path
121 map (second toDecisionTree) edges
123 case (decisionEdges, fallback) of
124 ([(_tag, decisionTree)], []) ->
128 Decision path decisionEdges Nothing
131 toDecisionTree fallback
134 Decision path decisionEdges (Just (toDecisionTree fallback))
137isComplete :: [Test] -> Bool
140 IsCtor _ _ numAlts _ ->
141 numAlts == length tests
166{-| Flatten type aliases and use the VariantDict to figure out when a tag is
167the only variant so we can skip doing any tests on it.
169flattenPatterns :: Branch -> Branch
170flattenPatterns (Branch goal pathPatterns) =
171 Branch goal (foldr flatten [] pathPatterns)
174flatten :: (Path, Can.Pattern) -> [(Path, Can.Pattern)] -> [(Path, Can.Pattern)]
175flatten pathPattern@(path, A.At region pattern) otherPathPatterns =
178 pathPattern : otherPathPatterns
181 pathPattern : otherPathPatterns
183 Can.PCtor _ _ (Can.Union _ _ numAlts _) _ _ ctorArgs ->
185 case map dearg ctorArgs of
187 flatten (Unbox path, arg) otherPathPatterns
190 foldr flatten otherPathPatterns (subPositions path args)
192 pathPattern : otherPathPatterns
194 Can.PTuple a b maybeC ->
195 flatten (Index Index.first path, a) $
196 flatten (Index Index.second path, b) $
202 flatten (Index Index.third path, c) otherPathPatterns
207 Can.PAlias realPattern alias ->
208 flatten (path, realPattern) $
209 (path, A.At region (Can.PVar alias)) : otherPathPatterns
212 pathPattern : otherPathPatterns
215 pathPattern : otherPathPatterns
218 pathPattern : otherPathPatterns
221 pathPattern : otherPathPatterns
224 pathPattern : otherPathPatterns
227 pathPattern : otherPathPatterns
230subPositions :: Path -> [Can.Pattern] -> [(Path, Can.Pattern)]
231subPositions path patterns =
232 Index.indexedMap (\index pattern -> (Index index path, pattern)) patterns
235dearg :: Can.PatternCtorArg -> Can.Pattern
236dearg (Can.PatternCtorArg _ _ pattern) =
244{-| If the first branch has no more "decision points" we can finally take that
245path. If that is the case we give the resulting label and a mapping from free
246variables to "how to get their value". So a pattern like (Just (x,_)) will give
247us something like ("x" => value.0.0)
249checkForMatch :: [Branch] -> Maybe Int
250checkForMatch branches =
252 Branch goal patterns : _ | all (not . needsTests . snd) patterns ->
260-- GATHER OUTGOING EDGES
263gatherEdges :: [Branch] -> Path -> ([(Test, [Branch])], [Branch])
264gatherEdges branches path =
267 testsAtPath path branches
270 map (edgesFor path branches) relevantTests
273 if isComplete relevantTests then
276 filter (isIrrelevantTo path) branches
278 ( allEdges, fallbacks )
282-- FIND RELEVANT TESTS
285testsAtPath :: Path -> [Branch] -> [Test]
286testsAtPath selectedPath branches =
289 Maybe.mapMaybe (testAtPath selectedPath) branches
291 skipVisited test curr@(uniqueTests, visitedTests) =
292 if Set.member test visitedTests then
296 , Set.insert test visitedTests
299 fst (foldr skipVisited ([], Set.empty) allTests)
302testAtPath :: Path -> Branch -> Maybe Test
303testAtPath selectedPath (Branch _ pathPatterns) =
304 case List.lookup selectedPath pathPatterns of
308 Just (A.At _ pattern) ->
310 Can.PCtor _ _ (Can.Union _ _ numAlts opts) name index _ ->
311 Just (IsCtor name index numAlts opts)
314 Just (case ps of { [] -> IsNil ; _ -> IsCons })
344 error "aliases should never reach 'testAtPath' function"
351edgesFor :: Path -> [Branch] -> Test -> (Test, [Branch])
352edgesFor path branches test =
354 , Maybe.mapMaybe (toRelevantBranch test path) branches
358toRelevantBranch :: Test -> Path -> Branch -> Maybe Branch
359toRelevantBranch test path branch@(Branch goal pathPatterns) =
360 case extract path pathPatterns of
361 Found start (A.At region pattern) end ->
363 Can.PCtor _ _ (Can.Union _ _ numAlts _) name _ ctorArgs ->
365 IsCtor testName _ _ _ | name == testName ->
367 case map dearg ctorArgs of
368 [arg] | numAlts == 1 ->
369 start ++ [(Unbox path, arg)] ++ end
372 start ++ subPositions path args ++ end
380 Just (Branch goal (start ++ end))
388 let tl' = A.At region (Can.PList tl) in
389 Just (Branch goal (start ++ subPositions path [ hd, tl' ] ++ end))
397 Just (Branch goal (start ++ subPositions path [hd,tl] ++ end))
404 IsChr testChr | chr == testChr ->
405 Just (Branch goal (start ++ end))
411 IsStr testStr | str == testStr ->
412 Just (Branch goal (start ++ end))
419 IsInt testInt | int == testInt ->
420 Just (Branch goal (start ++ end))
426 Just (Branch goal (start ++ end))
428 Can.PTuple a b maybeC ->
429 Just (Branch goal (start ++ subPositions path (a : b : Maybe.maybeToList maybeC) ++ end))
449 | Found [(Path, Can.Pattern)] Can.Pattern [(Path, Can.Pattern)]
452extract :: Path -> [(Path, Can.Pattern)] -> Extract
453extract selectedPath pathPatterns =
458 first@(path, pattern) : rest ->
459 if path == selectedPath then
460 Found [] pattern rest
463 case extract selectedPath rest of
467 Found start foundPattern end ->
468 Found (first : start) foundPattern end
472-- FIND IRRELEVANT BRANCHES
475isIrrelevantTo :: Path -> Branch -> Bool
476isIrrelevantTo selectedPath (Branch _ pathPatterns) =
477 case List.lookup selectedPath pathPatterns of
482 not (needsTests pattern)
485needsTests :: Can.Pattern -> Bool
486needsTests (A.At _ pattern) =
489 Can.PAnything -> False
490 Can.PRecord _ -> False
491 Can.PCtor _ _ _ _ _ _ -> True
493 Can.PCons _ _ -> True
495 Can.PTuple _ _ _ -> True
500 error "aliases should never reach 'isIrrelevantTo' function"
508pickPath :: [Branch] -> Path
512 Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
514 case bests (addWeights (smallDefaults branches) allPaths) of
519 head (bests (addWeights (smallBranchingFactor branches) tiedPaths))
522isChoicePath :: (Path, Can.Pattern) -> Maybe Path
523isChoicePath (path, pattern) =
524 if needsTests pattern then
530addWeights :: (Path -> Int) -> [Path] -> [(Path, Int)]
531addWeights toWeight paths =
532 map (\path -> (path, toWeight path)) paths
535bests :: [(Path, Int)] -> [Path]
539 error "Cannot choose the best of zero paths. This should never happen."
541 (headPath, headWeight) : weightedPaths ->
543 gatherMinimum acc@(minWeight, paths) (path, weight) =
544 if weight == minWeight then
545 (minWeight, path : paths)
547 else if weight < minWeight then
553 snd (List.foldl' gatherMinimum (headWeight, [headPath]) weightedPaths)
557-- PATH PICKING HEURISTICS
560smallDefaults :: [Branch] -> Path -> Int
561smallDefaults branches path =
562 length (filter (isIrrelevantTo path) branches)
565smallBranchingFactor :: [Branch] -> Path -> Int
566smallBranchingFactor branches path =
569 gatherEdges branches path
571 length edges + (if null fallback then 0 else 1)
578instance Binary Test where
581 IsCtor a b c d -> putWord8 0 >> put a >> put b >> put c >> put d
584 IsTuple -> putWord8 3
585 IsChr a -> putWord8 4 >> put a
586 IsStr a -> putWord8 5 >> put a
587 IsInt a -> putWord8 6 >> put a
592 0 -> liftM4 IsCtor get get get get
599 _ -> error "problem getting DecisionTree.Test binary"
602instance Binary Path where
605 Index a b -> putWord8 0 >> put a >> put b
606 Unbox a -> putWord8 1 >> put a
612 0 -> liftM2 Index get get
615 _ -> 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