Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clash not inlining certain floating point subtractions #2097

Closed
acairncross opened this issue Feb 16, 2022 · 2 comments · Fixed by #2108
Closed

Clash not inlining certain floating point subtractions #2097

acairncross opened this issue Feb 16, 2022 · 2 comments · Fixed by #2108
Assignees
Labels

Comments

@acairncross
Copy link

acairncross commented Feb 16, 2022

Compiling this:

{-# LANGUAGE DeriveGeneric #-}

import Clash.Prelude

topEntity :: MyFloat
topEntity = -1.0

data MyFloat = MyFloat {
  negative :: Bool,
  exponent :: Unsigned 8,
  mantissa :: UFixed 0 23
} deriving (Eq, Generic, BitPack)

instance Num MyFloat where
  (-) a b = bitCoerce @Float (bitCoerce a - bitCoerce b)

instance Fractional MyFloat where
  fromRational = bitCoerce @Float . fromRational

errors with:

<no location info>: error:
    Clash error call:
    Clash.Normalize.Transformations(857): Report as bug: caseCon error: case 1.0 of
      GHC.Types.F#[3891110078048108559] (y[6989586621679217777] :: GHC.Prim.Float#[3674937295934324758]) ->
        GHC.Types.F#[3891110078048108559] (GHC.Prim.minusFloat# x[6989586621679217773][LocalId] y[6989586621679217777][LocalId])
    CallStack (from HasCallStack):
      error, called at src/Clash/Normalize/Transformations.hs:857:3 in clash-lib-1.4.7-A3e0Bg7blinAlB0TODW2ZW:Clash.Normalize.Transformations

Clash seems to have trouble with the Float subtraction in (-) a b = bitCoerce @Float (bitCoerce a - bitCoerce b) needed to evaluate the literal -1 :: MyFloat.

Changing topEntity to -1 :: Float does compile on the other hand.

@christiaanb
Copy link
Member

Ah, the issue is simply that we haven't added support for matching on Float literals, like we do for Integer and Natural literals:

matchLiteralContructor
:: Term
-> Literal
-> [(Pat,Term)]
-> NormalizeSession Term
matchLiteralContructor c (IntegerLiteral l) alts = go (reverse alts)
where
go [(DefaultPat,e)] = changed e
go ((DataPat dc [] xs,e):alts')
| dcTag dc == 1
, l >= ((-2)^(63::Int)) && l < 2^(63::Int)
= let fvs = Lens.foldMapOf freeLocalIds unitVarSet e
(binds,_) = List.partition ((`elemVarSet` fvs) . fst)
$ List.zipEqual xs [Literal (IntLiteral l)]
e' = case binds of
[] -> e
_ -> Letrec binds e
in changed e'
| dcTag dc == 2
, l >= 2^(63::Int)
#if MIN_VERSION_base(4,15,0)
= let !(IP ba) = l
#else
= let !(Jp# !(BN# ba)) = l
#endif
ba' = BA.ByteArray ba
fvs = Lens.foldMapOf freeLocalIds unitVarSet e
(binds,_) = List.partition ((`elemVarSet` fvs) . fst)
$ List.zipEqual xs [Literal (ByteArrayLiteral ba')]
e' = case binds of
[] -> e
_ -> Letrec binds e
in changed e'
| dcTag dc == 3
, l < ((-2)^(63::Int))
#if MIN_VERSION_base(4,15,0)
= let !(IN ba) = l
#else
= let !(Jn# !(BN# ba)) = l
#endif
ba' = BA.ByteArray ba
fvs = Lens.foldMapOf freeLocalIds unitVarSet e
(binds,_) = List.partition ((`elemVarSet` fvs) . fst)
$ List.zipEqual xs [Literal (ByteArrayLiteral ba')]
e' = case binds of
[] -> e
_ -> Letrec binds e
in changed e'
| otherwise
= go alts'
go ((LitPat l', e):alts')
| IntegerLiteral l == l'
= changed e
| otherwise
= go alts'
go _ = error $ $(curLoc) ++ "Report as bug: caseCon error: " ++ showPpr c
matchLiteralContructor c (NaturalLiteral l) alts = go (reverse alts)
where
go [(DefaultPat,e)] = changed e
go ((DataPat dc [] xs,e):alts')
| dcTag dc == 1
, l >= 0 && l < 2^(64::Int)
= let fvs = Lens.foldMapOf freeLocalIds unitVarSet e
(binds,_) = List.partition ((`elemVarSet` fvs) . fst)
$ List.zipEqual xs [Literal (WordLiteral l)]
e' = case binds of
[] -> e
_ -> Letrec binds e
in changed e'
| dcTag dc == 2
, l >= 2^(64::Int)
#if MIN_VERSION_base(4,15,0)
= let !(IP ba) = l
#else
= let !(Jp# !(BN# ba)) = l
#endif
ba' = BA.ByteArray ba
fvs = Lens.foldMapOf freeLocalIds unitVarSet e
(binds,_) = List.partition ((`elemVarSet` fvs) . fst)
$ List.zipEqual xs [Literal (ByteArrayLiteral ba')]
e' = case binds of
[] -> e
_ -> Letrec binds e
in changed e'
| otherwise
= go alts'
go ((LitPat l', e):alts')
| NaturalLiteral l == l'
= changed e
| otherwise
= go alts'
go _ = error $ $(curLoc) ++ "Report as bug: caseCon error: " ++ showPpr c

@alex-mckenna alex-mckenna self-assigned this Feb 24, 2022
@martijnbastiaan martijnbastiaan added this to the 1.6.2 milestone Feb 25, 2022
alex-mckenna pushed a commit that referenced this issue Feb 25, 2022
In the primitives unpackFloat# and unpackDouble#, the result from
the primitive evaluator was an unboxed literal (i.e. Float#) when
the actual result of the function is a boxed literal (i.e. Float).
This led to #2097 having core that looked like

```
case 1.0# of
  F# x -> ...
```

which will rightly never match. When the primitives instead return
the correct type we get a case expression

```
case F# 1.0# of
  F# x -> ...
```

where the caseCon transformation can fire.
@alex-mckenna
Copy link
Contributor

alex-mckenna commented Feb 25, 2022

So a few things here:

  • when you just use -1.0 apparently you end up with 0 - 1.0, i.e. fromInteger 0 - fromRational 1.0. In the debug log I was seeing the first argument to the topEntity replaced with EmptyCase until I implemented fromInteger too (GHC 9.0.2). This was needed to make the final version compile, so it's worth saying that you should at least always implement fromInteger for numbers so things don't mysteriously go wrong

  • I disagree with @christiaanb's assessment of the fix. We match on Integer and Natural constructors because they have them, but FloatLiteral is Float# which has no constructors. The real culprit here was unpackFloat# and unpackDouble# which return boxed Float and Double but the primitive evaluation rule returned Float# and Double#. This means you end up with something like

    case 0.0# of
      F# x -> ...

    which obviously won't caseCon, but with the primitives fixed we end up with

    case F# 0.0# of
      F# x -> ...

    which does caseCon and normalization can finish.

However, this still doesn't make this particular example compile. We're missing the primitives F# and D# in the evaluator. With those added the example compiles and runs with no problems 🎉

PR incoming...

alex-mckenna pushed a commit that referenced this issue Feb 25, 2022
alex-mckenna pushed a commit that referenced this issue Feb 25, 2022
In the primitives unpackFloat# and unpackDouble#, the result from
the primitive evaluator was an unboxed literal (i.e. Float#) when
the actual result of the function is a boxed literal (i.e. Float).
This led to #2097 having core that looked like

```
case 1.0# of
  F# x -> ...
```

which will rightly never match. When the primitives instead return
the correct type we get a case expression

```
case F# 1.0# of
  F# x -> ...
```

where the caseCon transformation can fire.
alex-mckenna pushed a commit that referenced this issue Feb 25, 2022
alex-mckenna pushed a commit that referenced this issue Feb 25, 2022
alex-mckenna pushed a commit that referenced this issue Feb 25, 2022
In the primitives unpackFloat# and unpackDouble#, the result from
the primitive evaluator was an unboxed literal (i.e. Float#) when
the actual result of the function is a boxed literal (i.e. Float).
This led to #2097 having core that looked like

```
case 1.0# of
  F# x -> ...
```

which will rightly never match. When the primitives instead return
the correct type we get a case expression

```
case F# 1.0# of
  F# x -> ...
```

where the caseCon transformation can fire.
mergify bot pushed a commit that referenced this issue Feb 25, 2022
In the primitives unpackFloat# and unpackDouble#, the result from
the primitive evaluator was an unboxed literal (i.e. Float#) when
the actual result of the function is a boxed literal (i.e. Float).
This led to #2097 having core that looked like

```
case 1.0# of
  F# x -> ...
```

which will rightly never match. When the primitives instead return
the correct type we get a case expression

```
case F# 1.0# of
  F# x -> ...
```

where the caseCon transformation can fire.

(cherry picked from commit 31c25df)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants