generated from tweag/project
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathZ3.hs
131 lines (112 loc) · 4.6 KB
/
Z3.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
{-# LANGUAGE CApiFFI #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
-- | A module providing a backend that sends commands to Z3 using its C API.
module SMTLIB.Backends.Z3
( Config (..),
Handle,
defaultConfig,
new,
close,
with,
toBackend,
)
where
import Control.Exception (bracket)
import Control.Monad (forM_, void)
import qualified Data.ByteString as BS
import Data.ByteString.Builder.Extra
( defaultChunkSize,
smallChunkSize,
toLazyByteStringWith,
untrimmedStrategy,
)
import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Data.ByteString.Unsafe
import Foreign.C.String (CString)
import Foreign.ForeignPtr (ForeignPtr, finalizeForeignPtr, newForeignPtr, withForeignPtr)
import Foreign.Ptr (FunPtr, Ptr, nullPtr)
import SMTLIB.Backends (Backend (..))
data Z3Context
data Z3Config
newtype Config = Config
{ -- | A list of options to set during the solver's initialization.
-- Each pair is of the form @(paramId, paramValue)@, e.g.
-- @(":produce-models", "true")@.
--
-- Note that Z3 has different kinds of parameters, and not all of
-- them can be set here. In particular, there are the so called
-- global and module parameters with a value that affects
-- all solver instances. We have found some of these global
-- parameters to be ignored when provided here. You might have more
-- luck setting them after starting the solver:
--
-- > command_ solver "(set-option :parameter_name value)"
--
-- Or using `Z3_global_param_set` from the Z3 API directly.
--
-- > foreign import capi unsafe "z3.h Z3_global_param_set" c_Z3_global_param_set :: CString -> CString -> IO ()
parameters :: [(BS.ByteString, BS.ByteString)]
}
-- | By default, don't set any options during initialization.
defaultConfig :: Config
defaultConfig = Config []
newtype Handle = Handle
{ -- | A black-box representing the internal state of the solver.
context :: ForeignPtr Z3Context
}
foreign import capi unsafe "z3.h &Z3_del_context" c_Z3_del_context :: FunPtr (Ptr Z3Context -> IO ())
foreign import capi unsafe "z3.h Z3_set_param_value" c_Z3_set_param_value :: Ptr Z3Config -> CString -> CString -> IO ()
foreign import capi unsafe "z3.h Z3_mk_config" c_Z3_mk_config :: IO (Ptr Z3Config)
foreign import capi unsafe "z3.h Z3_mk_context" c_Z3_mk_context :: Ptr Z3Config -> IO (Ptr Z3Context)
foreign import capi unsafe "z3.h Z3_del_config" c_Z3_del_config :: Ptr Z3Config -> IO ()
foreign import capi unsafe "z3.h Z3_set_error_handler" c_Z3_set_error_handler :: Ptr Z3Context -> Ptr () -> IO ()
-- We use ccall to avoid warnings about constness in the C side
-- In the meantime we check in cbits/z3.c that the type of the function is
-- compatible.
foreign import ccall unsafe "z3.h Z3_eval_smtlib2_string" c_Z3_eval_smtlib2_string :: Ptr Z3Context -> CString -> IO CString
-- | Create a new solver instance.
new :: Config -> IO Handle
new config = do
-- we don't set a finalizer for this object as we manually delete it during the
-- context's creation
cfg <- c_Z3_mk_config
forM_ (parameters config) $ \(paramId, paramValue) ->
BS.useAsCString paramId $ \cparamId ->
BS.useAsCString paramValue $ \cparamValue ->
c_Z3_set_param_value cfg cparamId cparamValue
{-
We set the error handler to ignore errors. That way if an error happens it doesn't
cause the whole program to crash, and the error message is simply transmitted to
the Haskell layer inside the output of the 'send' method.
-}
pctx <- c_Z3_mk_context cfg
c_Z3_del_config cfg
c_Z3_set_error_handler pctx nullPtr
ctx <- newForeignPtr c_Z3_del_context pctx
return $ Handle ctx
-- | Release the resources associated with a Z3 instance.
close :: Handle -> IO ()
close = finalizeForeignPtr . context
-- | Create a Z3 instance, use it to run a computation and release its resources.
with :: Config -> (Handle -> IO a) -> IO a
with config = bracket (new config) close
-- | Create a solver backend out of a Z3 instance.
toBackend :: Handle -> Backend
toBackend handle = Backend backendSend backendSend_
where
backendSend cmd = do
let ctx = context handle
let cmd' =
LBS.toStrict $
toLazyByteStringWith
(untrimmedStrategy smallChunkSize defaultChunkSize)
"\NUL"
cmd
Data.ByteString.Unsafe.unsafeUseAsCString cmd' $ \ccmd' ->
withForeignPtr ctx $ \pctx -> do
resp <- c_Z3_eval_smtlib2_string pctx ccmd'
LBS.fromStrict <$> BS.packCString resp
backendSend_ = void . backendSend