Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/2026-01-29T18_10_05+01_00_fix_3129
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FIXED: Reduce constants to NF before specialization [#3129](https://github.com/clash-lang/clash-compiler/issues/3129)
18 changes: 13 additions & 5 deletions clash-lib/src/Clash/Normalize/Transformations/Specialize.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Copyright : (C) 2012-2016, University of Twente,
2016-2017, Myrtle Software Ltd,
2017-2018, Google Inc.,
2021-2024, QBayLogic B.V.
2021-2026, QBayLogic B.V.
License : BSD2 (see the file LICENSE)
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>

Expand Down Expand Up @@ -87,7 +87,7 @@ import Clash.Rewrite.Types
, typeTranslator, workFreeBinders, debugOpts, topEntities, specializationLimit)
import Clash.Rewrite.Util
( mkBinderFor, mkDerivedName, mkFunction, mkTmBinderFor, setChanged, changed
, normalizeTermTypes, normalizeId)
, normalizeTermTypes, normalizeId, whnfRW)
import Clash.Rewrite.WorkFree (isWorkFree)
import Clash.Normalize.Types
( NormRewrite, NormalizeSession, specialisationCache, specialisationHistory)
Expand Down Expand Up @@ -290,9 +290,17 @@ constantSpec ctx@(TransformContext is0 tfCtx) e@(App e1 e2)
= do specInfo<- constantSpecInfo ctx e2
if csrFoundConstant specInfo then
let newBindings = csrNewBindings specInfo in
if null newBindings then
-- Whole of e2 is constant
specialize ctx (App e1 e2)
if null newBindings then do
-- Whole of e2 is constant, we reduce it here eagerly before specialization
-- because we have observed long running compile times when having
-- recursively defined constants, see:
-- https://github.com/clash-lang/clash-compiler/issues/3129
e2Red <- case collectArgs e2 of
(Prim p0, _) -> whnfRW False ctx e2 $ \_ctx1 e2Red -> case e2Red of
(collectArgs -> (Prim p1, _)) | primName p0 == primName p1 -> return e2
_ -> changed e2Red
_ -> return e2
specialize ctx (App e1 e2Red)
Comment on lines +298 to +303
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this should have a comment pointing to the bug report. I know that we can do code archaeology, but given that this is such a hack I think it deserves one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also done

else do
-- Parts of e2 are constant
let is1 = extendInScopeSetList is0 (fst <$> csrNewBindings specInfo)
Expand Down
1 change: 1 addition & 0 deletions tests/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,7 @@ runClashTest = defaultMain
, outputTest "PortNamesWithRTree" def{hdlTargets=[Verilog]}
, clashLibTest "T1182A" def
, clashLibTest "T1182B" def
, runTest "T3129" def{hdlSim=[], clashFlags=["-fclash-spec-limit=400"]}
]
, clashTestGroup "Unit"
[ runTest "Imap" def
Expand Down
33 changes: 33 additions & 0 deletions tests/shouldwork/TopEntity/T3129.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module T3129 where

import Clash.Prelude

import GHC.Base (isTrue#)
import GHC.Num.Natural (Natural (NS), naturalLogBase#)
import GHC.Prim (eqWord#, plusWord#)

naturalCLogBase :: Natural -> Natural -> Natural
naturalCLogBase x y =
let z1 = naturalLogBase# x y
z2 = naturalLogBase# x (y-1)
in case y of
1 -> 0
_ | isTrue# (z1 `eqWord#` z2) -> NS (z1 `plusWord#` 1##)
| otherwise -> NS z1

f :: Natural -> Natural
f m = 2 * (naturalCLogBase 2 m)

g :: Natural -> Natural
g m =
let i = f m
calc 0 _ _ = 1
calc 1 val tmp = (val * tmp) `mod` m
calc n val tmp = calc
(if n `mod` 2 == 0 then n `div` 2 else n - 1 )
(if n `mod` 2 == 0 then val * val `mod` m else val )
(if n `mod` 2 == 0 then tmp `mod` m else (tmp * val) `mod` m)
in calc (m - i - 1) 2 1

topEntity :: Natural
topEntity = g 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
Loading