1{-# OPTIONS_GHC -Wall #-}
2module Optimize.Patterns.DecisionTree where
3{- To learn more about how this works, definitely read through:
5 "When Do Match-Compilation Heuristics Matter?"
7by Kevin Scott and Norman Ramsey. The rough idea is that we start with a simple
8list of patterns and expressions, and then turn that into a "decision tree"
9that requires as few tests as possible to make it to a leaf. Read the paper, it
10explains this extraordinarily well! We are currently using the same heuristics
11as SML/NJ to get nice trees.
14import Control.Arrow (second)
15import qualified Data.List as List
16import qualified Data.Map as Map
17import qualified Data.Set as Set
18import qualified Data.Maybe as Maybe
20import qualified AST.Helpers as Help
21import qualified AST.Literal as L
22import qualified AST.Pattern as P
23import qualified AST.Variable as Var
24import qualified Reporting.Annotation as A
27type CPattern = P.CanonicalPattern
32{-| Users of this module will mainly interact with this function. It takes
33some normal branches and gives out a decision tree that has "labels" at all
34the leafs and a dictionary that maps these "labels" to the code that should
37If 2 or more leaves point to the same label, we need to do some tricks in JS to
38make that work nicely. When is JS getting goto?! ;) That is outside the scope
41compile :: VariantDict -> [(CPattern, Int)] -> DecisionTree
42compile variantDict rawBranches =
44 format (pattern, index) =
45 Branch index [(Empty, pattern)]
47 toDecisionTree variantDict (map format rawBranches)
50{-| When a certain union type is defined, you specify a certain number of tags.
51This helps us do a few optimizations:
53 * If there is only one possible tag, we can always skip checking what it is.
54 Tuples are a common example of this.
55 * If we use all possible tags, we can skip doing the last test. If we have
56 checked N-1 of N tags, there is no need to test the Nth, we know its the
59So this dictionary maps tags to the number of variants that exist, so it'll
60contain things like [ ("Just", 2), ("Nothing", 2), ("_Tuple2", 1), ... ] which
61we can use for these optimizations.
64 Map.Map Var.Home (Map.Map String Int)
73 , _edges :: [(Test, DecisionTree)]
74 , _default :: Maybe DecisionTree
80 = Constructor Var.Canonical
95add :: Path -> Path -> Path
102 error "nothing should be added to an alias path"
104 Position index subpath ->
105 Position index (add subpath finalLink)
107 Field name subpath ->
108 Field name (add subpath finalLink)
111subPositions :: Path -> [CPattern] -> [(Path, CPattern)]
112subPositions path patterns =
114 (\index pattern -> (add path (Position index Empty), pattern))
119-- ACTUALLY BUILD DECISION TREES
124 , _patterns :: [(Path, CPattern)]
128toDecisionTree :: VariantDict -> [Branch] -> DecisionTree
129toDecisionTree variantDict rawBranches =
132 map (flattenPatterns variantDict) rawBranches
134 case checkForMatch branches of
141 pickPath variantDict branches
144 gatherEdges variantDict branches path
147 map (second (toDecisionTree variantDict)) edges
149 case (decisionEdges, fallback) of
150 ([(_tag, decisionTree)], []) ->
154 Decision path decisionEdges Nothing
157 toDecisionTree variantDict fallback
160 Decision path decisionEdges (Just (toDecisionTree variantDict fallback))
163isComplete :: VariantDict -> [Test] -> Bool
164isComplete variantDict tests =
167 getArity variantDict var == length tests
169 Literal (L.Boolean _) ->
176getArity :: VariantDict -> Var.Canonical -> Int
177getArity variantDict (Var.Canonical home name) =
178 case Map.lookup name =<< Map.lookup home variantDict of
183 if Help.isTuple name then
188 "Since the Optimize phase happens after canonicalization and type \
189 \inference, it is impossible that a pattern cannot be found."
194{-| Flatten type aliases and use the VariantDict to figure out when a tag is
195the only variant so we can skip doing any tests on it.
197flattenPatterns :: VariantDict -> Branch -> Branch
198flattenPatterns variantDict (Branch goal pathPatterns) =
199 Branch goal (concatMap (flatten variantDict) pathPatterns)
202flatten :: VariantDict -> (Path, CPattern) -> [(Path, CPattern)]
203flatten variantDict pathPattern@(path, A.A ann pattern) =
211 P.Alias alias realPattern ->
212 [ (add path Alias, A.A ann (P.Var alias))
213 , (path, realPattern)
219 P.Data tag patterns ->
220 if getArity variantDict tag == 1 then
221 concatMap (flatten variantDict) (subPositions path patterns)
232{-| If the first branch has no more "decision points" we can finally take that
233path. If that is the case we give the resulting label and a mapping from free
234variables to "how to get their value". So a pattern like (Just (x,_)) will give
235us something like ("x" => value.0.0)
237checkForMatch :: [Branch] -> Maybe Int
238checkForMatch branches =
240 Branch goal patterns : _ | all (not . needsTests . snd) patterns ->
247-- GATHER OUTGOING EDGES
249gatherEdges :: VariantDict -> [Branch] -> Path -> ([(Test, [Branch])], [Branch])
250gatherEdges variantDict branches path =
253 testsAtPath path branches
256 map (edgesFor path branches) relevantTests
258 if isComplete variantDict relevantTests then
260 , snd (last allRawEdges)
265 , filter (isIrrelevantTo path) branches
269-- FIND RELEVANT TESTS
271testsAtPath :: Path -> [Branch] -> [Test]
272testsAtPath selectedPath branches =
275 Maybe.mapMaybe (testAtPath selectedPath) branches
277 skipVisited test curr@(uniqueTests, visitedTests) =
278 if Set.member test visitedTests then
282 , Set.insert test visitedTests
285 fst (foldr skipVisited ([], Set.empty) allTests)
288testAtPath :: Path -> Branch -> Maybe Test
289testAtPath selectedPath (Branch _ pathPatterns) =
290 case List.lookup selectedPath pathPatterns of
294 Just (A.A _ pattern) ->
297 Just (Constructor name)
306 error "aliases should never reach 'testAtPath' function"
317edgesFor :: Path -> [Branch] -> Test -> (Test, [Branch])
318edgesFor path branches test =
320 , Maybe.mapMaybe (toRelevantBranch test path) branches
324toRelevantBranch :: Test -> Path -> Branch -> Maybe Branch
325toRelevantBranch test path branch@(Branch goal pathPatterns) =
326 case extract path pathPatterns of
327 Just (start, A.A _ pattern, end) ->
329 P.Data name patterns ->
330 if test == Constructor name then
331 Just (Branch goal (start ++ subPositions path patterns ++ end))
337 if test == Literal lit then
338 Just (Branch goal (start ++ end))
352 -> [(Path, CPattern)]
353 -> Maybe ([(Path, CPattern)], CPattern, [(Path, CPattern)])
354extract selectedPath pathPatterns =
359 first@(path, pattern) : rest ->
360 if path == selectedPath then
361 Just ([], pattern, rest)
364 case extract selectedPath rest of
368 Just (start, foundPattern, end) ->
369 Just (first : start, foundPattern, end)
372-- FIND IRRELEVANT BRANCHES
374isIrrelevantTo :: Path -> Branch -> Bool
375isIrrelevantTo selectedPath (Branch _ pathPatterns) =
376 case List.lookup selectedPath pathPatterns of
381 not (needsTests pattern)
384needsTests :: CPattern -> Bool
385needsTests (A.A _ pattern) =
394 error "aliases should never reach 'isIrrelevantTo' function"
408pickPath :: VariantDict -> [Branch] -> Path
409pickPath variantDict branches =
412 Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
414 case bests (addWeights (smallDefaults branches) allPaths) of
419 head (bests (addWeights (smallBranchingFactor variantDict branches) tiedPaths))
422isChoicePath :: (Path, CPattern) -> Maybe Path
423isChoicePath (path, pattern) =
424 if needsTests pattern then
430addWeights :: (Path -> Int) -> [Path] -> [(Path, Int)]
431addWeights toWeight paths =
432 map (\path -> (path, toWeight path)) paths
435bests :: [(Path, Int)] -> [Path]
436bests ((path,weight):weightedPaths) =
438 gatherMinimum acc@(minWeight, paths) (path, weight) =
439 if weight == minWeight then
440 (minWeight, path : paths)
442 else if weight < minWeight then
448 snd (List.foldl' gatherMinimum (weight, [path]) weightedPaths)
451-- PATH PICKING HEURISTICS
453smallDefaults :: [Branch] -> Path -> Int
454smallDefaults branches path =
455 length (filter (isIrrelevantTo path) branches)
458smallBranchingFactor :: VariantDict -> [Branch] -> Path -> Int
459smallBranchingFactor variantDict branches path =
462 gatherEdges variantDict branches path
464 length edges + (if null fallback then 0 else 1)
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