summaryrefslogtreecommitdiff
path: root/compiler/cmm/CmmSwitch.hs
diff options
context:
space:
mode:
authorSylvain Henry <sylvain@haskus.fr>2020-01-07 02:44:39 +0100
committerMarge Bot <ben+marge-bot@smart-cactus.org>2020-01-25 05:22:20 -0500
commit6e2d9ee25bce06ae51d2f1cf8df4f7422106a383 (patch)
tree4bb0aa9527bc0bed4fb2e991eb02d0f031d514bf /compiler/cmm/CmmSwitch.hs
parentc3fde723633d1788e4ded8c6f59eb7cef1ae95fd (diff)
downloadhaskell-6e2d9ee25bce06ae51d2f1cf8df4f7422106a383.tar.gz
Module hierarchy: Cmm (cf #13009)
Diffstat (limited to 'compiler/cmm/CmmSwitch.hs')
-rw-r--r--compiler/cmm/CmmSwitch.hs500
1 files changed, 0 insertions, 500 deletions
diff --git a/compiler/cmm/CmmSwitch.hs b/compiler/cmm/CmmSwitch.hs
deleted file mode 100644
index 26bf5c4ce9..0000000000
--- a/compiler/cmm/CmmSwitch.hs
+++ /dev/null
@@ -1,500 +0,0 @@
-{-# LANGUAGE GADTs #-}
-module CmmSwitch (
- SwitchTargets,
- mkSwitchTargets,
- switchTargetsCases, switchTargetsDefault, switchTargetsRange, switchTargetsSigned,
- mapSwitchTargets, switchTargetsToTable, switchTargetsFallThrough,
- switchTargetsToList, eqSwitchTargetWith,
-
- SwitchPlan(..),
- targetSupportsSwitch,
- createSwitchPlan,
- ) where
-
-import GhcPrelude
-
-import Outputable
-import DynFlags
-import Hoopl.Label (Label)
-
-import Data.Maybe
-import Data.List (groupBy)
-import Data.Function (on)
-import qualified Data.Map as M
-
--- Note [Cmm Switches, the general plan]
--- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
---
--- Compiling a high-level switch statement, as it comes out of a STG case
--- expression, for example, allows for a surprising amount of design decisions.
--- Therefore, we cleanly separated this from the Stg → Cmm transformation, as
--- well as from the actual code generation.
---
--- The overall plan is:
--- * The Stg → Cmm transformation creates a single `SwitchTargets` in
--- emitSwitch and emitCmmLitSwitch in GHC.StgToCmm/Utils.hs.
--- At this stage, they are unsuitable for code generation.
--- * A dedicated Cmm transformation (CmmImplementSwitchPlans) replaces these
--- switch statements with code that is suitable for code generation, i.e.
--- a nice balanced tree of decisions with dense jump tables in the leafs.
--- The actual planning of this tree is performed in pure code in createSwitchPlan
--- in this module. See Note [createSwitchPlan].
--- * The actual code generation will not do any further processing and
--- implement each CmmSwitch with a jump tables.
---
--- When compiling to LLVM or C, CmmImplementSwitchPlans leaves the switch
--- statements alone, as we can turn a SwitchTargets value into a nice
--- switch-statement in LLVM resp. C, and leave the rest to the compiler.
---
--- See Note [CmmSwitch vs. CmmImplementSwitchPlans] why the two module are
--- separated.
-
------------------------------------------------------------------------------
--- Note [Magic Constants in CmmSwitch]
---
--- There are a lot of heuristics here that depend on magic values where it is
--- hard to determine the "best" value (for whatever that means). These are the
--- magic values:
-
--- | Number of consecutive default values allowed in a jump table. If there are
--- more of them, the jump tables are split.
---
--- Currently 7, as it costs 7 words of additional code when a jump table is
--- split (at least on x64, determined experimentally).
-maxJumpTableHole :: Integer
-maxJumpTableHole = 7
-
--- | Minimum size of a jump table. If the number is smaller, the switch is
--- implemented using conditionals.
--- Currently 5, because an if-then-else tree of 4 values is nice and compact.
-minJumpTableSize :: Int
-minJumpTableSize = 5
-
--- | Minimum non-zero offset for a jump table. See Note [Jump Table Offset].
-minJumpTableOffset :: Integer
-minJumpTableOffset = 2
-
-
------------------------------------------------------------------------------
--- Switch Targets
-
--- Note [SwitchTargets]:
--- ~~~~~~~~~~~~~~~~~~~~~
---
--- The branches of a switch are stored in a SwitchTargets, which consists of an
--- (optional) default jump target, and a map from values to jump targets.
---
--- If the default jump target is absent, the behaviour of the switch outside the
--- values of the map is undefined.
---
--- We use an Integer for the keys the map so that it can be used in switches on
--- unsigned as well as signed integers.
---
--- The map may be empty (we prune out-of-range branches here, so it could be us
--- emptying it).
---
--- Before code generation, the table needs to be brought into a form where all
--- entries are non-negative, so that it can be compiled into a jump table.
--- See switchTargetsToTable.
-
-
--- | A value of type SwitchTargets contains the alternatives for a 'CmmSwitch'
--- value, and knows whether the value is signed, the possible range, an
--- optional default value and a map from values to jump labels.
-data SwitchTargets =
- SwitchTargets
- Bool -- Signed values
- (Integer, Integer) -- Range
- (Maybe Label) -- Default value
- (M.Map Integer Label) -- The branches
- deriving (Show, Eq)
-
--- | The smart constructor mkSwitchTargets normalises the map a bit:
--- * No entries outside the range
--- * No entries equal to the default
--- * No default if all elements have explicit values
-mkSwitchTargets :: Bool -> (Integer, Integer) -> Maybe Label -> M.Map Integer Label -> SwitchTargets
-mkSwitchTargets signed range@(lo,hi) mbdef ids
- = SwitchTargets signed range mbdef' ids'
- where
- ids' = dropDefault $ restrict ids
- mbdef' | defaultNeeded = mbdef
- | otherwise = Nothing
-
- -- Drop entries outside the range, if there is a range
- restrict = restrictMap (lo,hi)
-
- -- Drop entries that equal the default, if there is a default
- dropDefault | Just l <- mbdef = M.filter (/= l)
- | otherwise = id
-
- -- Check if the default is still needed
- defaultNeeded = fromIntegral (M.size ids') /= hi-lo+1
-
-
--- | Changes all labels mentioned in the SwitchTargets value
-mapSwitchTargets :: (Label -> Label) -> SwitchTargets -> SwitchTargets
-mapSwitchTargets f (SwitchTargets signed range mbdef branches)
- = SwitchTargets signed range (fmap f mbdef) (fmap f branches)
-
--- | Returns the list of non-default branches of the SwitchTargets value
-switchTargetsCases :: SwitchTargets -> [(Integer, Label)]
-switchTargetsCases (SwitchTargets _ _ _ branches) = M.toList branches
-
--- | Return the default label of the SwitchTargets value
-switchTargetsDefault :: SwitchTargets -> Maybe Label
-switchTargetsDefault (SwitchTargets _ _ mbdef _) = mbdef
-
--- | Return the range of the SwitchTargets value
-switchTargetsRange :: SwitchTargets -> (Integer, Integer)
-switchTargetsRange (SwitchTargets _ range _ _) = range
-
--- | Return whether this is used for a signed value
-switchTargetsSigned :: SwitchTargets -> Bool
-switchTargetsSigned (SwitchTargets signed _ _ _) = signed
-
--- | switchTargetsToTable creates a dense jump table, usable for code generation.
---
--- Also returns an offset to add to the value; the list is 0-based on the
--- result of that addition.
---
--- The conversion from Integer to Int is a bit of a wart, as the actual
--- scrutinee might be an unsigned word, but it just works, due to wrap-around
--- arithmetic (as verified by the CmmSwitchTest test case).
-switchTargetsToTable :: SwitchTargets -> (Int, [Maybe Label])
-switchTargetsToTable (SwitchTargets _ (lo,hi) mbdef branches)
- = (fromIntegral (-start), [ labelFor i | i <- [start..hi] ])
- where
- labelFor i = case M.lookup i branches of Just l -> Just l
- Nothing -> mbdef
- start | lo >= 0 && lo < minJumpTableOffset = 0 -- See Note [Jump Table Offset]
- | otherwise = lo
-
--- Note [Jump Table Offset]
--- ~~~~~~~~~~~~~~~~~~~~~~~~
---
--- Usually, the code for a jump table starting at x will first subtract x from
--- the value, to avoid a large amount of empty entries. But if x is very small,
--- the extra entries are no worse than the subtraction in terms of code size, and
--- not having to do the subtraction is quicker.
---
--- I.e. instead of
--- _u20N:
--- leaq -1(%r14),%rax
--- jmp *_n20R(,%rax,8)
--- _n20R:
--- .quad _c20p
--- .quad _c20q
--- do
--- _u20N:
--- jmp *_n20Q(,%r14,8)
---
--- _n20Q:
--- .quad 0
--- .quad _c20p
--- .quad _c20q
--- .quad _c20r
-
--- | The list of all labels occurring in the SwitchTargets value.
-switchTargetsToList :: SwitchTargets -> [Label]
-switchTargetsToList (SwitchTargets _ _ mbdef branches)
- = maybeToList mbdef ++ M.elems branches
-
--- | Groups cases with equal targets, suitable for pretty-printing to a
--- c-like switch statement with fall-through semantics.
-switchTargetsFallThrough :: SwitchTargets -> ([([Integer], Label)], Maybe Label)
-switchTargetsFallThrough (SwitchTargets _ _ mbdef branches) = (groups, mbdef)
- where
- groups = map (\xs -> (map fst xs, snd (head xs))) $
- groupBy ((==) `on` snd) $
- M.toList branches
-
--- | Custom equality helper, needed for "CmmCommonBlockElim"
-eqSwitchTargetWith :: (Label -> Label -> Bool) -> SwitchTargets -> SwitchTargets -> Bool
-eqSwitchTargetWith eq (SwitchTargets signed1 range1 mbdef1 ids1) (SwitchTargets signed2 range2 mbdef2 ids2) =
- signed1 == signed2 && range1 == range2 && goMB mbdef1 mbdef2 && goList (M.toList ids1) (M.toList ids2)
- where
- goMB Nothing Nothing = True
- goMB (Just l1) (Just l2) = l1 `eq` l2
- goMB _ _ = False
- goList [] [] = True
- goList ((i1,l1):ls1) ((i2,l2):ls2) = i1 == i2 && l1 `eq` l2 && goList ls1 ls2
- goList _ _ = False
-
------------------------------------------------------------------------------
--- Code generation for Switches
-
-
--- | A SwitchPlan abstractly describes how a Switch statement ought to be
--- implemented. See Note [createSwitchPlan]
-data SwitchPlan
- = Unconditionally Label
- | IfEqual Integer Label SwitchPlan
- | IfLT Bool Integer SwitchPlan SwitchPlan
- | JumpTable SwitchTargets
- deriving Show
---
--- Note [createSwitchPlan]
--- ~~~~~~~~~~~~~~~~~~~~~~~
---
--- A SwitchPlan describes how a Switch statement is to be broken down into
--- smaller pieces suitable for code generation.
---
--- createSwitchPlan creates such a switch plan, in these steps:
--- 1. It splits the switch statement at segments of non-default values that
--- are too large. See splitAtHoles and Note [Magic Constants in CmmSwitch]
--- 2. Too small jump tables should be avoided, so we break up smaller pieces
--- in breakTooSmall.
--- 3. We fill in the segments between those pieces with a jump to the default
--- label (if there is one), returning a SeparatedList in mkFlatSwitchPlan
--- 4. We find and replace two less-than branches by a single equal-to-test in
--- findSingleValues
--- 5. The thus collected pieces are assembled to a balanced binary tree.
-
-{-
- Note [Two alts + default]
- ~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Discussion and a bit more info at #14644
-
-When dealing with a switch of the form:
-switch(e) {
- case 1: goto l1;
- case 3000: goto l2;
- default: goto ldef;
-}
-
-If we treat it as a sparse jump table we would generate:
-
-if (e > 3000) //Check if value is outside of the jump table.
- goto ldef;
-else {
- if (e < 3000) { //Compare to upper value
- if(e != 1) //Compare to remaining value
- goto ldef;
- else
- goto l2;
- }
- else
- goto l1;
-}
-
-Instead we special case this to :
-
-if (e==1) goto l1;
-else if (e==3000) goto l2;
-else goto l3;
-
-This means we have:
-* Less comparisons for: 1,<3000
-* Unchanged for 3000
-* One more for >3000
-
-This improves code in a few ways:
-* One comparison less means smaller code which helps with cache.
-* It exchanges a taken jump for two jumps no taken in the >range case.
- Jumps not taken are cheaper (See Agner guides) making this about as fast.
-* For all other cases the first range check is removed making it faster.
-
-The end result is that the change is not measurably slower for the case
->3000 and faster for the other cases.
-
-This makes running this kind of match in an inner loop cheaper by 10-20%
-depending on the data.
-In nofib this improves wheel-sieve1 by 4-9% depending on problem
-size.
-
-We could also add a second conditional jump after the comparison to
-keep the range check like this:
- cmp 3000, rArgument
- jg <default>
- je <branch 2>
-While this is fairly cheap it made no big difference for the >3000 case
-and slowed down all other cases making it not worthwhile.
--}
-
-
--- | Does the target support switch out of the box? Then leave this to the
--- target!
-targetSupportsSwitch :: HscTarget -> Bool
-targetSupportsSwitch HscC = True
-targetSupportsSwitch HscLlvm = True
-targetSupportsSwitch _ = False
-
--- | This function creates a SwitchPlan from a SwitchTargets value, breaking it
--- down into smaller pieces suitable for code generation.
-createSwitchPlan :: SwitchTargets -> SwitchPlan
--- Lets do the common case of a singleton map quickly and efficiently (#10677)
-createSwitchPlan (SwitchTargets _signed _range (Just defLabel) m)
- | [(x, l)] <- M.toList m
- = IfEqual x l (Unconditionally defLabel)
--- And another common case, matching "booleans"
-createSwitchPlan (SwitchTargets _signed (lo,hi) Nothing m)
- | [(x1, l1), (_x2,l2)] <- M.toAscList m
- --Checking If |range| = 2 is enough if we have two unique literals
- , hi - lo == 1
- = IfEqual x1 l1 (Unconditionally l2)
--- See Note [Two alts + default]
-createSwitchPlan (SwitchTargets _signed _range (Just defLabel) m)
- | [(x1, l1), (x2,l2)] <- M.toAscList m
- = IfEqual x1 l1 (IfEqual x2 l2 (Unconditionally defLabel))
-createSwitchPlan (SwitchTargets signed range mbdef m) =
- -- pprTrace "createSwitchPlan" (text (show ids) $$ text (show (range,m)) $$ text (show pieces) $$ text (show flatPlan) $$ text (show plan)) $
- plan
- where
- pieces = concatMap breakTooSmall $ splitAtHoles maxJumpTableHole m
- flatPlan = findSingleValues $ mkFlatSwitchPlan signed mbdef range pieces
- plan = buildTree signed $ flatPlan
-
-
----
---- Step 1: Splitting at large holes
----
-splitAtHoles :: Integer -> M.Map Integer a -> [M.Map Integer a]
-splitAtHoles _ m | M.null m = []
-splitAtHoles holeSize m = map (\range -> restrictMap range m) nonHoles
- where
- holes = filter (\(l,h) -> h - l > holeSize) $ zip (M.keys m) (tail (M.keys m))
- nonHoles = reassocTuples lo holes hi
-
- (lo,_) = M.findMin m
- (hi,_) = M.findMax m
-
----
---- Step 2: Avoid small jump tables
----
--- We do not want jump tables below a certain size. This breaks them up
--- (into singleton maps, for now).
-breakTooSmall :: M.Map Integer a -> [M.Map Integer a]
-breakTooSmall m
- | M.size m > minJumpTableSize = [m]
- | otherwise = [M.singleton k v | (k,v) <- M.toList m]
-
----
---- Step 3: Fill in the blanks
----
-
--- | A FlatSwitchPlan is a list of SwitchPlans, with an integer inbetween every
--- two entries, dividing the range.
--- So if we have (abusing list syntax) [plan1,n,plan2], then we use plan1 if
--- the expression is < n, and plan2 otherwise.
-
-type FlatSwitchPlan = SeparatedList Integer SwitchPlan
-
-mkFlatSwitchPlan :: Bool -> Maybe Label -> (Integer, Integer) -> [M.Map Integer Label] -> FlatSwitchPlan
-
--- If we have no default (i.e. undefined where there is no entry), we can
--- branch at the minimum of each map
-mkFlatSwitchPlan _ Nothing _ [] = pprPanic "mkFlatSwitchPlan with nothing left to do" empty
-mkFlatSwitchPlan signed Nothing _ (m:ms)
- = (mkLeafPlan signed Nothing m , [ (fst (M.findMin m'), mkLeafPlan signed Nothing m') | m' <- ms ])
-
--- If we have a default, we have to interleave segments that jump
--- to the default between the maps
-mkFlatSwitchPlan signed (Just l) r ms = let ((_,p1):ps) = go r ms in (p1, ps)
- where
- go (lo,hi) []
- | lo > hi = []
- | otherwise = [(lo, Unconditionally l)]
- go (lo,hi) (m:ms)
- | lo < min
- = (lo, Unconditionally l) : go (min,hi) (m:ms)
- | lo == min
- = (lo, mkLeafPlan signed (Just l) m) : go (max+1,hi) ms
- | otherwise
- = pprPanic "mkFlatSwitchPlan" (integer lo <+> integer min)
- where
- min = fst (M.findMin m)
- max = fst (M.findMax m)
-
-
-mkLeafPlan :: Bool -> Maybe Label -> M.Map Integer Label -> SwitchPlan
-mkLeafPlan signed mbdef m
- | [(_,l)] <- M.toList m -- singleton map
- = Unconditionally l
- | otherwise
- = JumpTable $ mkSwitchTargets signed (min,max) mbdef m
- where
- min = fst (M.findMin m)
- max = fst (M.findMax m)
-
----
---- Step 4: Reduce the number of branches using ==
----
-
--- A sequence of three unconditional jumps, with the outer two pointing to the
--- same value and the bounds off by exactly one can be improved
-findSingleValues :: FlatSwitchPlan -> FlatSwitchPlan
-findSingleValues (Unconditionally l, (i, Unconditionally l2) : (i', Unconditionally l3) : xs)
- | l == l3 && i + 1 == i'
- = findSingleValues (IfEqual i l2 (Unconditionally l), xs)
-findSingleValues (p, (i,p'):xs)
- = (p,i) `consSL` findSingleValues (p', xs)
-findSingleValues (p, [])
- = (p, [])
-
----
---- Step 5: Actually build the tree
----
-
--- Build a balanced tree from a separated list
-buildTree :: Bool -> FlatSwitchPlan -> SwitchPlan
-buildTree _ (p,[]) = p
-buildTree signed sl = IfLT signed m (buildTree signed sl1) (buildTree signed sl2)
- where
- (sl1, m, sl2) = divideSL sl
-
-
-
---
--- Utility data type: Non-empty lists with extra markers in between each
--- element:
---
-
-type SeparatedList b a = (a, [(b,a)])
-
-consSL :: (a, b) -> SeparatedList b a -> SeparatedList b a
-consSL (a, b) (a', xs) = (a, (b,a'):xs)
-
-divideSL :: SeparatedList b a -> (SeparatedList b a, b, SeparatedList b a)
-divideSL (_,[]) = error "divideSL: Singleton SeparatedList"
-divideSL (p,xs) = ((p, xs1), m, (p', xs2))
- where
- (xs1, (m,p'):xs2) = splitAt (length xs `div` 2) xs
-
---
--- Other Utilities
---
-
-restrictMap :: (Integer,Integer) -> M.Map Integer b -> M.Map Integer b
-restrictMap (lo,hi) m = mid
- where (_, mid_hi) = M.split (lo-1) m
- (mid, _) = M.split (hi+1) mid_hi
-
--- for example: reassocTuples a [(b,c),(d,e)] f == [(a,b),(c,d),(e,f)]
-reassocTuples :: a -> [(a,a)] -> a -> [(a,a)]
-reassocTuples initial [] last
- = [(initial,last)]
-reassocTuples initial ((a,b):tuples) last
- = (initial,a) : reassocTuples b tuples last
-
--- Note [CmmSwitch vs. CmmImplementSwitchPlans]
--- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--- I (Joachim) separated the two somewhat closely related modules
---
--- - CmmSwitch, which provides the CmmSwitchTargets type and contains the strategy
--- for implementing a Cmm switch (createSwitchPlan), and
--- - CmmImplementSwitchPlans, which contains the actual Cmm graph modification,
---
--- for these reasons:
---
--- * CmmSwitch is very low in the dependency tree, i.e. does not depend on any
--- GHC specific modules at all (with the exception of Output and Hoople
--- (Literal)). CmmImplementSwitchPlans is the Cmm transformation and hence very
--- high in the dependency tree.
--- * CmmSwitch provides the CmmSwitchTargets data type, which is abstract, but
--- used in CmmNodes.
--- * Because CmmSwitch is low in the dependency tree, the separation allows
--- for more parallelism when building GHC.
--- * The interaction between the modules is very explicit and easy to
--- understand, due to the small and simple interface.