From e1071f6ee200ec4bf8a36b402873d9f3e78fe3c0 Mon Sep 17 00:00:00 2001
From: Joachim Breitner <mail@joachim-breitner.de>
Date: Tue, 10 Apr 2018 09:26:09 -0400
Subject: [PATCH] Add test case for #15005

this succeeds on `master` right now, but I confirmed that it fails on
ghc-8.4.1-release.

(cherry picked from commit 3f59d3802170f495702674b4f8e4e80247803654)
---
 .../tests/simplCore/should_compile/T15005.hs  | 189 ++++++++++++++++++
 .../tests/simplCore/should_compile/all.T      |   1 +
 2 files changed, 190 insertions(+)
 create mode 100644 testsuite/tests/simplCore/should_compile/T15005.hs

diff --git a/testsuite/tests/simplCore/should_compile/T15005.hs b/testsuite/tests/simplCore/should_compile/T15005.hs
new file mode 100644
index 000000000000..e59f49fe4b6a
--- /dev/null
+++ b/testsuite/tests/simplCore/should_compile/T15005.hs
@@ -0,0 +1,189 @@
+module T15005 (
+    OrderCell,
+    ElementCell,
+    rawAlgorithm,
+    rawAlgorithmWithSize
+    ) where
+
+-- Control
+
+import Control.Applicative
+import Control.Monad
+import Control.Monad.ST
+
+-- Data
+
+import Data.Word
+import Data.Bits
+import Data.STRef
+
+type RawOrder s o = STRef s (o s)
+
+type RawElement s e = STRef s (e s)
+
+data RawAlgorithm s o e = RawAlgorithm {
+    newOrder        :: ST s (RawOrder s o),
+    compareElements :: RawElement s e -> RawElement s e -> RawOrder s o -> ST s Ordering,
+    newMinimum      :: RawOrder s o -> ST s (RawElement s e),
+    newMaximum      :: RawOrder s o -> ST s (RawElement s e),
+    newAfter        :: RawElement s e -> RawOrder s o -> ST s (RawElement s e),
+    newBefore       :: RawElement s e -> RawOrder s o -> ST s (RawElement s e),
+    delete          :: RawElement s e -> RawOrder s o -> ST s ()
+}
+{-FIXME:
+    If we ever allow users to plug in their own algorithms, we have to flag the
+    respective function as unsafe and point out that referential transparency is
+    in danger if the algorithm does not fulfill the specification. This is
+    because element comparison is presented to the user as a pure function. The
+    important condition is that for any two elements, compareElements must
+    always return the same result as long as delete is not called on either
+    element.
+-}
+
+type OrderCell = Cell
+
+type ElementCell = Cell
+
+data Cell s = Cell {
+                  label :: Label,
+                  next  :: CellRef s,
+                  prev  :: CellRef s
+              }
+
+type CellRef s = STRef s (Cell s)
+
+newtype Label = Label LabelWord deriving (Eq, Ord)
+
+type LabelWord = Word64
+
+labelWordSize :: Int
+labelWordSize = 64
+
+initialBaseLabel :: Label
+initialBaseLabel = Label 0
+
+rawAlgorithm :: RawAlgorithm s OrderCell ElementCell
+rawAlgorithm = rawAlgorithmWithSize defaultSize
+
+defaultSize :: Int
+defaultSize = 63
+
+rawAlgorithmWithSize :: Int -> RawAlgorithm s OrderCell ElementCell
+rawAlgorithmWithSize size
+    | size < 0 || size >= labelWordSize
+        = error "Data.Order.Algorithm.dietzSleatorAmortizedLogWithSize: \
+                \Size out of bounds"
+    | otherwise
+        = RawAlgorithm {
+              newOrder        = fixST $
+                                \ ref -> newSTRef $ Cell {
+                                   label = initialBaseLabel,
+                                   next  = ref,
+                                   prev  = ref
+                                },
+              compareElements = \ ref1 ref2 baseRef -> do
+                                    baseCell <- readSTRef baseRef
+                                    cell1 <- readSTRef ref1
+                                    cell2 <- readSTRef ref2
+                                    let offset1 = labelDiff (label cell1)
+                                                            (label baseCell)
+                                    let offset2 = labelDiff (label cell2)
+                                                            (label baseCell)
+                                    return $ compare offset1 offset2,
+              newMinimum      = newAfterCell,
+              newMaximum      = newBeforeCell,
+              newAfter        = const . newAfterCell,
+              newBefore       = const . newBeforeCell,
+              delete          = \ ref _ -> do
+                                    cell <- readSTRef ref
+                                    modifySTRef
+                                        (prev cell)
+                                        (\ prevCell -> prevCell {
+                                                           next = next cell
+                                                       })
+                                    modifySTRef
+                                        (next cell)
+                                        (\ nextCell -> nextCell {
+                                                           prev = prev cell
+                                                       })
+          } where
+
+    noOfLabels :: LabelWord
+    noOfLabels = shiftL 1 size
+
+    labelMask :: LabelWord
+    labelMask = pred noOfLabels
+
+    toLabel :: LabelWord -> Label
+    toLabel = Label . (.&. labelMask)
+
+    labelSum :: Label -> Label -> Label
+    labelSum (Label word1) (Label word2) = toLabel (word1 + word2)
+
+    labelDiff :: Label -> Label -> Label
+    labelDiff (Label word1) (Label word2) = toLabel (word1 - word2)
+
+    labelDistance :: Label -> Label -> LabelWord
+    labelDistance lbl1 lbl2 = case labelDiff lbl1 lbl2 of
+                                  Label word | word == 0 -> noOfLabels
+                                             | otherwise -> word
+
+    newAfterCell :: CellRef s -> ST s (CellRef s)
+    newAfterCell ref = do
+        relabel ref
+        lbl <- label <$> readSTRef ref
+        nextRef <- next <$> readSTRef ref
+        nextLbl <- label <$> readSTRef nextRef
+        newRef <- newSTRef $ Cell {
+            label = labelSum lbl (Label (labelDistance nextLbl lbl `div` 2)),
+            next  = nextRef,
+            prev  = ref
+        }
+        modifySTRef ref     (\ cell     -> cell     { next = newRef })
+        modifySTRef nextRef (\ nextCell -> nextCell { prev = newRef })
+        return newRef
+
+    relabel :: CellRef s -> ST s ()
+    relabel startRef = do
+        startCell <- readSTRef startRef
+        let delimSearch ref gapCount = do
+                cell <- readSTRef ref
+                let gapSum = labelDistance (label cell) (label startCell)
+                if gapSum <= gapCount ^ 2
+                    then if ref == startRef
+                             then error "Data.Order.Algorithm.\
+                                        \dietzSleatorAmortizedLogWithSize: \
+                                        \Order full"
+                             else delimSearch (next cell) (succ gapCount)
+                    else return (ref, gapSum, gapCount)
+        (delimRef, gapSum, gapCount) <- delimSearch (next startCell) 1
+        let smallGap = gapSum `div` gapCount
+        let largeGapCount = gapSum `mod` gapCount
+        let changeLabels ref ix = when (ref /= delimRef) $ do
+                cell <- readSTRef ref
+                let lbl = labelSum
+                              (label startCell)
+                              (Label (ix * smallGap + min largeGapCount ix))
+                writeSTRef ref (cell { label = lbl })
+                changeLabels (next cell) (succ ix)
+        changeLabels (next startCell) 1
+    {-FIXME:
+        We allow the number of cells to be larger than the square root of the
+        number of possible labels as long as we find a sparse part in our circle
+        of cells (since our order full condition is only true if the complete
+        circle is congested). This should not influence correctness and probably
+        also not time complexity, but we should check this more thoroughly.
+    -}
+    {-FIXME:
+        We arrange the large and small gaps differently from Dietz and Sleator
+        by putting all the large gaps at the beginning instead of distributing
+        them over the relabeled area. However, this should not influence time
+        complexity, as the complexity proof seems to only rely on the fact that
+        gap sizes differ by at most 1. We should check this more thoroughly
+        though.
+    -}
+
+    newBeforeCell :: CellRef s -> ST s (CellRef s)
+    newBeforeCell ref = do
+        cell <- readSTRef ref
+        newAfterCell (prev cell)
diff --git a/testsuite/tests/simplCore/should_compile/all.T b/testsuite/tests/simplCore/should_compile/all.T
index c4b2f30d1fa8..0c5cd99c3fc8 100644
--- a/testsuite/tests/simplCore/should_compile/all.T
+++ b/testsuite/tests/simplCore/should_compile/all.T
@@ -296,3 +296,4 @@ test('T13990', normal, compile, ['-dcore-lint -O'])
 test('T14650', normal, compile, ['-O2'])
 test('T14959', normal, compile, ['-O'])
 test('T15002', [ req_profiling ], compile, ['-O -fprof-auto -prof'])
+test('T15005', normal, compile, ['-O'])
-- 
GitLab