Skip to content

Commit 62f2d9d

Browse files
authored
Merge pull request #81 from Copilot-Language/T79-IEEE-option
Add `smtFloatMode` option to `VerifierOptions`. Refs #79.
2 parents 737c6af + de7b226 commit 62f2d9d

6 files changed

Lines changed: 232 additions & 77 deletions

File tree

copilot-verifier/CHANGELOG

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
2025-01-31
1+
2025-02-03
22
* Add `smtSolver` option to `VerifierOptions`. (#78)
3+
* Add `smtFloatMode` option to `VerifierOptions`. (#79)
34

45
2025-01-20
56
* Version bump (4.2). (#76)

copilot-verifier/copilot-verifier.cabal

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ library
7070
hs-source-dirs: src
7171
exposed-modules:
7272
Copilot.Verifier
73+
Copilot.Verifier.FloatMode
7374
Copilot.Verifier.Log
7475
Copilot.Verifier.Solver
7576

@@ -81,6 +82,7 @@ library copilot-verifier-examples
8182
case-insensitive,
8283
copilot >= 4.2 && < 4.3,
8384
copilot-language >= 4.2 && < 4.3,
85+
copilot-libraries >= 4.2 && < 4.3,
8486
copilot-prettyprinter >= 4.2 && < 4.3,
8587
copilot-verifier
8688
exposed-modules:
@@ -104,6 +106,7 @@ library copilot-verifier-examples
104106
Copilot.Verifier.Examples.ShouldPass.Clock
105107
Copilot.Verifier.Examples.ShouldPass.Counter
106108
Copilot.Verifier.Examples.ShouldPass.Engine
109+
Copilot.Verifier.Examples.ShouldPass.FPNegation
107110
Copilot.Verifier.Examples.ShouldPass.FPOps
108111
Copilot.Verifier.Examples.ShouldPass.Heater
109112
Copilot.Verifier.Examples.ShouldPass.IntOps

copilot-verifier/examples/Copilot/Verifier/Examples.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import qualified Copilot.Verifier.Examples.ShouldPass.Arith a
2828
import qualified Copilot.Verifier.Examples.ShouldPass.Clock as Clock
2929
import qualified Copilot.Verifier.Examples.ShouldPass.Counter as Counter
3030
import qualified Copilot.Verifier.Examples.ShouldPass.Engine as Engine
31+
import qualified Copilot.Verifier.Examples.ShouldPass.FPNegation as FPNegation
3132
import qualified Copilot.Verifier.Examples.ShouldPass.FPOps as FPOps
3233
import qualified Copilot.Verifier.Examples.ShouldPass.Heater as Heater
3334
import qualified Copilot.Verifier.Examples.ShouldPass.IntOps as IntOps
@@ -70,6 +71,7 @@ shouldPassExamples verb = Map.fromList
7071
, example "Clock" (Clock.verifySpec verb)
7172
, example "Counter" (Counter.verifySpec verb)
7273
, example "Engine" (Engine.verifySpec verb)
74+
, example "FPNegation" (FPNegation.verifySpec verb)
7375
, example "FPOps" (FPOps.verifySpec verb)
7476
, example "Heater" (Heater.verifySpec verb)
7577
, example "IntOps" (IntOps.verifySpec verb)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{-# LANGUAGE NoImplicitPrelude #-}
2+
3+
-- | This will not succeed when 'smtFloatMode' is 'FloatUninterpreted', as Clang
4+
-- will turn @input /= 30.0@ below into a more complicated expression that also
5+
-- checks if @30.0@ is NaN or not. This is logically equivalent to the original
6+
-- specification, but an SMT solver cannot conclude this when all of the
7+
-- floating-point operations involved are treated as uninterpreted functions.
8+
--
9+
-- To make this work, we set 'smtFloatMode' to 'FloatIEEE' instead. This works
10+
-- because all of the floating-point operations in the specification below are
11+
-- native to SMT-LIB.
12+
module Copilot.Verifier.Examples.ShouldPass.FPNegation where
13+
14+
import Copilot.Compile.C99 (mkDefaultCSettings)
15+
import Copilot.Verifier ( Verbosity, VerifierOptions(..)
16+
, defaultVerifierOptions, verifyWithOptions )
17+
import Copilot.Verifier.FloatMode (FloatMode(..))
18+
import qualified Copilot.Library.PTLTL as PTLTL
19+
import Language.Copilot
20+
21+
input :: Stream Float
22+
input = extern "input" Nothing
23+
24+
-- | MyProperty
25+
-- @
26+
-- Input is never 30.0
27+
-- @
28+
propMyProperty :: Stream Bool
29+
propMyProperty = PTLTL.alwaysBeen (input /= 30.0)
30+
31+
-- | Clock that increases in one-unit steps.
32+
clock :: Stream Int64
33+
clock = [0] ++ (clock + 1)
34+
35+
-- | First Time Point
36+
ftp :: Stream Bool
37+
ftp = [True] ++ false
38+
39+
pre :: Stream Bool -> Stream Bool
40+
pre = ([False] ++)
41+
42+
tpre :: Stream Bool -> Stream Bool
43+
tpre = ([True] ++)
44+
45+
notPreviousNot :: Stream Bool -> Stream Bool
46+
notPreviousNot = not . PTLTL.previous . not
47+
48+
-- | Complete specification. Calls C handler functions when properties are
49+
-- violated.
50+
spec :: Spec
51+
spec = do
52+
trigger "handlerMyProperty" (not propMyProperty) []
53+
54+
verifySpec :: Verbosity -> IO ()
55+
verifySpec verb = do
56+
spec' <- reify spec
57+
verifyWithOptions
58+
(defaultVerifierOptions
59+
{ verbosity = verb
60+
, smtFloatMode = FloatIEEE
61+
})
62+
mkDefaultCSettings [] "fpNegation" spec'

copilot-verifier/src/Copilot/Verifier.hs

Lines changed: 107 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
{-# LANGUAGE DeriveGeneric #-}
44
{-# LANGUAGE DerivingStrategies #-}
55
{-# LANGUAGE ExplicitNamespaces #-}
6+
{-# LANGUAGE FlexibleContexts #-}
67
{-# LANGUAGE GADTs #-}
78
{-# LANGUAGE OverloadedStrings #-}
89
{-# LANGUAGE ImplicitParams #-}
@@ -49,6 +50,7 @@ import qualified Copilot.Core.Type as CT
4950

5051
import qualified Copilot.Theorem.What4 as CW4
5152

53+
import qualified Copilot.Verifier.FloatMode as FloatMode
5254
import qualified Copilot.Verifier.Log as Log
5355
import qualified Copilot.Verifier.Solver as Solver
5456

@@ -73,10 +75,10 @@ import Lang.Crucible.Backend
7375
, labeledPred, labeledPredMsg
7476
-- , ProofObligations, proofGoal, goalsToList, labeledPredMsg
7577
)
76-
import Lang.Crucible.Backend.Simple (newSimpleBackend)
78+
import Lang.Crucible.Backend.Simple (SimpleBackend, newSimpleBackend)
7779
import Lang.Crucible.CFG.Core (AnyCFG(..), cfgArgTypes, cfgReturnType)
7880
import Lang.Crucible.CFG.Common ( freshGlobalVar )
79-
import Lang.Crucible.FunctionHandle (newHandleAllocator)
81+
import Lang.Crucible.FunctionHandle (HandleAllocator, newHandleAllocator)
8082
import Lang.Crucible.Simulator
8183
( SimContext(..), ctxSymInterface, ExecResult(..), ExecState(..)
8284
, defaultAbortHandler, runOverrideSim, partialValue, gpValue
@@ -151,7 +153,7 @@ import What4.Interface
151153
, getAnnotation, natAdd, natEq, natIte, natLit
152154
)
153155
import What4.Expr.Builder
154-
( FloatModeRepr(..), ExprBuilder, BoolExpr, startCaching
156+
( Flags, ExprBuilder, BoolExpr, startCaching
155157
, newExprBuilder
156158
)
157159
import What4.FunctionName (functionName)
@@ -204,6 +206,20 @@ data VerifierOptions = VerifierOptions
204206
-- configurable in the future.
205207
, smtSolver :: Solver.Solver
206208
-- ^ Which SMT solver to use when solving proof goals.
209+
, smtFloatMode :: FloatMode.FloatMode
210+
-- ^ How the verifier should interpret floating-point operations when
211+
-- translating them to SMT.
212+
--
213+
-- By default, the verifier will treat all floating-point operations as
214+
-- uninterpreted functions ('FloatMode.FloatUninterpreted'). This allows the
215+
-- verifier to perform some limited reasoning about floating-point
216+
-- operations that SMT solvers do not have built-in operations for (@sin@,
217+
-- @cos@, @tan@, etc.), but at the expense of not being able to verify C
218+
-- code in which the compiler optimizes floating-point expressions. One can
219+
-- also opt into an alternative mode where floating-point values are treated
220+
-- as IEEE-754 floats ('FloatMode.FloatIEEE'), but this comes with the
221+
-- drawback that the verifier will not be able to perform /any/ reasoning
222+
-- about @sin@, @cos@, @tan@, etc.
207223
} deriving stock Show
208224

209225
-- | The default 'VerifierOptions':
@@ -216,12 +232,16 @@ data VerifierOptions = VerifierOptions
216232
-- * Do not log any SMT solver interactions.
217233
--
218234
-- * Use the Z3 SMT solver.
235+
--
236+
-- * Treat all floating-point operations as uninterpreted functions when
237+
-- translating to SMT.
219238
defaultVerifierOptions :: VerifierOptions
220239
defaultVerifierOptions = VerifierOptions
221240
{ verbosity = Default
222241
, assumePartialSideConds = False
223242
, logSmtInteractions = False
224243
, smtSolver = Solver.Z3
244+
, smtFloatMode = FloatMode.FloatUninterpreted
225245
}
226246

227247
-- | Like 'defaultVerifierOptions', except that the verifier will assume side
@@ -335,86 +355,97 @@ verifyBitcode ::
335355
FilePath {- ^ Path to the bitcode file to verify -} ->
336356
IO ()
337357
verifyBitcode opts csettings properties spec cruxOpts llvmOpts cFile bcFile =
358+
FloatMode.withFloatMode (smtFloatMode opts) $ \fm ->
338359
do -- Set up the expression builder and symbolic backend
339360
halloc <- newHandleAllocator
340-
(sym :: ExprBuilder t st fs) <-
341-
newExprBuilder FloatUninterpretedRepr CopilotVerifierData globalNonceGenerator
361+
sym <- newExprBuilder fm CopilotVerifierData globalNonceGenerator
342362
bak <- newSimpleBackend sym
343363
-- turn on hash-consing
344364
startCaching sym
345365

346-
-- capture LLVM side-condition information for use in error reporting
347-
clRefs <- newCopilotLogRefs
348-
let ?recordLLVMAnnotation = recordLLVMAnnotation clRefs
349-
350-
-- Set up the solver to use for verification.
351-
let adapter :: SolverAdapter st
352-
adapter = Solver.solverAdapter (smtSolver opts)
353-
extendConfig (solver_adapter_config_options adapter) (getConfiguration sym)
354-
355-
-- Set up the Crucible/LLVM simulation context
356-
memVar <- mkMemVar "llvm_memory" halloc
357-
let simctx = (setupSimCtxt halloc bak (memOpts llvmOpts) memVar)
358-
{ printHandle = view Log.outputHandle ?outputConfig }
359-
360-
-- Load and translate the input LLVM module
361-
llvmMod <- parseLLVM bcFile
362-
Some trans <-
363-
let ?transOpts = transOpts llvmOpts
364-
in translateModule halloc memVar llvmMod
365-
366-
Log.sayCopilot Log.TranslatedToCrucible
367-
368-
-- Grab some metadata from the bitcode file and options;
369-
-- make the available via implicit arguments to the places
370-
-- that expect them.
371-
let llvmCtxt = trans ^. transContext
372-
let ?lc = llvmCtxt ^. llvmTypeCtx
373-
let ?memOpts = memOpts llvmOpts
374-
let ?intrinsicsOpts = intrinsicsOpts llvmOpts
375-
376-
llvmPtrWidth llvmCtxt $ \ptrW ->
377-
withPtrWidth ptrW $
378-
379-
do -- Compute the LLVM memory state with global variables allocated
380-
-- but not initialized
381-
emptyMem <- initializeAllMemory bak llvmCtxt llvmMod
382-
383-
-- Compute the LLVM memory state with global variables initialized
384-
-- to their initial values.
385-
initialMem <- populateAllGlobals bak (trans ^. globalInitMap) emptyMem
386-
387-
-- Use the Copilot spec directly to compute the symbolic states
388-
-- necessary to carry out the states of the bisimulation proof.
389-
Log.sayCopilot Log.GeneratingProofState
390-
proofStateBundle <- CW4.computeBisimulationProofBundle sym properties spec
391-
392-
-- First check that the initial state of the program matches the starting
393-
-- segment of the associated Copilot streams.
394-
let cruxOptsInit = setCruxOfflineSolverOutput "initial-step" cruxOpts
395-
initGoals <-
396-
verifyInitialState cruxOptsInit [adapter] clRefs simctx initialMem
397-
(CW4.initialStreamState proofStateBundle)
398-
399-
-- Now, the real meat. Carry out the bisimulation step of the proof.
400-
let cruxOptsTrans = setCruxOfflineSolverOutput "transition-step" cruxOpts
401-
bisimGoals <-
402-
verifyStepBisimulation opts cruxOptsTrans [adapter] csettings
403-
clRefs simctx llvmMod trans memVar initialMem proofStateBundle
404-
405-
Log.sayCopilot $ Log.SuccessfulProofSummary cFile initGoals bisimGoals
406-
-- We only want to inform users about Noisy if the verbosity level is
407-
-- sufficiently low. Crux's logging framework isn't particularly
408-
-- suited to doing this, as it assumes that all log messages enabled
409-
-- for low verbosity levels should also be enabled for higher
410-
-- verbosity levels. That is a reasonable assumption most of the time,
411-
-- but not here.
412-
--
413-
-- To compensate, we hack around the issue by manually checking the
414-
-- verbosity level before logging the message.
415-
when (verbosity opts < Noisy) $
416-
Log.sayCopilot Log.NoisyVerbositySuggestion
366+
FloatMode.withInterpretedFloatExprBuilder sym fm $
367+
verifyWithSymBackend bak halloc
417368
where
369+
verifyWithSymBackend ::
370+
forall t st fm.
371+
IsInterpretedFloatExprBuilder (ExprBuilder t st (Flags fm)) =>
372+
SimpleBackend t st (Flags fm) ->
373+
HandleAllocator ->
374+
IO ()
375+
verifyWithSymBackend bak halloc = do
376+
let sym = backendGetSym bak
377+
-- capture LLVM side-condition information for use in error reporting
378+
clRefs <- newCopilotLogRefs
379+
let ?recordLLVMAnnotation = recordLLVMAnnotation clRefs
380+
381+
-- Set up the solver to use for verification.
382+
let adapter :: SolverAdapter st
383+
adapter = Solver.solverAdapter (smtSolver opts)
384+
extendConfig (solver_adapter_config_options adapter) (getConfiguration sym)
385+
386+
-- Set up the Crucible/LLVM simulation context
387+
memVar <- mkMemVar "llvm_memory" halloc
388+
let simctx = (setupSimCtxt halloc bak (memOpts llvmOpts) memVar)
389+
{ printHandle = view Log.outputHandle ?outputConfig }
390+
391+
-- Load and translate the input LLVM module
392+
llvmMod <- parseLLVM bcFile
393+
Some trans <-
394+
let ?transOpts = transOpts llvmOpts
395+
in translateModule halloc memVar llvmMod
396+
397+
Log.sayCopilot Log.TranslatedToCrucible
398+
399+
-- Grab some metadata from the bitcode file and options;
400+
-- make the available via implicit arguments to the places
401+
-- that expect them.
402+
let llvmCtxt = trans ^. transContext
403+
let ?lc = llvmCtxt ^. llvmTypeCtx
404+
let ?memOpts = memOpts llvmOpts
405+
let ?intrinsicsOpts = intrinsicsOpts llvmOpts
406+
407+
llvmPtrWidth llvmCtxt $ \ptrW ->
408+
withPtrWidth ptrW $
409+
410+
do -- Compute the LLVM memory state with global variables allocated
411+
-- but not initialized
412+
emptyMem <- initializeAllMemory bak llvmCtxt llvmMod
413+
414+
-- Compute the LLVM memory state with global variables initialized
415+
-- to their initial values.
416+
initialMem <- populateAllGlobals bak (trans ^. globalInitMap) emptyMem
417+
418+
-- Use the Copilot spec directly to compute the symbolic states
419+
-- necessary to carry out the states of the bisimulation proof.
420+
Log.sayCopilot Log.GeneratingProofState
421+
proofStateBundle <- CW4.computeBisimulationProofBundle sym properties spec
422+
423+
-- First check that the initial state of the program matches the starting
424+
-- segment of the associated Copilot streams.
425+
let cruxOptsInit = setCruxOfflineSolverOutput "initial-step" cruxOpts
426+
initGoals <-
427+
verifyInitialState cruxOptsInit [adapter] clRefs simctx initialMem
428+
(CW4.initialStreamState proofStateBundle)
429+
430+
-- Now, the real meat. Carry out the bisimulation step of the proof.
431+
let cruxOptsTrans = setCruxOfflineSolverOutput "transition-step" cruxOpts
432+
bisimGoals <-
433+
verifyStepBisimulation opts cruxOptsTrans [adapter] csettings
434+
clRefs simctx llvmMod trans memVar initialMem proofStateBundle
435+
436+
Log.sayCopilot $ Log.SuccessfulProofSummary cFile initGoals bisimGoals
437+
-- We only want to inform users about Noisy if the verbosity level is
438+
-- sufficiently low. Crux's logging framework isn't particularly
439+
-- suited to doing this, as it assumes that all log messages enabled
440+
-- for low verbosity levels should also be enabled for higher
441+
-- verbosity levels. That is a reasonable assumption most of the time,
442+
-- but not here.
443+
--
444+
-- To compensate, we hack around the issue by manually checking the
445+
-- verbosity level before logging the message.
446+
when (verbosity opts < Noisy) $
447+
Log.sayCopilot Log.NoisyVerbositySuggestion
448+
418449
-- If @logSmtInteractions@ is enabled, enable offline solver output in the
419450
-- supplied 'CruxOptions' with the supplied file template. Otherwise, return
420451
-- the supplied 'CruxOptions' unaltered.

0 commit comments

Comments
 (0)