Skip to content

Special Float values are translated incorrectly #697

Description

@RyanGlScott

Description

The following functions use realToFrac in their implementations to translate Float values.

The realToFrac function can mis-translate special floating-point values, such as negative zero, infinity, and NaN. This can cause copilot-bluespec to generate unexpected Bluespec code, and it can cause copilot-theorem to produce incorrect counterexamples.

Type

  • Bug: incorrect generated code.

Additional context

Requester

  • Ryan Scott (Galois)

Method to check presence of bug

The following search finds mentions of realToFrac in the implementation:

$ grep -nHre 'realToFrac' copilot**/src copilot**/tests
copilot-bluespec/src/Copilot/Compile/Bluespec/Expr.hs:539: Float -> constFP ty . realToFrac
copilot-theorem/src/Copilot/Theorem/What4.hs:710: (realToFrac . fst . bfToDouble NearEven)

At a minimum, these should be removed.

It is sometimes possible to test for the presence of incorrect results produced by realToFrac, depending on GHC versions and optimization levels. The following test case has been observed to demonstrate incorrect generated code from copilot-bluespec:

-- Test.hs
{-# LANGUAGE NoImplicitPrelude #-}
moduleMain (main) whereimportqualifiedCopilot.Compile.BluespecasBluespecimportLanguage.Copilotnans::StreamFloat
nans = [read"NaN"] ++ nans
infinities::StreamFloat
infinities = [inf, -inf] ++ infinities
whereinf::Float
inf =read"Infinity"zeroes::StreamFloat
zeroes = [0, -0] ++ zeroes
spec::Spec
spec =
trigger "rtf" true [arg nans, arg infinities, arg zeroes]
main::IO()
main =do
interpret 2 spec
spec' <- reify spec
Bluespec.compile "Test" spec'

Compare the results of the Copilot interpreter, which handles special Float values correctly:

$ runghc Test.hs
rtf:
(NaN,Infinity,0.0)
(NaN,-Infinity,-0.0)

to the generated Bluespec code, where NaN is mistranslated to 5.104235503814077e38 (i.e., infinity) and negative zero is mistranslated to zero:

 s0_0 :: Prelude.Reg Float <- mkReg (5.104235503814077e38::Float);let{ s0 ::Vector.Vector 1(Prelude.Reg Float);
s0 = update newVector 0 s0_0;};
s1_0 :: Prelude.Reg Float <- mkReg (3.402823669209385e38::Float);
s1_1 :: Prelude.Reg Float <- mkReg
((Prelude.negate 3.402823669209385e38)::Float);let{ s1 ::Vector.Vector 2(Prelude.Reg Float);
s1 = update (update newVector 0 s1_0)1 s1_1;};
s2_0 :: Prelude.Reg Float <- mkReg (0.0::Float);
s2_1 :: Prelude.Reg Float <- mkReg (0.0::Float);

In addition, copilot-theorem generates an incorrect counterexample for the following program when Copilot is compiled without optimizations:

-- Test.hsmoduleMain (main) whereimportqualifiedPreludeasPimportControl.Monad (void, forM_)
importqualifiedData.MapasMapimportLanguage.CopilotimportCopilot.Theorem.What4spec::Spec
spec =let floats ::StreamFloat
floats = extern "floats"Nothingin
void $ prop "refl"$ forAll (floats <= floats)
main::IO()
main =do
spec' <- reify spec
-- Use Z3 to prove the properties.
results <- proveWithCounterExample Z3 spec'
-- Print the results.
forM_ results $\(nm, res) ->doputStr$ nm <>": "case res ofValidCex->putStrLn"valid"InvalidCex cex ->doputStrLn"invalid"putStrLn$ ppCounterExample cex
UnknownCex->putStrLn"unknown"--| Pretty-print a counterexample for user display.ppCounterExample::CounterExample->String
ppCounterExample cex
|anyP.not (baseCases cex)
=ifMap.null baseCaseVals
then" All possible extern values during the base case(s) "P.++"constitute a counterexample."elseunlines$" The base cases failed with the following extern values:":map
(\((name, _), val) ->""P.++ name P.++": "P.++show val)
(Map.toList baseCaseVals)
|P.not (inductionStep cex)
=ifMap.null inductionStepVals
then" All possible extern values during the induction step "P.++"constitute a counterexample."elseunlines$" The induction step failed with the following extern values:":map
(\((name, _), val) ->""P.++ name P.++": "P.++show val)
(Map.toList inductionStepVals)
|otherwise=error$"ppCounterExample: "P.++"Counterexample without failing base cases or induction step"where
allExternVals = concreteExternValues cex
baseCaseVals =Map.filterWithKey
(\(_, offset) _ ->case offset ofAbsoluteOffset {} ->TrueRelativeOffset {} ->False
)
allExternVals
inductionStepVals =Map.filterWithKey
(\(_, offset) _ ->case offset ofAbsoluteOffset {} ->FalseRelativeOffset {} ->True
)
allExternVals
$ cabal build copilot copilot-bluespec --write-ghc-environment-file=always --disable-optimization
$ runghc Test.hs
refl: invalid
The induction step failed with the following extern values:
floats: Infinity

That last line should read floats: NaN, not reads: Infinity.

Expected result

copilot-bluespec and copilot-theorem should not use realToFrac to translate floating point values, and should instead use a function that translates NaN and special values correctly.

Desired result

copilot-bluespec and copilot-theorem should not use realToFrac to translate floating point values, and should instead use a function that translates NaN and special values correctly.

Proposed solution

Replace uses of realToFrac in copilot-bluespec and copilot-theorem with calls to a function that converts floats correctly including special cases such as NaN and negative zero.

Further notes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

CR:Status:ClosedAdmin only: Change request that has been completedCR:Type:BugAdmin only: Change request pertaining to error detected

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions