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
5051import qualified Copilot.Theorem.What4 as CW4
5152
53+ import qualified Copilot.Verifier.FloatMode as FloatMode
5254import qualified Copilot.Verifier.Log as Log
5355import 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 )
7779import Lang.Crucible.CFG.Core (AnyCFG (.. ), cfgArgTypes , cfgReturnType )
7880import Lang.Crucible.CFG.Common ( freshGlobalVar )
79- import Lang.Crucible.FunctionHandle (newHandleAllocator )
81+ import Lang.Crucible.FunctionHandle (HandleAllocator , newHandleAllocator )
8082import 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 )
153155import What4.Expr.Builder
154- ( FloatModeRepr ( .. ) , ExprBuilder , BoolExpr , startCaching
156+ ( Flags , ExprBuilder , BoolExpr , startCaching
155157 , newExprBuilder
156158 )
157159import 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.
219238defaultVerifierOptions :: VerifierOptions
220239defaultVerifierOptions = 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 ()
337357verifyBitcode 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