Skip to content

Commit 3e6f36c

Browse files
christiaanbalex-mckenna
authored andcommitted
Don't loop on hidden recursive data type
The recursive occurrence was hiding behind a type family. So we should always expand type families when checking whether a data type is recursive. Fixes #1921 (cherry picked from commit 865da87)
1 parent 2345bda commit 3e6f36c

File tree

4 files changed

+89
-4
lines changed

4 files changed

+89
-4
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FIXED: Dont' loop on recursive data types hiding behind type families [#1921](https://github.com/clash-lang/clash-compiler/issues/1921)

clash-lib/src/Clash/Netlist/Util.hs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ import Clash.Core.Term
8989
import Clash.Core.TermInfo
9090
import Clash.Core.TyCon
9191
(TyCon (FunTyCon), TyConName, TyConMap, tyConDataCons)
92-
import Clash.Core.Type (Type (..), TypeView (..),
93-
coreView1, splitTyConAppM, tyView, TyVar)
92+
import Clash.Core.Type
93+
(Type (..), TyVar, TypeView (..), coreView1, normalizeType, splitTyConAppM, tyView)
9494
import Clash.Core.Util
9595
(substArgTys, tyLitShow)
9696
import Clash.Core.Var
@@ -616,11 +616,30 @@ hasUnconstrainedExistential tcm dc =
616616

617617

618618
-- | Simple check if a TyCon is recursively defined.
619+
--
620+
-- Note [Look through type families in recursivity check]
621+
--
622+
-- Consider:
623+
--
624+
-- @
625+
-- data SList :: [Type] -> Type where
626+
-- SNil :: SList []
627+
-- CSons :: a -> Sing (as :: [k]) -> SList (a:as)
628+
--
629+
-- type family Sing [a] = SList [a]
630+
-- @
631+
--
632+
-- Without looking through type families, we would think that /SList/ is not
633+
-- recursive. This lead to issue #1921
619634
isRecursiveTy :: TyConMap -> TyConName -> Bool
620635
isRecursiveTy m tc = case tyConDataCons (m `lookupUniqMap'` tc) of
621636
[] -> False
622-
dcs -> let argTyss = map dcArgTys dcs
623-
argTycons = (map fst . catMaybes) $ (concatMap . map) splitTyConAppM argTyss
637+
dcs -> let argTyss = map dcArgTys dcs
638+
argTycons = (map fst . catMaybes)
639+
$ (concatMap . map)
640+
-- Note [Look through type families in recursivity check]
641+
(splitTyConAppM . normalizeType m)
642+
argTyss
624643
in tc `elem` argTycons
625644

626645
-- | Determines if a Core type is translatable to a HWType given a function that

tests/Main.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ runClashTest = defaultMain $ clashTestRoot
540540
, runTest "T1606B" def{hdlSim=False}
541541
, runTest "T1742" def{hdlSim=False, buildTargets=BuildSpecific ["shell"]}
542542
, runTest "T1756" def{hdlSim=False}
543+
, runTest "T1921" def{hdlTargets=[Verilog], hdlSim=False}
543544
] <>
544545
if compiledWith == Cabal then
545546
-- This tests fails without environment files present, which are only

tests/shouldwork/Issues/T1921.hs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{-# LANGUAGE CPP
2+
, DerivingStrategies
3+
, GeneralizedNewtypeDeriving
4+
, LambdaCase
5+
, AllowAmbiguousTypes
6+
, ApplicativeDo
7+
, StandaloneDeriving #-}
8+
9+
module T1921 where
10+
11+
import Clash.Prelude
12+
import Control.Lens
13+
import Data.Default
14+
#if MIN_VERSION_singletons(3,0,0)
15+
import Prelude.Singletons
16+
import GHC.TypeLits.Singletons as TL
17+
#else
18+
import Data.Singletons.Prelude
19+
import Data.Singletons.TypeLits as TL
20+
#endif
21+
22+
topEntity :: Clock System -> Reset System -> Enable System -> Signal System (Unsigned 8)
23+
topEntity = exposeClockResetEnable fibonacciLFSR8
24+
25+
-- Straightforward newtype
26+
newtype LFSRState n = LFSRState { runLFSRState :: BitVector n }
27+
deriving newtype (NFDataX, AutoReg)
28+
instance KnownNat n => Default (LFSRState n) where
29+
def = LFSRState (fromIntegral 1)
30+
31+
fibonacciLFSR8 :: HiddenClockResetEnable dom => Signal dom (Unsigned 8)
32+
fibonacciLFSR8 = fibonacciLFSRType @('[3,4,5,7]) @8
33+
34+
fibonacciLFSRType
35+
:: forall (taps :: [Nat]) (n :: Nat) dom
36+
. SingI taps
37+
=> KnownNat n
38+
=> HiddenClockResetEnable dom
39+
=> Signal dom (Unsigned n)
40+
fibonacciLFSRType =
41+
let lfsr = autoReg def (LFSRState <$> lfsr')
42+
lfsr' = do lfsrState <- lfsr
43+
-- shift the bit register by one, and then replace
44+
-- the bit on the end by xor of the taps (via go)
45+
return $
46+
shiftL (runLFSRState lfsrState) 1
47+
& ix 0
48+
.~ go lfsrState (sing :: Sing taps)
49+
in unpack <$> lfsr'
50+
51+
where
52+
go :: forall (n :: Nat) (indices :: [Nat])
53+
. SingI indices
54+
=> KnownNat n
55+
=> LFSRState n -> SList indices -> Bit
56+
go b@(LFSRState bs) = \case
57+
-- If there is only one tap left, return that bit
58+
(SCons a SNil) -> withKnownNat a
59+
$ bs ^?! ix (fromIntegral $ TL.natVal a)
60+
-- XOR a tapped bit with the remaining taps
61+
(SCons a as) -> withSingI as $ withKnownNat a
62+
$ xor (bs ^?! ix (fromIntegral $ TL.natVal a)) (go b as)
63+
-- This should never happen (we can't have no taps)
64+
SNil -> error "A no-tap LFSR is ill-defined"

0 commit comments

Comments
 (0)