fast-math
fast-math copied to clipboard
Fragile rewrite rules
GHC panics on the following program:
module Test where
import Numeric.FastMath
import GHC.Exts
times4 :: Double -> Double
times4 (D# x) = D# ((x *## x) *## (x *## x))
ghc-7.10.1 Test.hs -cpp -XMagicHash -O -fforce-recomp
...
[5 of 5] Compiling Test ( Test.hs, Test.o )
ghc: panic! (the 'impossible' happened)
(GHC version 7.10.1 for x86_64-unknown-linux):
Simplifier ticks exhausted
When trying RuleFired double commute left *
To increase the limit, use -fsimpl-tick-factor=N (default 100)
If you need to do this, let GHC HQ know, and what factor you needed
To see detailed counts use -ddump-simpl-stats
Total ticks: 4564
Please report this as a GHC bug: http://www.haskell.org/ghc/reportabug
It is not a bug in GHC though. It happens because of the following rewrite rule in Numeric.FastMath.Approximation
"double commute left *" [~2] forall x1 x2 x3. (*##) x1 ((*##) x2 x3)
= (*##) ((*##) x2 x3) x1
This rewrites (x *## x) *## (x *## x)
to (x *## x) *## (x *## x)
.
From the GHC user's guide [1]:
GHC makes no attempt to make sure that the rules are confluent or terminating. For example:
"loop" forall x y. f x y = f y x
This rule will cause the compiler to go into an infinite loop.
The are similar fragile rewrite rules in this package. I don't know what to about this, other than to remove those rules. Or at least put a big warning in the README.
[1] https://downloads.haskell.org/~ghc/7.10.1/docs/html/users_guide/rewrite-rules.html#rule-semantics
Good catch. There's probably a way to force this to terminate by putting an id
function call in there somewhere.
Do we have a resolution for this? @mikeizbicki has basically taken over the package these days…
This does not reproduce for me.
$ cat Test.hs
module Test (times4) where
import Numeric.FastMath
import GHC.Exts
times4 :: Double -> Double
times4 (D# x) = D# ((x *## x) *## (x *## x))
GHC 8.6.3:
[nix-shell:~]$ ghc Test.hs -cpp -XMagicHash -O -fforce-recomp -ddump-simpl -dsuppress-all
[1 of 1] Compiling Test ( Test.hs, Test.o )
Result size of Tidy Core
= {terms: 27, types: 11, coercions: 0, joins: 0/1}
-- RHS size: {terms: 12, types: 4, coercions: 0, joins: 0/1}
times4
times4
= \ ds_d12P ->
case ds_d12P of { D# x_a11H ->
let {
xx_a12Z
xx_a12Z = *## x_a11H x_a11H } in
D# (*## xx_a12Z xx_a12Z)
}
... irrelevant bits ...
GHC 8.4.4:
[nix-shell:~]$ ghc Test.hs -cpp -XMagicHash -O -fforce-recomp -ddump-simpl -dsuppress-all
[1 of 1] Compiling Test ( Test.hs, Test.o )
==================== Tidy Core ====================
Result size of Tidy Core
= {terms: 27, types: 11, coercions: 0, joins: 0/1}
-- RHS size: {terms: 12, types: 4, coercions: 0, joins: 0/1}
times4
times4
= \ ds_d12U ->
case ds_d12U of { D# x_a11N ->
let {
xx_a134
xx_a134 = *## x_a11N x_a11N } in
D# (*## xx_a134 xx_a134)
}
... irrelevant bits ...
GHC 8.2.2:
[nix-shell:~]$ ghc Test.hs -cpp -XMagicHash -O -fforce-recomp -ddump-simpl -dsuppress-all
[1 of 1] Compiling Test ( Test.hs, Test.o )
==================== Tidy Core ====================
Result size of Tidy Core
= {terms: 27, types: 11, coercions: 0, joins: 0/1}
-- RHS size: {terms: 12, types: 4, coercions: 0, joins: 0/1}
times4
times4
= \ ds_dY7 ->
case ds_dY7 of { D# x_aXd ->
let {
xx_aYh
xx_aYh = *## x_aXd x_aXd } in
D# (*## xx_aYh xx_aYh)
}
... irrelevant bits ...
GHC HEAD:
[nix-shell:~]$ ghc Test.hs -cpp -XMagicHash -O -fforce-recomp -ddump-simpl -dsuppress-all
[1 of 1] Compiling Test ( Test.hs, Test.o )
==================== Tidy Core ====================
Result size of Tidy Core
= {terms: 27, types: 11, coercions: 0, joins: 0/1}
-- RHS size: {terms: 12, types: 4, coercions: 0, joins: 0/1}
times4
times4
= \ ds_d12H ->
case ds_d12H of { D# x_a11A ->
let {
xx_a12R
xx_a12R = *## x_a11A x_a11A } in
D# (*## xx_a12R xx_a12R)
}
... irrelevant bits ...
-O1 and -O2 produce the same results, which is expected, since I don't think GHC can optimise that further.