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)
16import qualified Data.List as List
17import qualified Data.Map as Map
18import qualified Data.Set as Set
19import qualified Data.Maybe as Maybe
20import qualified Data.Text as Text
21import Data.Text (Text)
23import qualified AST.Helpers as Help
24import qualified AST.Literal as L
25import qualified AST.Pattern as P
26import qualified AST.Variable as Var
27import qualified Reporting.Annotation as A
30type CPattern = P.Canonical
37{-| Users of this module will mainly interact with this function. It takes
38some normal branches and gives out a decision tree that has "labels" at all
39the leafs and a dictionary that maps these "labels" to the code that should
42If 2 or more leaves point to the same label, we need to do some tricks in JS to
43make that work nicely. When is JS getting goto?! ;) That is outside the scope
46compile :: VariantDict -> [(CPattern, Int)] -> DecisionTree
47compile variantDict rawBranches =
49 format (pattern, index) =
50 Branch index [(Empty, pattern)]
52 toDecisionTree variantDict (map format rawBranches)
55{-| When a certain union type is defined, you specify a certain number of tags.
56This helps us do a few optimizations:
58 * If there is only one possible tag, we can always skip checking what it is.
59 Tuples are a common example of this.
60 * If we use all possible tags, we can skip doing the last test. If we have
61 checked N-1 of N tags, there is no need to test the Nth, we know its the
64So this dictionary maps tags to the number of variants that exist, so it'll
65contain things like [ ("Just", 2), ("Nothing", 2), ("_Tuple2", 1), ... ] which
66we can use for these optimizations.
69 Map.Map Var.Home (Map.Map Text Int)
80 , _edges :: [(Test, DecisionTree)]
81 , _default :: Maybe DecisionTree
87 = Constructor Var.Canonical
104add :: Path -> Path -> Path
111 error "nothing should be added to an alias path"
113 Position index subpath ->
114 Position index (add subpath finalLink)
116 Field name subpath ->
117 Field name (add subpath finalLink)
120subPositions :: Path -> [CPattern] -> [(Path, CPattern)]
121subPositions path patterns =
123 (\index pattern -> (add path (Position index Empty), pattern))
129-- ACTUALLY BUILD DECISION TREES
135 , _patterns :: [(Path, CPattern)]
139toDecisionTree :: VariantDict -> [Branch] -> DecisionTree
140toDecisionTree variantDict rawBranches =
143 map (flattenPatterns variantDict) rawBranches
145 case checkForMatch branches of
152 pickPath variantDict branches
155 gatherEdges variantDict branches path
158 map (second (toDecisionTree variantDict)) edges
160 case (decisionEdges, fallback) of
161 ([(_tag, decisionTree)], []) ->
165 Decision path decisionEdges Nothing
168 toDecisionTree variantDict fallback
171 Decision path decisionEdges (Just (toDecisionTree variantDict fallback))
174isComplete :: VariantDict -> [Test] -> Bool
175isComplete variantDict tests =
178 getArity variantDict var == length tests
180 Literal (L.Boolean _) ->
187getArity :: VariantDict -> Var.Canonical -> Int
188getArity variantDict (Var.Canonical home name) =
189 case Map.lookup name =<< Map.lookup home variantDict of
194 if Help.isTuple name then
195 read (drop 6 (Text.unpack name))
199 "Since the Optimize phase happens after canonicalization and type \
200 \inference, it is impossible that a pattern cannot be found."
207{-| Flatten type aliases and use the VariantDict to figure out when a tag is
208the only variant so we can skip doing any tests on it.
210flattenPatterns :: VariantDict -> Branch -> Branch
211flattenPatterns variantDict (Branch goal pathPatterns) =
212 Branch goal (concatMap (flatten variantDict) pathPatterns)
215flatten :: VariantDict -> (Path, CPattern) -> [(Path, CPattern)]
216flatten variantDict pathPattern@(path, A.A ann pattern) =
224 P.Alias alias realPattern ->
225 (add path Alias, A.A ann (P.Var alias))
227 flatten variantDict (path, realPattern)
232 P.Ctor tag patterns ->
233 if getArity variantDict tag == 1 then
234 concatMap (flatten variantDict) (subPositions path patterns)
247{-| If the first branch has no more "decision points" we can finally take that
248path. If that is the case we give the resulting label and a mapping from free
249variables to "how to get their value". So a pattern like (Just (x,_)) will give
250us something like ("x" => value.0.0)
252checkForMatch :: [Branch] -> Maybe Int
253checkForMatch branches =
255 Branch goal patterns : _ | all (not . needsTests . snd) patterns ->
263-- GATHER OUTGOING EDGES
266gatherEdges :: VariantDict -> [Branch] -> Path -> ([(Test, [Branch])], [Branch])
267gatherEdges variantDict branches path =
270 testsAtPath path branches
273 map (edgesFor path branches) relevantTests
276 if isComplete variantDict relevantTests then
279 filter (isIrrelevantTo path) branches
281 ( allEdges, fallbacks )
285-- FIND RELEVANT TESTS
288testsAtPath :: Path -> [Branch] -> [Test]
289testsAtPath selectedPath branches =
292 Maybe.mapMaybe (testAtPath selectedPath) branches
294 skipVisited test curr@(uniqueTests, visitedTests) =
295 if Set.member test visitedTests then
299 , Set.insert test visitedTests
302 fst (foldr skipVisited ([], Set.empty) allTests)
305testAtPath :: Path -> Branch -> Maybe Test
306testAtPath selectedPath (Branch _ pathPatterns) =
307 case List.lookup selectedPath pathPatterns of
311 Just (A.A _ pattern) ->
314 Just (Constructor name)
323 error "aliases should never reach 'testAtPath' function"
336edgesFor :: Path -> [Branch] -> Test -> (Test, [Branch])
337edgesFor path branches test =
339 , Maybe.mapMaybe (toRelevantBranch test path) branches
343toRelevantBranch :: Test -> Path -> Branch -> Maybe Branch
344toRelevantBranch test path branch@(Branch goal pathPatterns) =
345 case extract path pathPatterns of
346 Just (start, A.A _ pattern, end) ->
348 P.Ctor name patterns ->
349 if test == Constructor name then
350 Just (Branch goal (start ++ subPositions path patterns ++ end))
356 if test == Literal lit then
357 Just (Branch goal (start ++ end))
371 -> [(Path, CPattern)]
372 -> Maybe ([(Path, CPattern)], CPattern, [(Path, CPattern)])
373extract selectedPath pathPatterns =
378 first@(path, pattern) : rest ->
379 if path == selectedPath then
380 Just ([], pattern, rest)
383 case extract selectedPath rest of
387 Just (start, foundPattern, end) ->
388 Just (first : start, foundPattern, end)
392-- FIND IRRELEVANT BRANCHES
395isIrrelevantTo :: Path -> Branch -> Bool
396isIrrelevantTo selectedPath (Branch _ pathPatterns) =
397 case List.lookup selectedPath pathPatterns of
402 not (needsTests pattern)
405needsTests :: CPattern -> Bool
406needsTests (A.A _ pattern) =
415 error "aliases should never reach 'isIrrelevantTo' function"
431pickPath :: VariantDict -> [Branch] -> Path
432pickPath variantDict branches =
435 Maybe.mapMaybe isChoicePath (concatMap _patterns branches)
437 case bests (addWeights (smallDefaults branches) allPaths) of
442 head (bests (addWeights (smallBranchingFactor variantDict branches) tiedPaths))
445isChoicePath :: (Path, CPattern) -> Maybe Path
446isChoicePath (path, pattern) =
447 if needsTests pattern then
453addWeights :: (Path -> Int) -> [Path] -> [(Path, Int)]
454addWeights toWeight paths =
455 map (\path -> (path, toWeight path)) paths
458bests :: [(Path, Int)] -> [Path]
462 error "Cannot choose the best of zero paths. This should never happen."
464 (headPath, headWeight) : weightedPaths ->
466 gatherMinimum acc@(minWeight, paths) (path, weight) =
467 if weight == minWeight then
468 (minWeight, path : paths)
470 else if weight < minWeight then
476 snd (List.foldl' gatherMinimum (headWeight, [headPath]) weightedPaths)
480-- PATH PICKING HEURISTICS
483smallDefaults :: [Branch] -> Path -> Int
484smallDefaults branches path =
485 length (filter (isIrrelevantTo path) branches)
488smallBranchingFactor :: VariantDict -> [Branch] -> Path -> Int
489smallBranchingFactor variantDict branches path =
492 gatherEdges variantDict branches path
494 length edges + (if null fallback then 0 else 1)
501instance Binary Test where
505 0 -> Constructor <$> get
507 _ -> error "problem getting DecisionTree.Test binary"
512 putWord8 0 >> put var
515 putWord8 1 >> put literal
518instance Binary Path where
522 0 -> Position <$> get <*> get
523 1 -> Field <$> get <*> get
526 _ -> error "problem getting DecisionTree.Path binary"
530 Position index rest ->
531 putWord8 0 >> put index >> put rest
534 putWord8 1 >> put field >> put rest
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