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, liftM5)
26import qualified Data.List as List
27import qualified Data.Maybe as Maybe
28import qualified Data.Name as Name
29import qualified Data.Set as Set
30import qualified Data.Utf8 as Utf8
32import qualified AST.Canonical as Can
33import qualified Data.Index as Index
34import qualified Elm.ModuleName as ModuleName
35import qualified Reporting.Annotation as A
42{-| Users of this module will mainly interact with this function. It takes
43some normal branches and gives out a decision tree that has "labels" at all
44the leafs and a dictionary that maps these "labels" to the code that should
47If 2 or more leaves point to the same label, we need to do some tricks in JS to
48make that work nicely. When is JS getting goto?! ;) That is outside the scope
51compile :: [(Can.Pattern, Int)] -> DecisionTree
54 format (pattern, index) =
55 Branch index [(Empty, pattern)]
57 toDecisionTree (map format rawBranches)
68 , _edges :: [(Test, DecisionTree)]
69 , _default :: Maybe DecisionTree
75 = IsCtor ModuleName.Canonical Name.Name Index.ZeroBased Int Can.CtorOpts
87 = Index Index.ZeroBased Path
94-- ACTUALLY BUILD DECISION TREES
100 , _patterns :: [(Path, Can.Pattern)]
104toDecisionTree :: [Branch] -> DecisionTree
105toDecisionTree rawBranches =
108 map flattenPatterns rawBranches
110 case checkForMatch branches of
120 gatherEdges branches path
123 map (second toDecisionTree) edges
125 case (decisionEdges, fallback) of
126 ([(_tag, decisionTree)], []) ->
130 Decision path decisionEdges Nothing
133 toDecisionTree fallback
136 Decision path decisionEdges (Just (toDecisionTree fallback))
139isComplete :: [Test] -> Bool
142 IsCtor _ _ _ numAlts _ ->
143 numAlts == length tests
171{-| Flatten type aliases and use the VariantDict to figure out when a tag is
172the only variant so we can skip doing any tests on it.
174flattenPatterns :: Branch -> Branch
175flattenPatterns (Branch goal pathPatterns) =
176 Branch goal (foldr flatten [] pathPatterns)
179flatten :: (Path, Can.Pattern) -> [(Path, Can.Pattern)] -> [(Path, Can.Pattern)]
180flatten pathPattern@(path, A.At region pattern) otherPathPatterns =
183 pathPattern : otherPathPatterns
186 pathPattern : otherPathPatterns
188 Can.PCtor _ _ (Can.Union _ _ numAlts _) _ _ ctorArgs ->
190 case map dearg ctorArgs of
192 flatten (Unbox path, arg) otherPathPatterns
195 foldr flatten otherPathPatterns (subPositions path args)
197 pathPattern : otherPathPatterns
199 Can.PTuple a b maybeC ->
200 flatten (Index Index.first path, a) $
201 flatten (Index Index.second path, b) $
207 flatten (Index Index.third path, c) otherPathPatterns
212 Can.PAlias realPattern alias ->
213 flatten (path, realPattern) $
214 (path, A.At region (Can.PVar alias)) : otherPathPatterns
217 pathPattern : otherPathPatterns
220 pathPattern : otherPathPatterns
223 pathPattern : otherPathPatterns
226 pathPattern : otherPathPatterns
229 pathPattern : otherPathPatterns
232 pathPattern : otherPathPatterns
235 pathPattern : otherPathPatterns
238subPositions :: Path -> [Can.Pattern] -> [(Path, Can.Pattern)]
239subPositions path patterns =
240 Index.indexedMap (\index pattern -> (Index index path, pattern)) patterns
243dearg :: Can.PatternCtorArg -> Can.Pattern
244dearg (Can.PatternCtorArg _ _ pattern) =
252{-| If the first branch has no more "decision points" we can finally take that
253path. If that is the case we give the resulting label and a mapping from free
254variables to "how to get their value". So a pattern like (Just (x,_)) will give
255us something like ("x" => value.0.0)
257checkForMatch :: [Branch] -> Maybe Int
258checkForMatch branches =
260 Branch goal patterns : _ | all (not . needsTests . snd) patterns ->
268-- GATHER OUTGOING EDGES
271gatherEdges :: [Branch] -> Path -> ([(Test, [Branch])], [Branch])
272gatherEdges branches path =
275 testsAtPath path branches
278 map (edgesFor path branches) relevantTests
281 if isComplete relevantTests then
284 filter (isIrrelevantTo path) branches
286 ( allEdges, fallbacks )
290-- FIND RELEVANT TESTS
293testsAtPath :: Path -> [Branch] -> [Test]
294testsAtPath selectedPath branches =
297 Maybe.mapMaybe (testAtPath selectedPath) branches
299 skipVisited test curr@(uniqueTests, visitedTests) =
300 if Set.member test visitedTests then
304 , Set.insert test visitedTests
307 fst (foldr skipVisited ([], Set.empty) allTests)
310testAtPath :: Path -> Branch -> Maybe Test
311testAtPath selectedPath (Branch _ pathPatterns) =
312 case List.lookup selectedPath pathPatterns of
316 Just (A.At _ pattern) ->
318 Can.PCtor home _ (Can.Union _ _ numAlts opts) name index _ ->
319 Just (IsCtor home name index numAlts opts)
322 Just (case ps of { [] -> IsNil ; _ -> IsCons })
355 error "aliases should never reach 'testAtPath' function"
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 Found start (A.At region pattern) end ->
374 Can.PCtor _ _ (Can.Union _ _ numAlts _) name _ ctorArgs ->
376 IsCtor _ testName _ _ _ | name == testName ->
378 case map dearg ctorArgs of
379 [arg] | numAlts == 1 ->
380 start ++ [(Unbox path, arg)] ++ end
383 start ++ subPositions path args ++ end
391 Just (Branch goal (start ++ end))
399 let tl' = A.At region (Can.PList tl) in
400 Just (Branch goal (start ++ subPositions path [ hd, tl' ] ++ end))
408 Just (Branch goal (start ++ subPositions path [hd,tl] ++ end))
415 IsChr testChr | chr == testChr ->
416 Just (Branch goal (start ++ end))
422 IsStr testStr | str == testStr ->
423 Just (Branch goal (start ++ end))
430 IsInt testInt | int == testInt ->
431 Just (Branch goal (start ++ end))
438 IsBool testBool | bool == testBool ->
439 Just (Branch goal (start ++ end))
445 Just (Branch goal (start ++ end))
447 Can.PTuple a b maybeC ->
448 Just (Branch goal (start ++ subPositions path (a : b : Maybe.maybeToList maybeC) ++ end))
468 | Found [(Path, Can.Pattern)] Can.Pattern [(Path, Can.Pattern)]
471extract :: Path -> [(Path, Can.Pattern)] -> Extract
472extract selectedPath pathPatterns =
477 first@(path, pattern) : rest ->
478 if path == selectedPath then
479 Found [] pattern rest
482 case extract selectedPath rest of
486 Found start foundPattern end ->
487 Found (first : start) foundPattern end
491-- FIND IRRELEVANT BRANCHES
494isIrrelevantTo :: Path -> Branch -> Bool
495isIrrelevantTo selectedPath (Branch _ pathPatterns) =
496 case List.lookup selectedPath pathPatterns of
501 not (needsTests pattern)
504needsTests :: Can.Pattern -> Bool
505needsTests (A.At _ pattern) =
508 Can.PAnything -> False
509 Can.PRecord _ -> False
510 Can.PCtor _ _ _ _ _ _ -> True
512 Can.PCons _ _ -> True
514 Can.PTuple _ _ _ -> True
518 Can.PBool _ _ -> True
520 error "aliases should never reach 'isIrrelevantTo' function"
528pickPath :: [Branch] -> Path
532 Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
534 case bests (addWeights (smallDefaults branches) allPaths) of
539 head (bests (addWeights (smallBranchingFactor branches) tiedPaths))
542isChoicePath :: (Path, Can.Pattern) -> Maybe Path
543isChoicePath (path, pattern) =
544 if needsTests pattern then
550addWeights :: (Path -> Int) -> [Path] -> [(Path, Int)]
551addWeights toWeight paths =
552 map (\path -> (path, toWeight path)) paths
555bests :: [(Path, Int)] -> [Path]
559 error "Cannot choose the best of zero paths. This should never happen."
561 (headPath, headWeight) : weightedPaths ->
563 gatherMinimum acc@(minWeight, paths) (path, weight) =
564 if weight == minWeight then
565 (minWeight, path : paths)
567 else if weight < minWeight then
573 snd (List.foldl' gatherMinimum (headWeight, [headPath]) weightedPaths)
577-- PATH PICKING HEURISTICS
580smallDefaults :: [Branch] -> Path -> Int
581smallDefaults branches path =
582 length (filter (isIrrelevantTo path) branches)
585smallBranchingFactor :: [Branch] -> Path -> Int
586smallBranchingFactor branches path =
589 gatherEdges branches path
591 length edges + (if null fallback then 0 else 1)
598instance Binary Test where
601 IsCtor a b c d e -> putWord8 0 >> put a >> put b >> put c >> put d >> put e
604 IsTuple -> putWord8 3
605 IsChr a -> putWord8 4 >> put a
606 IsStr a -> putWord8 5 >> put a
607 IsInt a -> putWord8 6 >> put a
608 IsBool a -> putWord8 7 >> put a
613 0 -> liftM5 IsCtor get get get get get
620 7 -> liftM IsBool get
621 _ -> error "problem getting DecisionTree.Test binary"
624instance Binary Path where
627 Index a b -> putWord8 0 >> put a >> put b
628 Unbox a -> putWord8 1 >> put a
634 0 -> liftM2 Index get get
637 _ -> 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