-
Notifications
You must be signed in to change notification settings - Fork 570
Optimize pass-through cases #2057
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
no-longer-on-githu-b
wants to merge
18
commits into
purescript:master
from
no-longer-on-githu-b:master
Closed
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
00c75ec
Optimize pass-through cases
no-longer-on-githu-b 0c7cfb4
Make dissectConstruction function less trigger-happy
no-longer-on-githu-b 678e9f6
Use Test.Assert in PassThroughCases optimization test
no-longer-on-githu-b 67fdb7b
Add myself to the contributors list
no-longer-on-githu-b d70db25
Put CoreFn optimizations in a list so that new ones can be added late…
no-longer-on-githu-b 81e1da7
Explain why PassThroughCases is a safe optimization and why it's perf…
no-longer-on-githu-b badbf0a
Optimize dissectConstruction function so it is tail-recursive and sin…
no-longer-on-githu-b 71ceecf
Use fresh names instead of "v" in PassThroughCases optimization to pr…
no-longer-on-githu-b 34782bd
Remove shadowing of variables from everywhereOnValuesM implementation
no-longer-on-githu-b 41de3c0
Prevent PassThroughCases from optimizing code like "A x -> A M.x"
no-longer-on-githu-b 329a5c4
Add test for PassThroughCase with code like "A x -> A M.x"
no-longer-on-githu-b 1f6665b
Compare modules of data ctors in PassThroughCases
no-longer-on-githu-b bf810e7
Rename same to refEq in test support library
no-longer-on-githu-b 2346660
Minor code style issue as per @garyb's comment
no-longer-on-githu-b 9475549
Clean up implementation of handleCaseAlternative in everywhereOnValue…
no-longer-on-githu-b 76739c8
Optimize nested expressions in PassThroughCases optimization
no-longer-on-githu-b a62cc7e
Update PassThroughCases optimization for v0.9.1
no-longer-on-githu-b e7ff8d8
Eliminate unused imports
no-longer-on-githu-b File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| 'use strict'; | ||
|
|
||
| exports.refEq = function(a) { | ||
| return function(b) { | ||
| return function() { | ||
| return a === b; | ||
| }; | ||
| }; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| module Main where | ||
|
|
||
| import Control.Monad.Eff (Eff) | ||
| import Control.Monad.Eff.Console (CONSOLE, log) | ||
| import Data.Maybe as Maybe | ||
| import Prelude | ||
| import Test.Assert (ASSERT, assert) | ||
|
|
||
| foreign import refEq :: forall eff a b. a -> b -> Eff eff Boolean | ||
|
|
||
| data T = C | D Int | E Int Int | X | ||
|
|
||
| data Maybe a = Just a | Nothing | ||
|
|
||
| main :: forall e. Eff (console :: CONSOLE, assert :: ASSERT | e) Unit | ||
| main = do | ||
| let c = C | ||
| let d = D 1 | ||
| let e = E 1 2 | ||
| let nothing = Maybe.Nothing | ||
| let just = Maybe.Just 1 | ||
|
|
||
| aEq <- refEq c (case c of | ||
| C -> C | ||
| _ -> X) | ||
| assert (aEq) | ||
|
|
||
| dEq1 <- refEq d (case d of | ||
| D x -> D x | ||
| _ -> X) | ||
| assert (dEq1) | ||
|
|
||
| dEq2 <- refEq d (case d of | ||
| D x -> D 2 | ||
| _ -> X) | ||
| assert (not dEq2) | ||
|
|
||
| eEq1 <- refEq e (case e of | ||
| E x y -> E x y | ||
| _ -> X) | ||
| assert (eEq1) | ||
|
|
||
| eEq2 <- refEq e (case e of | ||
| E x y -> E y x | ||
| _ -> X) | ||
| assert (not eEq2) | ||
|
|
||
| nothingEq <- refEq nothing (case nothing of | ||
| Maybe.Nothing -> Maybe.Nothing | ||
| _ -> Maybe.Just 1) | ||
| assert (nothingEq) | ||
|
|
||
| justEq1 <- refEq just (case just of | ||
| Maybe.Just x -> Maybe.Just x | ||
| _ -> Maybe.Nothing) | ||
| assert (justEq1) | ||
|
|
||
| justEq2 <- refEq just (case just of | ||
| Maybe.Just x -> Maybe.Just 2 | ||
| _ -> Maybe.Nothing) | ||
| assert (not justEq2) | ||
|
|
||
| let maybe = case (case just of | ||
| Maybe.Just maybe -> Maybe.Just Maybe.maybe | ||
| _ -> Maybe.Nothing) of | ||
| Maybe.Just m -> m | ||
| _ -> \x f m -> Maybe.maybe x f m | ||
| dangerousEq1 <- refEq Maybe.maybe maybe | ||
| assert (dangerousEq1) | ||
|
|
||
| dangerousEq2 <- refEq just (case just of | ||
| Maybe.Just x -> Just x | ||
| _ -> Nothing) | ||
| assert (not dangerousEq2) | ||
|
|
||
| nestedEq <- refEq just (case just of | ||
| Maybe.Just x -> let j = Maybe.Just x in j | ||
| _ -> Maybe.Nothing) | ||
| assert (nestedEq) | ||
|
|
||
| shadowEq <- refEq just (case just of | ||
| Maybe.Just x -> let x = 2 in Maybe.Just x | ||
| _ -> Maybe.Nothing) | ||
| assert (not shadowEq) | ||
|
|
||
| log "Done" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| {-# LANGUAGE FlexibleContexts #-} | ||
| module Language.PureScript.CoreFn.Optimizer (optimize) where | ||
|
|
||
| import Control.Monad.Reader (asks, MonadReader) | ||
| import Control.Monad.Supply.Class (MonadSupply) | ||
| import Language.PureScript.CoreFn.Ann (Ann) | ||
| import Language.PureScript.CoreFn.Module (Module) | ||
| import Language.PureScript.CoreFn.Optimizer.PassThroughCases | ||
| import Language.PureScript.Options (Options, optionsNoOptimizations) | ||
| import Prelude | ||
|
|
||
| -- | | ||
| -- Apply a series of optimizer passes to CoreFn | ||
| -- | ||
| optimize :: (Monad m, MonadReader Options m, MonadSupply m) | ||
| => Module Ann | ||
| -> m (Module Ann) | ||
| optimize m = do | ||
| noOpt <- asks optionsNoOptimizations | ||
| if noOpt then return m else optimize' m | ||
|
|
||
| optimize' :: (Monad m, MonadSupply m) => Module Ann -> m (Module Ann) | ||
| optimize' = go passes | ||
| where passes = [ passThroughCases | ||
| ] | ||
| go [] m = return m | ||
| go (p : ps) m = p m >>= go ps | ||
89 changes: 89 additions & 0 deletions
89
src/Language/PureScript/CoreFn/Optimizer/PassThroughCases.hs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| -- | | ||
| -- Optimizer step for simplifying "pass-through cases", for example: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please explain here why this optimization is not possible elsewhere (types) and why it is safe? |
||
| -- | ||
| -- > f (C x) = C x | ||
| -- > -- becomes | ||
| -- > f c@(C _) = c | ||
| -- | ||
| -- This optimization is safe; PureScript provides no way to compare the memory | ||
| -- location of values, so data constructor calls can be optimized out freely. | ||
| -- | ||
| -- This optimization must be performed after type checking, because it may | ||
| -- change the code in such a way that the result is ill-typed. This optimization | ||
| -- is performed before code generation, because it is common to all back-ends. | ||
| -- | ||
|
|
||
| module Language.PureScript.CoreFn.Optimizer.PassThroughCases | ||
| ( passThroughCases | ||
| ) where | ||
|
|
||
| import Control.Monad.Supply.Class (freshName, MonadSupply) | ||
| import Language.PureScript.CoreFn.Ann (Ann) | ||
| import Language.PureScript.CoreFn.Binders (Binder(..)) | ||
| import Language.PureScript.CoreFn.Expr | ||
| import Language.PureScript.CoreFn.Meta (Meta(IsConstructor)) | ||
| import Language.PureScript.CoreFn.Module (Module, moduleDecls) | ||
| import Language.PureScript.CoreFn.Traversals (everywhereOnValues, everywhereOnValuesM) | ||
| import Language.PureScript.Names | ||
| import Prelude | ||
|
|
||
| passThroughCases :: (Monad m, MonadSupply m) => Module Ann -> m (Module Ann) | ||
| passThroughCases m = (\mds -> m { moduleDecls = mds }) <$> onBinds (moduleDecls m) | ||
| where | ||
| onBinds :: (Monad m, MonadSupply m) => [Bind Ann] -> m [Bind Ann] | ||
| onBinds = let (f, _, _) = everywhereOnValuesM return onExpr return | ||
| in mapM f | ||
|
|
||
| onExpr :: (Monad m, MonadSupply m) => Expr Ann -> m (Expr Ann) | ||
| onExpr (Case ss ts cs) = Case ss ts <$> mapM onCaseAlternative cs | ||
| onExpr e = return e | ||
|
|
||
| onCaseAlternative :: (Monad m, MonadSupply m) => CaseAlternative Ann -> m (CaseAlternative Ann) | ||
| onCaseAlternative (CaseAlternative [bndr@(ConstructorBinder bndrAnn _ ctor prms)] (Right body)) = do | ||
| v <- Ident <$> freshName | ||
| let (_, comments, type_, _) = bndrAnn | ||
| vAnn = (Nothing, comments, type_, Nothing) | ||
| (_, f, _) = everywhereOnValues id (replaceReconstructions v vAnn ctor prms) id | ||
| return $ CaseAlternative [NamedBinder bndrAnn v bndr] (Right $ f body) | ||
| onCaseAlternative a = return a | ||
|
|
||
| replaceReconstructions :: Ident | ||
| -> Ann | ||
| -> Qualified (ProperName 'ConstructorName) | ||
| -> [Binder Ann] | ||
| -> Expr Ann | ||
| -> Expr Ann | ||
| replaceReconstructions v vAnn ctor prms expr | ||
| | isReconstruction ctor prms expr = Var vAnn (Qualified Nothing $ v) | ||
| | otherwise = expr | ||
|
|
||
| isReconstruction :: Qualified (ProperName 'ConstructorName) | ||
| -> [Binder Ann] | ||
| -> Expr Ann | ||
| -> Bool | ||
| isReconstruction (Qualified (Just ctorModule) ctor) prms body = | ||
| case dissectConstruction body of | ||
| Just (Qualified (Just ctorModule') ctor', args) -> | ||
| ctorModule == ctorModule' | ||
| && Ident (runProperName ctor) == ctor' | ||
| && length prms == length args | ||
| && all isBinderArg (prms `zip` args) | ||
| _ -> False | ||
| where isBinderArg (VarBinder _ i, Var _ (Qualified Nothing i')) = i == i' | ||
| isBinderArg _ = False | ||
| isReconstruction _ _ _ = False | ||
|
|
||
|
|
||
| -- | | ||
| -- Return the constructor and arguments from what looks like a data constructor | ||
| -- call. | ||
| -- | ||
| dissectConstruction :: Expr Ann -> Maybe (Qualified Ident, [Expr Ann]) | ||
| dissectConstruction e = | ||
| case unApp e of | ||
| (Var ann c, args) | isConstructor ann -> Just (c, args) | ||
| _ -> Nothing | ||
| where | ||
| isConstructor :: Ann -> Bool | ||
| isConstructor (_, _, _, Just (IsConstructor _ _)) = True | ||
| isConstructor _ = False | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please list the optimizations here, even if it's just the one for now?