Skip to content

Commit a9226ce

Browse files
committed
WIP: merge linear-dest back into linear-base
1 parent 919fc6c commit a9226ce

File tree

27 files changed

+203660
-30
lines changed

27 files changed

+203660
-30
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ghc*.tar.xz filter=lfs diff=lfs merge=lfs -text

.github/workflows/ci.yaml

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,47 @@ on: [push, pull_request]
33
env:
44
# Bump this number to invalidate the Github-actions cache
55
cache-invalidation-key: 0
6-
nixpkgs-url: https://github.com/NixOS/nixpkgs/archive/574d1eac1c200690e27b8eb4e24887f8df7ac27c.tar.gz
6+
NIX_PATH: https://github.com/NixOS/nixpkgs/archive/574d1eac1c200690e27b8eb4e24887f8df7ac27c.tar.gz
7+
ghc-exe: $(pwd)/ghc-dps-compact-regions-prims-702220602b/bin/ghc
8+
ghc-name: ghc-dps-compact-regions-prims-702220602b
9+
ghc-internal-name: ghc-9.11.20240917-x86_64-unknown-linux
710

811
jobs:
912
cabal-test:
10-
name: cabal test - GHC ${{ matrix.ghc-version }}
11-
strategy:
12-
matrix:
13-
ghc-version: [96, 98, 910]
14-
runs-on: ubuntu-latest
13+
name: cabal test - ${{ env.ghc-name }}
14+
runs-on: [self-hosted, Linux, X64]
1515
steps:
16-
- uses: actions/checkout@v2
17-
- uses: cachix/install-nix-action@v15
18-
with:
19-
nix_path: "${{ env.nixpkgs-url }}"
20-
- name: Cache Cabal dependencies
21-
uses: actions/cache@v2
16+
- uses: actions/checkout@v3
2217
with:
23-
path: |
24-
~/.cabal/packages
25-
~/.cabal/store
26-
dist-newstyle
27-
key: cabal-deps-${{ runner.os }}-${{ hashFiles('nix/sources.json') }}-${{ matrix.ghc-version }}-v${{ env.cache-invalidation-key }}-${{ hashFiles('linear-base.cabal') }}-${{ github.sha }}
28-
restore-keys: cabal-deps-${{ runner.os }}-${{ hashFiles('nix/sources.json') }}-${{ matrix.ghc-version }}-v${{ env.cache-invalidation-key }}-${{ hashFiles('linear-base.cabal') }}-
18+
lfs: true
19+
- name: Checkout LFS objects
20+
run: git lfs checkout
2921
- name: Build Nix dependencies
30-
run: nix-shell --arg ghcVersion '"${{ matrix.ghc-version }}"' --arg installHls 'false' --pure --run "echo '=== Nix dependencies installed ==='"
22+
run: nix-shell --arg installHls 'false' --pure --run "echo '=== Nix dependencies installed ==='"
23+
- name: Install custom GHC
24+
run: nix-shell --pure --run "rm -rf ${{ env.ghc-name }} ${{ env.ghc-internal-name }} && tar xJf ${{ env.ghc-name }}.tar.xz && mv ${{ env.ghc-internal-name }} ${{ env.ghc-name }}"
3125
- name: Init Cabal's config file
32-
run: nix-shell --arg ghcVersion '"${{ matrix.ghc-version }}"' --arg installHls 'false' --pure --run "cabal --config-file=/home/runner/.cabal/config user-config -f init"
26+
run: nix-shell --arg installHls 'false' --pure --run "cabal --config-file=$HOME/.cabal/config user-config -f init"
3327
- name: Update Cabal's database
34-
run: nix-shell --arg ghcVersion '"${{ matrix.ghc-version }}"' --arg installHls 'false' --pure --run "cabal update"
28+
run: nix-shell --arg installHls 'false' --pure --run "cabal update"
3529
- name: Build Cabal's dependencies
36-
run: nix-shell --arg ghcVersion '"${{ matrix.ghc-version }}"' --arg installHls 'false' --pure --run "cabal build --dependencies-only"
30+
run: nix-shell --arg installHls 'false' --pure --run "cabal build -w ${{ env.ghc-exe }} --dependencies-only"
3731
- name: Build
38-
run: nix-shell --arg ghcVersion '"${{ matrix.ghc-version }}"' --arg installHls 'false' --pure --run "cabal build"
32+
run: nix-shell --arg installHls 'false' --pure --run "cabal build -w ${{ env.ghc-exe }}"
3933
- name: Haddock
40-
run: nix-shell --arg ghcVersion '"${{ matrix.ghc-version }}"' --arg installHls 'false' --pure --run "cabal haddock"
34+
run: nix-shell --arg installHls 'false' --pure --run "cabal haddock -w ${{ env.ghc-exe }}"
4135
- name: cabal-docspec
42-
run: nix-shell --arg ghcVersion '"${{ matrix.ghc-version }}"' --arg installHls 'false' --pure --run "cabal-docspec"
36+
run: nix-shell --arg installHls 'false' --pure --run "cabal-docspec -w ${{ env.ghc-exe }}"
4337
- name: Build benchmarks
44-
run: nix-shell --arg ghcVersion '"${{ matrix.ghc-version }}"' --arg installHls 'false' --pure --run "cabal build linear-base:bench:bench"
38+
run: nix-shell --arg installHls 'false' --pure --run "cabal build linear-base:bench:bench"
4539
- name: Run benchmarks
46-
run: nix-shell --arg ghcVersion '"${{ matrix.ghc-version }}"' --arg installHls 'false' --pure --run "cabal bench 2>&1 | tee benchmark_ghc${{ matrix.ghc-version }}.txt"
40+
run: nix-shell --arg installHls 'false' --pure --run "cabal bench 2>&1 | tee benchmark_${{ env.ghc-name }}.txt"
4741
- name: Upload benchmark results
4842
uses: actions/upload-artifact@v3
4943
with:
50-
name: linear-base_benchmarks_ghc${{ matrix.ghc-version }}
44+
name: linear-base_benchmarks_${{ env.ghc-name }}
5145
path: |
52-
benchmark_ghc${{ matrix.ghc-version }}.txt
46+
benchmark_${{ env.ghc-name }}.txt
5347
retention-days: 90
5448

5549
ormolu:

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ cabal.sandbox.config
2222
.stack-work/
2323
cabal.project.local
2424
.HTF/
25+
26+
ghc-dps-compact-regions-prims-702220602b

bench-version-changes/ghc911/after/Compact.hs

Whitespace-only changes.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
{-# LANGUAGE LinearTypes #-}
2+
{-# LANGUAGE AllowAmbiguousTypes #-}
3+
{-# LANGUAGE BangPatterns #-}
4+
{-# LANGUAGE DataKinds #-}
5+
{-# LANGUAGE FlexibleContexts #-}
6+
{-# LANGUAGE ImpredicativeTypes #-}
7+
{-# LANGUAGE KindSignatures #-}
8+
{-# LANGUAGE LinearTypes #-}
9+
{-# LANGUAGE RankNTypes #-}
10+
{-# LANGUAGE ScopedTypeVariables #-}
11+
{-# LANGUAGE TypeApplications #-}
12+
{-# OPTIONS_GHC -Wno-name-shadowing -Wno-x-partial #-}
13+
14+
module Bench.Utils where
15+
16+
import Control.DeepSeq
17+
import Test.Tasty (TestTree, testGroup)
18+
import Test.Tasty.HUnit (testCaseInfo, assertEqual)
19+
import Test.Tasty.Bench
20+
import Control.Exception (evaluate)
21+
import Data.Functor ((<&>))
22+
import GHC.Compact (compact, getCompact)
23+
24+
safetySameAsFirstImpl :: forall m a r. (Show r, Eq r) => [(a %m -> r, String, Bool)] -> [(IO a, String)] -> IO TestTree
25+
safetySameAsFirstImpl impls datasets = do
26+
let ((refImpl, refImplName, _):otherImpls) = impls
27+
testGroup "safety" <$> (sequence $
28+
datasets <&> \(loadSampleData, sizeName) -> do
29+
sampleData <- loadSampleData
30+
return $ testGroup sizeName $ otherImpls <&> \(impl, implName, _) ->
31+
testCaseInfo (refImplName ++ " and " ++ implName ++ " give the same result") $ do
32+
let expected = refImpl sampleData
33+
actual = impl sampleData
34+
assertEqual "same result" expected actual
35+
return "")
36+
37+
benchImpls :: forall m a r. (NFData r) => [(a %m -> r, String, Bool)] -> [(IO a, String)] -> IO Benchmark
38+
benchImpls impls datasets = do
39+
bgroup "benchmark" <$> (sequence $
40+
datasets <&> \(loadSampleData, sizeName) -> do
41+
sampleData <- loadSampleData
42+
return $ testGroup sizeName $ concat $ impls <&> \(impl, implName, isLazy) -> if isLazy
43+
then
44+
[ bench (implName ++ ".force") $ (flip whnfAppIO) sampleData $ \sampleData -> evaluate $ force $ impl sampleData,
45+
bench (implName ++ ".copyIntoReg") $ (flip whnfAppIO) sampleData $ \sampleData -> do
46+
resInRegion <- compact $ impl sampleData
47+
evaluate $ getCompact $ resInRegion
48+
]
49+
else
50+
[ bench implName $ (flip whnfAppIO) sampleData $ \sampleData -> evaluate $ impl sampleData ])
51+
52+
launchImpl :: forall m a r. (NFData r) => String -> [(a %m -> r, String, Bool)] -> [(IO a, String)] -> IO ()
53+
launchImpl requestedImplDataSetspec impls datasets = go impls (go' datasets) where
54+
(requestedSize, dotRequestedImplSpec) = span (/= '.') requestedImplDataSetspec
55+
(requestedImplRadical, requestedImplVariant) = span (/= '.') (tail dotRequestedImplSpec)
56+
go [] _ = error ("requested implementation '" ++ requestedImplRadical ++ "' not found")
57+
go ((impl, implName, isLazy):_) loadSampleData | implName == requestedImplRadical = do
58+
sampleData <- loadSampleData
59+
if isLazy
60+
then case requestedImplVariant of
61+
".force" -> evaluate $ rwhnf $ force $ impl sampleData
62+
".copyIntoReg" -> do
63+
resInRegion <- compact $ impl sampleData
64+
evaluate $ rwhnf $ getCompact $ resInRegion
65+
_ -> error ("variant '" ++ requestedImplVariant ++ "' not found (required for lazy impl)")
66+
else
67+
evaluate $ rwhnf $ impl sampleData
68+
putStrLn "Done!"
69+
go (_:xs) loadSampleData = go xs loadSampleData
70+
71+
go' [] = error ("requested size '" ++ requestedSize ++ "' not found")
72+
go' ((loadSampleData, sizeName):_) | sizeName == requestedSize = loadSampleData
73+
go' (_:xs) = go' xs
74+
75+
{-# LANGUAGE AllowAmbiguousTypes #-}
76+
{-# LANGUAGE BangPatterns #-}
77+
{-# LANGUAGE BlockArguments #-}
78+
{-# LANGUAGE DataKinds #-}
79+
{-# LANGUAGE FlexibleContexts #-}
80+
{-# LANGUAGE ImpredicativeTypes #-}
81+
{-# LANGUAGE KindSignatures #-}
82+
{-# LANGUAGE LinearTypes #-}
83+
{-# LANGUAGE RankNTypes #-}
84+
{-# LANGUAGE ScopedTypeVariables #-}
85+
{-# LANGUAGE TypeApplications #-}
86+
{-# OPTIONS_GHC -Wno-x-partial #-}
87+
88+
module Main (main) where
89+
90+
import System.Environment
91+
import Test.Tasty.Bench
92+
import qualified Bench.Map as Map
93+
import qualified Bench.TreeTraversal as TreeTraversal
94+
import qualified Bench.DList as DList
95+
import qualified Bench.Queue as Queue
96+
import qualified Bench.Parser as Parser
97+
import qualified Bench.Utils as Utils
98+
99+
-- run with
100+
-- cabal bench -w $(pwd)/ghc@580d39a221/bin/ghc --allow-newer linear-dest:bench:bench --benchmark-options='+RTS -T -N1 -RTS'
101+
-- run in isolation with
102+
-- cabal run -w $(pwd)/ghc@580d39a221/bin/ghc -v0 linear-dest:bench:bench -- -l | grep -P 'All\.[^\.]+\.benchmark\.' | while read -r name; do cabal run -w $(pwd)/ghc@580d39a221/bin/ghc -v0 linear-dest:bench:bench -- -p '$0 == "'"$name"'"' +RTS -T -N1 -RTS; done
103+
104+
launchImpl :: String -> IO ()
105+
launchImpl s =
106+
let (_all, dotModuleName) = span (/= '.') s
107+
(moduleName, dotBenchmark) = span (/= '.') (tail dotModuleName)
108+
(_benchmark, dotImplSizeSpec) = span (/= '.') (tail dotBenchmark)
109+
implSizeSpec = tail dotImplSizeSpec
110+
in case (_all ++ "." ++ moduleName ++ "." ++ _benchmark) of
111+
"All.Map.benchmark" -> Utils.launchImpl implSizeSpec Map.impls Map.dataSets
112+
"All.TreeTraversal.benchmark" -> Utils.launchImpl implSizeSpec TreeTraversal.impls TreeTraversal.dataSets
113+
"All.DList.benchmark" -> Utils.launchImpl implSizeSpec DList.impls DList.dataSets
114+
"All.Queue.benchmark" -> Utils.launchImpl implSizeSpec Queue.impls Queue.dataSets
115+
"All.Parser.benchmark" -> Utils.launchImpl implSizeSpec Parser.impls Parser.dataSets
116+
_ -> launchAllBenchs
117+
118+
launchAllBenchs :: IO ()
119+
launchAllBenchs = do
120+
mapBenchgroup <- bgroup "Map" <$> sequence [ Utils.benchImpls Map.impls Map.dataSets, Utils.safetySameAsFirstImpl Map.impls Map.dataSets ]
121+
treetraversalBenchgroup <- bgroup "TreeTraversal" <$> sequence [ Utils.benchImpls TreeTraversal.impls TreeTraversal.dataSets, Utils.safetySameAsFirstImpl TreeTraversal.impls TreeTraversal.dataSets, TreeTraversal.extraSafety ]
122+
dlistBenchgroup <- bgroup "DList" <$> sequence [ Utils.benchImpls DList.impls DList.dataSets, Utils.safetySameAsFirstImpl DList.impls DList.dataSets ]
123+
queueBenchgroup <- bgroup "Queue" <$> sequence [ Utils.benchImpls Queue.impls Queue.dataSets, Utils.safetySameAsFirstImpl Queue.impls Queue.dataSets ]
124+
parserBenchgroup <- bgroup "Parser" <$> sequence [ Utils.benchImpls Parser.impls Parser.dataSets, Utils.safetySameAsFirstImpl Parser.impls Parser.dataSets, Parser.extraSafety ]
125+
defaultMain
126+
[ mapBenchgroup,
127+
treetraversalBenchgroup,
128+
dlistBenchgroup,
129+
queueBenchgroup,
130+
parserBenchgroup
131+
]
132+
133+
main :: IO ()
134+
main = do
135+
args <- getArgs
136+
case args of
137+
s : _ -> launchImpl s
138+
_ -> launchAllBenchs
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
{-# LANGUAGE AllowAmbiguousTypes #-}
2+
{-# LANGUAGE BangPatterns #-}
3+
{-# LANGUAGE DataKinds #-}
4+
{-# LANGUAGE DeriveAnyClass #-}
5+
{-# LANGUAGE DeriveGeneric #-}
6+
{-# LANGUAGE FlexibleContexts #-}
7+
{-# LANGUAGE ImpredicativeTypes #-}
8+
{-# LANGUAGE KindSignatures #-}
9+
{-# LANGUAGE LinearTypes #-}
10+
{-# LANGUAGE PatternSynonyms #-}
11+
{-# LANGUAGE RankNTypes #-}
12+
{-# LANGUAGE ScopedTypeVariables #-}
13+
{-# LANGUAGE TypeApplications #-}
14+
{-# LANGUAGE NoImplicitPrelude #-}
15+
{-# LANGUAGE UnicodeSyntax #-}
16+
{-# LANGUAGE GADTs #-}
17+
{-# OPTIONS_GHC -Wno-name-shadowing #-}
18+
{-# OPTIONS_GHC -ddump-simpl -ddump-to-file -dsuppress-all #-}
19+
20+
module Compact.BFTraversal where
21+
22+
import Compact.Destination
23+
import Control.Functor.Linear ((<&>))
24+
import GHC.Generics
25+
import Prelude.Linear
26+
import Bench.Queue hiding (dataSets, impls)
27+
import Data.Proxy (Proxy)
28+
import Control.DeepSeq (NFData)
29+
import qualified Prelude as NonLin
30+
import qualified Data.Functor as NonLin
31+
import Prelude (Functor, fmap, Applicative, pure, (<*>), return)
32+
import Control.Exception (evaluate)
33+
import Control.DeepSeq (force)
34+
import Test.Tasty (TestTree, testGroup)
35+
import Test.Tasty.HUnit (assertEqual, testCaseInfo)
36+
import Control.Monad.State.Lazy (runState, state)
37+
38+
data BinTree a where
39+
Nil :: BinTree a
40+
Node :: a %1 -> (BinTree a) %1 -> (BinTree a) %1 -> BinTree a deriving (NonLin.Eq, Generic, NonLin.Show, NFData)
41+
42+
pattern Leaf :: forall {a}. a -> BinTree a
43+
pattern Leaf x = Node x Nil Nil
44+
45+
-- From "Phases in Software Architecture", Gibbons & al. 2023
46+
--------------------------------------------------------------------------------
47+
48+
(⊗) :: Applicative m => m a -> m b -> m (a, b)
49+
xs ys = pure (,) <*> xs <*> ys
50+
51+
data Phases m a where
52+
Pure :: a Phases m a
53+
Link :: (a b c) m a Phases m b Phases m c
54+
55+
instance Functor (Phases m) where
56+
fmap f (Pure x) = Pure (f x)
57+
fmap f (Link g mx my) = Link (\x y -> f (g x y)) mx my
58+
59+
instance Applicative m Applicative (Phases m) where
60+
pure x = Pure x
61+
Pure f <*> xs = NonLin.fmap f xs
62+
fs <*> Pure x = NonLin.fmap (\f f x) fs
63+
Link f xs ys <*> Link g zs ws = Link h (xs zs) (ys ws) where h (x, z) (y, w) = (f x y) (g z w)
64+
65+
now :: Applicative m m a Phases m a
66+
now xs = Link (curry fst) xs (Pure ())
67+
68+
later :: Applicative m Phases m a Phases m a
69+
later xs = Link (curry snd) (pure ()) xs
70+
71+
phase :: Applicative m Int m a Phases m a
72+
phase 1 = now
73+
phase i = later NonLin.. phase (i - 1)
74+
75+
runPhases :: Applicative m Phases m a m a
76+
runPhases (Pure x) = pure x
77+
runPhases (Link f xs ys) = pure f <*> xs <*> runPhases ys
78+
79+
bft' :: Applicative m (a m b) BinTree a Phases m (BinTree b)
80+
bft' _ Nil = pure Nil
81+
bft' f (Node x tl tr) = pure Node <*> now (f x) <*> later ((bft' f) tl) <*> later ((bft' f) tr)
82+
83+
mapPhasesBFS :: Applicative m (a m b) BinTree a m (BinTree b)
84+
mapPhasesBFS f = runPhases NonLin.. bft' f
85+
86+
--------------------------------------------------------------------------------
87+
88+
mapAccumBFS :: forall a b s. (s -> a -> (s, b)) -> s -> BinTree a -> (BinTree b, s)
89+
mapAccumBFS f s0 tree =
90+
unur . withRegion $
91+
\(_ :: Proxy r) token -> fromIncomplete $ alloc @r token <&>
92+
\dtree -> go s0 (singletonN (Ur tree, dtree))
93+
where
94+
go :: forall r. (Region r) => s -> NaiveQueue (Ur (BinTree a), Dest r (BinTree b)) %1 -> Ur s
95+
go s q = case dequeueN q of
96+
Nothing -> Ur s
97+
Just ((utree, dtree), q') -> case utree of
98+
Ur Nil -> dtree & fill @'Nil `lseq` go s q'
99+
Ur (Node x tl tr) -> case dtree & fill @'Node of
100+
(dr, dtl, dtr) ->
101+
let q'' = q' `enqueueN` (Ur tl, dtl) `enqueueN` (Ur tr, dtr)
102+
(s', r) = f s x
103+
in dr & fillLeaf r `lseq` go s' q''
104+
105+
--------------------------------------------------------------------------------
106+
107+
dataSets :: [(IO (BinTree ()), String)]
108+
dataSets =
109+
[ (evaluate $ force (go 0 10), "2^10")
110+
, (evaluate $ force (go 0 13), "2^13")
111+
, (evaluate $ force (go 0 16), "2^16")
112+
, (evaluate $ force (go 0 19), "2^19")
113+
, (evaluate $ force (go 0 22), "2^22")
114+
]
115+
where
116+
go :: Int -> Int -> BinTree ()
117+
go currentDepth maxDepth =
118+
if currentDepth >= maxDepth
119+
then Nil
120+
else Node () (go (currentDepth + 1) maxDepth) (go (currentDepth + 1) maxDepth)
121+
122+
dpsRelabel :: BinTree () -> (BinTree Int, Int)
123+
dpsRelabel base = mapAccumBFS (\s _ -> (s + 1, s)) 0 base
124+
125+
phasesRelabel :: BinTree () -> (BinTree Int, Int)
126+
phasesRelabel base = runState (mapPhasesBFS (\_ -> state (\s -> (s, s + 1))) base) 0
127+
128+
impls :: [(BinTree () -> (BinTree Int, Int), String, Bool)]
129+
impls =
130+
[ (dpsRelabel, "dpsRelabel", False)
131+
, (phasesRelabel, "phasesRelabel", True)
132+
]
133+
134+
extraSafety :: IO TestTree
135+
extraSafety =
136+
return $ testGroup "extraSafety" $ impls NonLin.<&> \(impl, implName, _) ->
137+
testCaseInfo (implName ++ " give the good result on a small example") $ do
138+
let expected :: (BinTree Int, Int)
139+
expected =
140+
( Node
141+
0
142+
(Node 1 (Leaf 3) (Leaf 4))
143+
(Node 2 (Leaf 5) Nil),
144+
6
145+
)
146+
base :: BinTree ()
147+
base =
148+
Node
149+
()
150+
(Node () (Leaf ()) (Leaf ()))
151+
(Node () (Leaf ()) Nil)
152+
actual = impl base
153+
assertEqual "same result" expected actual
154+
return $ ""

0 commit comments

Comments
 (0)