Skip to content

Commit 3835a1f

Browse files
author
Ryan Trinkle
committed
Merge remote-tracking branch 'origin/wip-optimizer' into develop
# Conflicts: # .gitignore # src/Reflex/Class.hs # src/Reflex/Spider/Internal.hs # test/Reflex/Test/Micro.hs
2 parents 64902ea + 1ff7dbf commit 3835a1f

25 files changed

+2055
-1600
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
dist
22
cabal.sandbox.config
33
.cabal-sandbox/
4+
dist-*
45
cabal-dev
56
*.o
67
*.hi
@@ -33,3 +34,5 @@ hsenv.log
3334
.#*
3435
/shell.nix
3536
/ghci-tmp
37+
*.dump-*
38+
*.verbose-core2core

bench/RunAll.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ newtype Ignore a = Ignore a
7474
instance NFData (Ignore a) where
7575
rnf !_ = ()
7676

77-
instance NFData (SpiderEventHandle a) where
77+
instance NFData (SpiderEventHandle x a) where
7878
rnf !_ = ()
7979

8080
instance NFData (Behavior t a) where

default.nix

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
{ mkDerivation, base, bifunctors, containers, deepseq
2-
, dependent-map, dependent-sum, exception-transformers
3-
, haskell-src-exts, haskell-src-meta, hlint, lens, MemoTrie
4-
, monad-control, mtl , primitive, ref-tf, semigroupoids
5-
, semigroups, split, stdenv, stm , syb, template-haskell
6-
, these, transformers, transformers-compat
1+
{ mkDerivation, base, bifunctors, containers, data-default, deepseq
2+
, dependent-map, dependent-sum, exception-transformers, ghc
3+
, haskell-src-exts, haskell-src-meta, lens, MemoTrie, monad-control
4+
, mtl, prim-uniq, primitive, ref-tf, semigroupoids, semigroups
5+
, split, stdenv, stm, syb, template-haskell, these, transformers
6+
, transformers-compat
77
}:
88
mkDerivation {
99
pname = "reflex";
10-
version = "0.4.0";
10+
version = "0.5.0";
1111
src = builtins.filterSource (path: type: baseNameOf path != ".git") ./.;
1212
libraryHaskellDepends = [
13-
base bifunctors containers dependent-map dependent-sum
14-
exception-transformers haskell-src-exts haskell-src-meta lens
15-
MemoTrie monad-control mtl primitive ref-tf semigroupoids
16-
semigroups stm syb template-haskell these transformers
13+
base bifunctors containers data-default dependent-map dependent-sum
14+
exception-transformers ghc haskell-src-exts haskell-src-meta lens
15+
MemoTrie monad-control mtl prim-uniq primitive ref-tf semigroupoids
16+
semigroups stm syb template-haskell these transformers
1717
transformers-compat
1818
];
1919
testHaskellDepends = [
20-
base bifunctors containers deepseq dependent-map dependent-sum
21-
hlint mtl ref-tf split transformers
20+
base bifunctors containers deepseq dependent-map dependent-sum mtl
21+
ref-tf split transformers
2222
];
2323
homepage = "https://github.com/reflex-frp/reflex";
2424
description = "Higher-order Functional Reactive Programming";

reflex.cabal

+25-10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ Cabal-version: >=1.9.2
1313
homepage: https://github.com/reflex-frp/reflex
1414
bug-reports: https://github.com/reflex-frp/reflex/issues
1515

16+
flag specialize-to-spidertimeline-global
17+
description: Specialize all Reflex functions to the SpiderTimeline Global implementation. This may improve performance, but will disable all other implementations.
18+
default: False
19+
manual: True
20+
1621
library
1722
hs-source-dirs: src
1823
build-depends:
@@ -24,11 +29,13 @@ library
2429
dependent-map >= 0.2.2 && < 0.3,
2530
dependent-sum == 0.3.*,
2631
exception-transformers == 0.4.*,
32+
ghc,
2733
haskell-src-exts >= 1.16 && < 1.18,
2834
haskell-src-meta == 0.6.*,
2935
lens >= 4.7 && < 5,
3036
monad-control >= 1.0.1 && < 1.1,
3137
mtl >= 2.1 && < 2.3,
38+
prim-uniq >= 0.1.0.1 && < 0.2,
3239
primitive >= 0.5 && < 0.7,
3340
ref-tf == 0.4.*,
3441
semigroupoids >= 4.0 && < 6,
@@ -45,23 +52,29 @@ library
4552
Data.WeakBag,
4653
Reflex,
4754
Reflex.Class,
48-
Reflex.Deletable.Class,
4955
Reflex.Dynamic,
5056
Reflex.Dynamic.TH,
5157
Reflex.Dynamic.Uniq,
5258
Reflex.DynamicWriter,
59+
Reflex.FunctorMaybe,
5360
Reflex.Host.Class,
61+
Reflex.Optimizer,
62+
Reflex.Patch,
5463
Reflex.PerformEvent.Base,
5564
Reflex.PerformEvent.Class,
5665
Reflex.PostBuild.Class,
57-
Reflex.Pure,
5866
Reflex.Spider,
5967
Reflex.Spider.Internal
6068

6169
other-extensions: TemplateHaskell
6270
ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2
6371
ghc-prof-options: -auto-all
6472

73+
if flag(specialize-to-spidertimeline-global)
74+
cpp-options: -DSPECIALIZE_TO_SPIDERTIMELINE_GLOBAL
75+
else
76+
exposed-modules: Reflex.Pure
77+
6578
test-suite semantics
6679
type: exitcode-stdio-1.0
6780
main-is: semantics.hs
@@ -79,15 +92,17 @@ test-suite semantics
7992
reflex,
8093
split,
8194
transformers >= 0.3
82-
83-
test-suite hlint
84-
type: exitcode-stdio-1.0
85-
main-is: hlint.hs
86-
hs-source-dirs: test
87-
build-depends: base, hlint == 1.9.*
88-
if impl(ghcjs)
95+
if flag(specialize-to-spidertimeline-global)
8996
buildable: False
9097

98+
--test-suite hlint
99+
-- type: exitcode-stdio-1.0
100+
-- main-is: hlint.hs
101+
-- hs-source-dirs: test
102+
-- build-depends: base, hlint == 1.9.*
103+
-- if impl(ghcjs)
104+
-- buildable: False
105+
91106
benchmark spider-bench
92107
type: exitcode-stdio-1.0
93108
hs-source-dirs: bench
@@ -123,7 +138,7 @@ benchmark saulzar-bench
123138
mtl,
124139
primitive,
125140
process,
126-
ref-tf == 0.4,
141+
ref-tf,
127142
reflex,
128143
split,
129144
stm,

src/Data/Functor/Misc.hs

+56
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
{-# LANGUAGE FlexibleInstances #-}
44
{-# LANGUAGE GADTs #-}
55
{-# LANGUAGE KindSignatures #-}
6+
{-# LANGUAGE LambdaCase #-}
67
{-# LANGUAGE MultiParamTypeClasses #-}
78
{-# LANGUAGE PolyKinds #-}
89
{-# LANGUAGE RankNTypes #-}
910
{-# LANGUAGE ScopedTypeVariables #-}
1011
{-# LANGUAGE StandaloneDeriving #-}
12+
{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
1113
-- | This module provides types and functions with no particular theme, but
1214
-- which are relevant to the use of 'Functor'-based datastructures like
1315
-- 'Data.Dependent.Map.DMap'.
@@ -20,7 +22,12 @@ module Data.Functor.Misc
2022
, WrapArg (..)
2123
-- * Convenience functions for DMap
2224
, mapWithFunctorToDMap
25+
, mapKeyValuePairsMonotonic
2326
, combineDMapsWithKey
27+
, EitherTag (..)
28+
, dmapToThese
29+
, eitherToDSum
30+
, dsumToEither
2431
-- * Deprecated functions
2532
, sequenceDmap
2633
, wrapDMap
@@ -132,6 +139,55 @@ combineDMapsWithKey f mg mh = DMap.fromList $ go (DMap.toList mg) (DMap.toList m
132139
GEQ -> (gk :=> f gk (These gv hv)) : go gs' hs'
133140
GGT -> (hk :=> f hk (That hv)) : go gs hs'
134141

142+
-- | Extract the values of a 'DMap' of 'EitherTag's.
143+
dmapToThese :: DMap (EitherTag a b) Identity -> Maybe (These a b)
144+
dmapToThese m = case (DMap.lookup LeftTag m, DMap.lookup RightTag m) of
145+
(Nothing, Nothing) -> Nothing
146+
(Just (Identity a), Nothing) -> Just $ This a
147+
(Nothing, Just (Identity b)) -> Just $ That b
148+
(Just (Identity a), Just (Identity b)) -> Just $ These a b
149+
150+
-- | Tag type for 'Either' to use it as a 'DSum'.
151+
data EitherTag l r a where
152+
LeftTag :: EitherTag l r l
153+
RightTag :: EitherTag l r r
154+
155+
instance GEq (EitherTag l r) where
156+
geq a b = case (a, b) of
157+
(LeftTag, LeftTag) -> Just Refl
158+
(RightTag, RightTag) -> Just Refl
159+
_ -> Nothing
160+
161+
instance GCompare (EitherTag l r) where
162+
gcompare a b = case (a, b) of
163+
(LeftTag, LeftTag) -> GEQ
164+
(LeftTag, RightTag) -> GLT
165+
(RightTag, LeftTag) -> GGT
166+
(RightTag, RightTag) -> GEQ
167+
168+
instance GShow (EitherTag l r) where
169+
gshowsPrec _ a = case a of
170+
LeftTag -> showString "LeftTag"
171+
RightTag -> showString "RightTag"
172+
173+
instance (Show l, Show r) => ShowTag (EitherTag l r) Identity where
174+
showTaggedPrec t n (Identity a) = case t of
175+
LeftTag -> showsPrec n a
176+
RightTag -> showsPrec n a
177+
178+
-- | Convert 'Either' to a 'DSum'. Inverse of 'dsumToEither'.
179+
eitherToDSum :: Either a b -> DSum (EitherTag a b) Identity
180+
eitherToDSum = \case
181+
Left a -> (LeftTag :=> Identity a)
182+
Right b -> (RightTag :=> Identity b)
183+
184+
-- | Convert 'DSum' to 'Either'. Inverse of 'eitherToDSum'.
185+
dsumToEither :: DSum (EitherTag a b) Identity -> Either a b
186+
dsumToEither = \case
187+
(LeftTag :=> Identity a) -> Left a
188+
(RightTag :=> Identity b) -> Right b
189+
190+
135191
--------------------------------------------------------------------------------
136192
-- Deprecated functions
137193
--------------------------------------------------------------------------------

src/Data/WeakBag.hs

+22-24
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
{-# LANGUAGE BangPatterns #-}
2+
{-# LANGUAGE ExistentialQuantification #-}
3+
{-# OPTIONS_GHC -fplugin=Reflex.Optimizer #-}
14
-- | This module defines the 'WeakBag' type, which represents a mutable
25
-- collection of items that does not cause the items to be retained in memory.
36
-- This is useful for situations where a value needs to be inspected or modified
@@ -12,7 +15,6 @@ module Data.WeakBag
1215
, remove
1316
) where
1417

15-
import Control.Concurrent.STM
1618
import Control.Exception
1719
import Control.Monad hiding (forM_, mapM_)
1820
import Control.Monad.IO.Class
@@ -28,15 +30,15 @@ import Prelude hiding (mapM_, traverse)
2830
-- that is, they can still be garbage-collected. As long as the @a@s remain
2931
-- alive, the 'WeakBag' will continue to refer to them.
3032
data WeakBag a = WeakBag
31-
{ _weakBag_nextId :: {-# UNPACK #-} !(TVar Int) --TODO: what if this wraps around?
32-
, _weakBag_children :: {-# UNPACK #-} !(TVar (IntMap (Weak a)))
33+
{ _weakBag_nextId :: {-# UNPACK #-} !(IORef Int) --TODO: what if this wraps around?
34+
, _weakBag_children :: {-# UNPACK #-} !(IORef (IntMap (Weak a)))
3335
}
3436

3537
-- | When inserting an item into a 'WeakBag', a 'WeakBagTicket' is returned. If
3638
-- the caller retains the ticket, the item is guranteed to stay in memory (and
3739
-- thus in the 'WeakBag'). The ticket can also be used to remove the item from
3840
-- the 'WeakBag' prematurely (i.e. while it is still alive), using 'remove'.
39-
data WeakBagTicket a = WeakBagTicket
41+
data WeakBagTicket = forall a. WeakBagTicket
4042
{ _weakBagTicket_weakItem :: {-# UNPACK #-} !(Weak a)
4143
, _weakBagTicket_item :: {-# NOUNPACK #-} !a
4244
}
@@ -50,30 +52,24 @@ insert :: a -- ^ The item
5052
-> (b -> IO ()) -- ^ A callback to be invoked when the item is removed
5153
-- (whether automatically by the item being garbage
5254
-- collected or manually via 'remove')
53-
-> IO (WeakBagTicket a) -- ^ Returns a 'WeakBagTicket' that ensures the
54-
-- item is retained and allows the item to be
55-
-- removed.
55+
-> IO WeakBagTicket -- ^ Returns a 'WeakBagTicket' that ensures the item
56+
-- is retained and allows the item to be removed.
5657
insert a (WeakBag nextId children) wbRef finalizer = {-# SCC "insert" #-} do
5758
a' <- evaluate a
5859
wbRef' <- evaluate wbRef
59-
myId <- atomically $ do
60-
myId <- readTVar nextId
61-
writeTVar nextId $! succ myId
62-
return myId
60+
myId <- atomicModifyIORef' nextId $ \n -> (succ n, n)
6361
let cleanup = do
6462
wb <- readIORef wbRef'
6563
mb <- deRefWeak wb
6664
forM_ mb $ \b -> do
67-
isLastNode <- atomically $ do --TODO: Should this run even when mb is Nothing?
68-
cs <- readTVar children
69-
let csWithoutMe = IntMap.delete myId cs
70-
writeTVar children $! csWithoutMe
71-
return $ IntMap.size csWithoutMe == 0
72-
when isLastNode $ finalizer b
65+
csWithoutMe <- atomicModifyIORef children $ \cs ->
66+
let !csWithoutMe = IntMap.delete myId cs
67+
in (csWithoutMe, csWithoutMe)
68+
when (IntMap.null csWithoutMe) $ finalizer b
7369
return ()
7470
return ()
7571
wa <- mkWeakPtr a' $ Just cleanup
76-
atomically $ modifyTVar' children $ IntMap.insert myId wa
72+
atomicModifyIORef' children $ \cs -> (IntMap.insert myId wa cs, ())
7773
return $ WeakBagTicket
7874
{ _weakBagTicket_weakItem = wa
7975
, _weakBagTicket_item = a'
@@ -83,8 +79,8 @@ insert a (WeakBag nextId children) wbRef finalizer = {-# SCC "insert" #-} do
8379
{-# INLINE empty #-}
8480
empty :: IO (WeakBag a)
8581
empty = {-# SCC "empty" #-} do
86-
nextId <- newTVarIO 1
87-
children <- newTVarIO IntMap.empty
82+
nextId <- newIORef 1
83+
children <- newIORef IntMap.empty
8884
let bag = WeakBag
8985
{ _weakBag_nextId = nextId
9086
, _weakBag_children = children
@@ -94,7 +90,7 @@ empty = {-# SCC "empty" #-} do
9490
-- | Create a 'WeakBag' with one item; equivalent to creating the 'WeakBag' with
9591
-- 'empty', then using 'insert'.
9692
{-# INLINE singleton #-}
97-
singleton :: a -> IORef (Weak b) -> (b -> IO ()) -> IO (WeakBag a, WeakBagTicket a)
93+
singleton :: a -> IORef (Weak b) -> (b -> IO ()) -> IO (WeakBag a, WeakBagTicket)
9894
singleton a wbRef finalizer = {-# SCC "singleton" #-} do
9995
bag <- empty
10096
ticket <- insert a bag wbRef finalizer
@@ -107,14 +103,16 @@ singleton a wbRef finalizer = {-# SCC "singleton" #-} do
107103
-- is made about the order of the traversal.
108104
traverse :: MonadIO m => WeakBag a -> (a -> m ()) -> m ()
109105
traverse (WeakBag _ children) f = {-# SCC "traverse" #-} do
110-
cs <- liftIO $ readTVarIO children
106+
cs <- liftIO $ readIORef children
111107
forM_ cs $ \c -> do
112108
ma <- liftIO $ deRefWeak c
113109
mapM_ f ma
114110

115111
-- | Remove an item from the 'WeakBag'; does nothing if invoked multiple times
116112
-- on the same 'WeakBagTicket'.
117113
{-# INLINE remove #-}
118-
remove :: WeakBagTicket a -> IO ()
119-
remove = {-# SCC "remove" #-} finalize . _weakBagTicket_weakItem
114+
remove :: WeakBagTicket -> IO ()
115+
remove (WeakBagTicket w _) = {-# SCC "remove" #-} finalize w
120116
--TODO: Should 'remove' also drop the reference to the item?
117+
118+
--TODO: can/should we provide a null WeakBagTicket?

0 commit comments

Comments
 (0)