1{-# OPTIONS_GHC -Wall #-}
2module Optimize.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
19import qualified Data.Text as Text
20import Data.Text (Text)
22import qualified AST.Helpers as Help
23import qualified AST.Literal as L
24import qualified AST.Pattern as P
25import qualified AST.Variable as Var
26import qualified Reporting.Annotation as A
29type CPattern = P.Canonical
36{-| Users of this module will mainly interact with this function. It takes
37some normal branches and gives out a decision tree that has "labels" at all
38the leafs and a dictionary that maps these "labels" to the code that should
41If 2 or more leaves point to the same label, we need to do some tricks in JS to
42make that work nicely. When is JS getting goto?! ;) That is outside the scope
45compile :: VariantDict -> [(CPattern, Int)] -> DecisionTree
46compile variantDict rawBranches =
48 format (pattern, index) =
49 Branch index [(Empty, pattern)]
51 toDecisionTree variantDict (map format rawBranches)
54{-| When a certain union type is defined, you specify a certain number of tags.
55This helps us do a few optimizations:
57 * If there is only one possible tag, we can always skip checking what it is.
58 Tuples are a common example of this.
59 * If we use all possible tags, we can skip doing the last test. If we have
60 checked N-1 of N tags, there is no need to test the Nth, we know its the
63So this dictionary maps tags to the number of variants that exist, so it'll
64contain things like [ ("Just", 2), ("Nothing", 2), ("_Tuple2", 1), ... ] which
65we can use for these optimizations.
68 Map.Map Var.Home (Map.Map Text Int)
79 , _edges :: [(Test, DecisionTree)]
80 , _default :: Maybe DecisionTree
86 = Constructor Var.Canonical
103add :: Path -> Path -> Path
110 error "nothing should be added to an alias path"
112 Position index subpath ->
113 Position index (add subpath finalLink)
115 Field name subpath ->
116 Field name (add subpath finalLink)
119subPositions :: Path -> [CPattern] -> [(Path, CPattern)]
120subPositions path patterns =
122 (\index pattern -> (add path (Position index Empty), pattern))
128-- ACTUALLY BUILD DECISION TREES
134 , _patterns :: [(Path, CPattern)]
138toDecisionTree :: VariantDict -> [Branch] -> DecisionTree
139toDecisionTree variantDict rawBranches =
142 map (flattenPatterns variantDict) rawBranches
144 case checkForMatch branches of
151 pickPath variantDict branches
154 gatherEdges variantDict branches path
157 map (second (toDecisionTree variantDict)) edges
159 case (decisionEdges, fallback) of
160 ([(_tag, decisionTree)], []) ->
164 Decision path decisionEdges Nothing
167 toDecisionTree variantDict fallback
170 Decision path decisionEdges (Just (toDecisionTree variantDict fallback))
173isComplete :: VariantDict -> [Test] -> Bool
174isComplete variantDict tests =
177 getArity variantDict var == length tests
179 Literal (L.Boolean _) ->
186getArity :: VariantDict -> Var.Canonical -> Int
187getArity variantDict (Var.Canonical home name) =
188 case Map.lookup name =<< Map.lookup home variantDict of
193 if Help.isTuple name then
194 read (drop 6 (Text.unpack name))
198 "Since the Optimize phase happens after canonicalization and type \
199 \inference, it is impossible that a pattern cannot be found."
206{-| Flatten type aliases and use the VariantDict to figure out when a tag is
207the only variant so we can skip doing any tests on it.
209flattenPatterns :: VariantDict -> Branch -> Branch
210flattenPatterns variantDict (Branch goal pathPatterns) =
211 Branch goal (concatMap (flatten variantDict) pathPatterns)
214flatten :: VariantDict -> (Path, CPattern) -> [(Path, CPattern)]
215flatten variantDict pathPattern@(path, A.A ann pattern) =
223 P.Alias alias realPattern ->
224 (add path Alias, A.A ann (P.Var alias))
226 flatten variantDict (path, realPattern)
231 P.Ctor tag patterns ->
232 if getArity variantDict tag == 1 then
233 concatMap (flatten variantDict) (subPositions path patterns)
246{-| If the first branch has no more "decision points" we can finally take that
247path. If that is the case we give the resulting label and a mapping from free
248variables to "how to get their value". So a pattern like (Just (x,_)) will give
249us something like ("x" => value.0.0)
251checkForMatch :: [Branch] -> Maybe Int
252checkForMatch branches =
254 Branch goal patterns : _ | all (not . needsTests . snd) patterns ->
262-- GATHER OUTGOING EDGES
265gatherEdges :: VariantDict -> [Branch] -> Path -> ([(Test, [Branch])], [Branch])
266gatherEdges variantDict branches path =
269 testsAtPath path branches
272 map (edgesFor path branches) relevantTests
275 if isComplete variantDict relevantTests then
278 filter (isIrrelevantTo path) branches
280 ( allEdges, fallbacks )
284-- FIND RELEVANT TESTS
287testsAtPath :: Path -> [Branch] -> [Test]
288testsAtPath selectedPath branches =
291 Maybe.mapMaybe (testAtPath selectedPath) branches
293 skipVisited test curr@(uniqueTests, visitedTests) =
294 if Set.member test visitedTests then
298 , Set.insert test visitedTests
301 fst (foldr skipVisited ([], Set.empty) allTests)
304testAtPath :: Path -> Branch -> Maybe Test
305testAtPath selectedPath (Branch _ pathPatterns) =
306 case List.lookup selectedPath pathPatterns of
310 Just (A.A _ pattern) ->
313 Just (Constructor name)
322 error "aliases should never reach 'testAtPath' function"
335edgesFor :: Path -> [Branch] -> Test -> (Test, [Branch])
336edgesFor path branches test =
338 , Maybe.mapMaybe (toRelevantBranch test path) branches
342toRelevantBranch :: Test -> Path -> Branch -> Maybe Branch
343toRelevantBranch test path branch@(Branch goal pathPatterns) =
344 case extract path pathPatterns of
345 Just (start, A.A _ pattern, end) ->
347 P.Ctor name patterns ->
348 if test == Constructor name then
349 Just (Branch goal (start ++ subPositions path patterns ++ end))
355 if test == Literal lit then
356 Just (Branch goal (start ++ end))
370 -> [(Path, CPattern)]
371 -> Maybe ([(Path, CPattern)], CPattern, [(Path, CPattern)])
372extract selectedPath pathPatterns =
377 first@(path, pattern) : rest ->
378 if path == selectedPath then
379 Just ([], pattern, rest)
382 case extract selectedPath rest of
386 Just (start, foundPattern, end) ->
387 Just (first : start, foundPattern, end)
391-- FIND IRRELEVANT BRANCHES
394isIrrelevantTo :: Path -> Branch -> Bool
395isIrrelevantTo selectedPath (Branch _ pathPatterns) =
396 case List.lookup selectedPath pathPatterns of
401 not (needsTests pattern)
404needsTests :: CPattern -> Bool
405needsTests (A.A _ pattern) =
414 error "aliases should never reach 'isIrrelevantTo' function"
430pickPath :: VariantDict -> [Branch] -> Path
431pickPath variantDict branches =
434 Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
436 case bests (addWeights (smallDefaults branches) allPaths) of
441 head (bests (addWeights (smallBranchingFactor variantDict branches) tiedPaths))
444isChoicePath :: (Path, CPattern) -> Maybe Path
445isChoicePath (path, pattern) =
446 if needsTests pattern then
452addWeights :: (Path -> Int) -> [Path] -> [(Path, Int)]
453addWeights toWeight paths =
454 map (\path -> (path, toWeight path)) paths
457bests :: [(Path, Int)] -> [Path]
461 error "Cannot choose the best of zero paths. This should never happen."
463 (headPath, headWeight) : weightedPaths ->
465 gatherMinimum acc@(minWeight, paths) (path, weight) =
466 if weight == minWeight then
467 (minWeight, path : paths)
469 else if weight < minWeight then
475 snd (List.foldl' gatherMinimum (headWeight, [headPath]) weightedPaths)
479-- PATH PICKING HEURISTICS
482smallDefaults :: [Branch] -> Path -> Int
483smallDefaults branches path =
484 length (filter (isIrrelevantTo path) branches)
487smallBranchingFactor :: VariantDict -> [Branch] -> Path -> Int
488smallBranchingFactor variantDict branches path =
491 gatherEdges variantDict branches path
493 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