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.Set as Set
29import Data.Text (Text)
31import qualified AST.Canonical as Can
32import qualified AST.Module.Name as ModuleName
33import qualified Data.Index as Index
34import qualified Elm.Name as N
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 N.Name Index.ZeroBased Int Can.CtorOpts
86 = Index Index.ZeroBased Path
93-- ACTUALLY BUILD DECISION TREES
99 , _patterns :: [(Path, Can.Pattern)]
103toDecisionTree :: [Branch] -> DecisionTree
104toDecisionTree rawBranches =
107 map flattenPatterns rawBranches
109 case checkForMatch branches of
119 gatherEdges branches path
122 map (second toDecisionTree) edges
124 case (decisionEdges, fallback) of
125 ([(_tag, decisionTree)], []) ->
129 Decision path decisionEdges Nothing
132 toDecisionTree fallback
135 Decision path decisionEdges (Just (toDecisionTree fallback))
138isComplete :: [Test] -> Bool
141 IsCtor _ _ _ numAlts _ ->
142 numAlts == length tests
167{-| Flatten type aliases and use the VariantDict to figure out when a tag is
168the only variant so we can skip doing any tests on it.
170flattenPatterns :: Branch -> Branch
171flattenPatterns (Branch goal pathPatterns) =
172 Branch goal (foldr flatten [] pathPatterns)
175flatten :: (Path, Can.Pattern) -> [(Path, Can.Pattern)] -> [(Path, Can.Pattern)]
176flatten pathPattern@(path, A.At region pattern) otherPathPatterns =
179 pathPattern : otherPathPatterns
182 pathPattern : otherPathPatterns
184 Can.PCtor _ _ (Can.Union _ _ numAlts _) _ _ ctorArgs ->
186 case map dearg ctorArgs of
188 flatten (Unbox path, arg) otherPathPatterns
191 foldr flatten otherPathPatterns (subPositions path args)
193 pathPattern : otherPathPatterns
195 Can.PTuple a b maybeC ->
196 flatten (Index Index.first path, a) $
197 flatten (Index Index.second path, b) $
203 flatten (Index Index.third path, c) otherPathPatterns
208 Can.PAlias realPattern alias ->
209 flatten (path, realPattern) $
210 (path, A.At region (Can.PVar alias)) : otherPathPatterns
213 pathPattern : otherPathPatterns
216 pathPattern : otherPathPatterns
219 pathPattern : otherPathPatterns
222 pathPattern : otherPathPatterns
225 pathPattern : otherPathPatterns
228 pathPattern : otherPathPatterns
231subPositions :: Path -> [Can.Pattern] -> [(Path, Can.Pattern)]
232subPositions path patterns =
233 Index.indexedMap (\index pattern -> (Index index path, pattern)) patterns
236dearg :: Can.PatternCtorArg -> Can.Pattern
237dearg (Can.PatternCtorArg _ _ pattern) =
245{-| If the first branch has no more "decision points" we can finally take that
246path. If that is the case we give the resulting label and a mapping from free
247variables to "how to get their value". So a pattern like (Just (x,_)) will give
248us something like ("x" => value.0.0)
250checkForMatch :: [Branch] -> Maybe Int
251checkForMatch branches =
253 Branch goal patterns : _ | all (not . needsTests . snd) patterns ->
261-- GATHER OUTGOING EDGES
264gatherEdges :: [Branch] -> Path -> ([(Test, [Branch])], [Branch])
265gatherEdges branches path =
268 testsAtPath path branches
271 map (edgesFor path branches) relevantTests
274 if isComplete relevantTests then
277 filter (isIrrelevantTo path) branches
279 ( allEdges, fallbacks )
283-- FIND RELEVANT TESTS
286testsAtPath :: Path -> [Branch] -> [Test]
287testsAtPath selectedPath branches =
290 Maybe.mapMaybe (testAtPath selectedPath) branches
292 skipVisited test curr@(uniqueTests, visitedTests) =
293 if Set.member test visitedTests then
297 , Set.insert test visitedTests
300 fst (foldr skipVisited ([], Set.empty) allTests)
303testAtPath :: Path -> Branch -> Maybe Test
304testAtPath selectedPath (Branch _ pathPatterns) =
305 case List.lookup selectedPath pathPatterns of
309 Just (A.At _ pattern) ->
311 Can.PCtor home _ (Can.Union _ _ numAlts opts) name index _ ->
312 Just (IsCtor home name index numAlts opts)
315 Just (case ps of { [] -> IsNil ; _ -> IsCons })
345 error "aliases should never reach 'testAtPath' function"
352edgesFor :: Path -> [Branch] -> Test -> (Test, [Branch])
353edgesFor path branches test =
355 , Maybe.mapMaybe (toRelevantBranch test path) branches
359toRelevantBranch :: Test -> Path -> Branch -> Maybe Branch
360toRelevantBranch test path branch@(Branch goal pathPatterns) =
361 case extract path pathPatterns of
362 Found start (A.At region pattern) end ->
364 Can.PCtor _ _ (Can.Union _ _ numAlts _) name _ ctorArgs ->
366 IsCtor _ testName _ _ _ | name == testName ->
368 case map dearg ctorArgs of
369 [arg] | numAlts == 1 ->
370 start ++ [(Unbox path, arg)] ++ end
373 start ++ subPositions path args ++ end
381 Just (Branch goal (start ++ end))
389 let tl' = A.At region (Can.PList tl) in
390 Just (Branch goal (start ++ subPositions path [ hd, tl' ] ++ end))
398 Just (Branch goal (start ++ subPositions path [hd,tl] ++ end))
405 IsChr testChr | chr == testChr ->
406 Just (Branch goal (start ++ end))
412 IsStr testStr | str == testStr ->
413 Just (Branch goal (start ++ end))
420 IsInt testInt | int == testInt ->
421 Just (Branch goal (start ++ end))
427 Just (Branch goal (start ++ end))
429 Can.PTuple a b maybeC ->
430 Just (Branch goal (start ++ subPositions path (a : b : Maybe.maybeToList maybeC) ++ end))
450 | Found [(Path, Can.Pattern)] Can.Pattern [(Path, Can.Pattern)]
453extract :: Path -> [(Path, Can.Pattern)] -> Extract
454extract selectedPath pathPatterns =
459 first@(path, pattern) : rest ->
460 if path == selectedPath then
461 Found [] pattern rest
464 case extract selectedPath rest of
468 Found start foundPattern end ->
469 Found (first : start) foundPattern end
473-- FIND IRRELEVANT BRANCHES
476isIrrelevantTo :: Path -> Branch -> Bool
477isIrrelevantTo selectedPath (Branch _ pathPatterns) =
478 case List.lookup selectedPath pathPatterns of
483 not (needsTests pattern)
486needsTests :: Can.Pattern -> Bool
487needsTests (A.At _ pattern) =
490 Can.PAnything -> False
491 Can.PRecord _ -> False
492 Can.PCtor _ _ _ _ _ _ -> True
494 Can.PCons _ _ -> True
496 Can.PTuple _ _ _ -> True
501 error "aliases should never reach 'isIrrelevantTo' function"
509pickPath :: [Branch] -> Path
513 Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
515 case bests (addWeights (smallDefaults branches) allPaths) of
520 head (bests (addWeights (smallBranchingFactor branches) tiedPaths))
523isChoicePath :: (Path, Can.Pattern) -> Maybe Path
524isChoicePath (path, pattern) =
525 if needsTests pattern then
531addWeights :: (Path -> Int) -> [Path] -> [(Path, Int)]
532addWeights toWeight paths =
533 map (\path -> (path, toWeight path)) paths
536bests :: [(Path, Int)] -> [Path]
540 error "Cannot choose the best of zero paths. This should never happen."
542 (headPath, headWeight) : weightedPaths ->
544 gatherMinimum acc@(minWeight, paths) (path, weight) =
545 if weight == minWeight then
546 (minWeight, path : paths)
548 else if weight < minWeight then
554 snd (List.foldl' gatherMinimum (headWeight, [headPath]) weightedPaths)
558-- PATH PICKING HEURISTICS
561smallDefaults :: [Branch] -> Path -> Int
562smallDefaults branches path =
563 length (filter (isIrrelevantTo path) branches)
566smallBranchingFactor :: [Branch] -> Path -> Int
567smallBranchingFactor branches path =
570 gatherEdges branches path
572 length edges + (if null fallback then 0 else 1)
579instance Binary Test where
582 IsCtor a b c d e -> putWord8 0 >> put a >> put b >> put c >> put d >> put e
585 IsTuple -> putWord8 3
586 IsChr a -> putWord8 4 >> put a
587 IsStr a -> putWord8 5 >> put a
588 IsInt a -> putWord8 6 >> put a
593 0 -> liftM5 IsCtor get get get get get
600 _ -> error "problem getting DecisionTree.Test binary"
603instance Binary Path where
606 Index a b -> putWord8 0 >> put a >> put b
607 Unbox a -> putWord8 1 >> put a
613 0 -> liftM2 Index get get
616 _ -> 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