mirror of
https://github.com/Z3Prover/z3
synced 2025-04-09 19:01:50 +00:00
608 lines
15 KiB
Plaintext
608 lines
15 KiB
Plaintext
/*++
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
Z3
|
|
|
|
Abstract:
|
|
|
|
OCaml API for Z3.
|
|
|
|
The following design is used for the treatment of reference counting:
|
|
|
|
- The conversion of Z3_context from ML to C remembers the Z3 context, and
|
|
registers a finalizer using Gc.finalize that calls Z3_del_context.
|
|
|
|
- The conversion of Z3_ast and other reference counted types from C to ML:
|
|
|
|
+ stores the last translated context with the Z3_ast in the wrapper
|
|
object;
|
|
|
|
+ registers a finalizer using Gc.finalize that decrements the reference
|
|
counter of the Z3_ast;
|
|
|
|
+ increments the reference count of the Z3_ast.
|
|
|
|
The finalizers are registered using (the C interface to) Gc.finalize,
|
|
not attaching a finalizer to a custom block. The finalizers registered
|
|
by Gc.finalize are guaranteed to be called in reverse
|
|
registration-order, which is necessary to ensure that Z3_context's are
|
|
finalized only after all the Z3_ast's within them.
|
|
|
|
- ML Z3.ast (and subtypes) are given generic hash and comparison
|
|
operations using Z3_get_ast_hash and Z3_get_ast_id. Other types could
|
|
be handled similarly if analogous hash and id operations were exported
|
|
by the C API.
|
|
|
|
- The wrapper for Z3_mk_context is customized (using quote(call,...) in
|
|
z3_api.patched.h) to call Z3_mk_context_rc, and the ML API does not
|
|
include mk_context_rc.
|
|
|
|
This scheme relies on the property that all reference counted values
|
|
returned from C to ML are in the Z3_context that was last sent from ML to
|
|
C. This is normally straightforward, but note that it depends on the
|
|
argument order of e.g. the Z3_*translate functions.
|
|
|
|
Non-reference counted Z3 types that have delete operations have finalizers
|
|
that call the delete operations. The exposed delete operations are
|
|
shadowed by nop functions. The types whose delete operation accepts a
|
|
context use Gc.finalize while those that do not use custom block
|
|
finalizers.
|
|
|
|
Custom c2ml functions check the Z3 error code prior to allocating ML
|
|
values or registering finalizers. Other functions check the Z3 error code
|
|
after making a Z3 library call.
|
|
|
|
Some operations return NULL pointers when operations fail, or accept NULL
|
|
pointers. To handle these cases Z3_{ast,func_interp,sort}_opt types are
|
|
introduced. These are synonyms of Z3_{ast,func_interp,sort} but are
|
|
translated into OCaml option types. If the NULL pointers were passed to
|
|
ML, even if the user does not access them, they will have finalizers
|
|
registered, so when they die the OCaml GC will crash trying to call
|
|
dec_ref on NULL.
|
|
|
|
There is an alternate implementation, enabled by setting LEAK_CONTEXTS,
|
|
that avoids the overhead of Gc.finalize finalizers, but at the price of
|
|
leaking Z3_context objects.
|
|
|
|
Notes:
|
|
|
|
OCaml does not support unsigned types, so CamlIDL conflates signed and
|
|
unsigned types of the same size. Therefore, functions in the C API
|
|
operating on unsigned values that become redundant after this conflation
|
|
are excluded from the ML API using [#ifndef CAMLIDL] in z3_api.h.
|
|
|
|
CamlIDL does not support function pointers, so functions in the C API with
|
|
function pointer arguments are handled manually.
|
|
|
|
Author:
|
|
|
|
Jakob Lichtenberg (JakobL) 2007-08-08
|
|
Josh Berdine (jjb) 2012-03-21
|
|
|
|
--*/
|
|
|
|
|
|
// cpp trick to include expanded macro arguments in string literals
|
|
#define xstr(s) str(s)
|
|
#define str(s) #s
|
|
quote(c,"#define xstr(s) str(s)");
|
|
quote(c,"#define str(s) #s");
|
|
|
|
|
|
// CamlIDL (1.05) has a bug where it does not accept [unsigned] as a type,
|
|
// only as a specifier, so unsigned is defined to be unsigned int.
|
|
#define unsigned unsigned int
|
|
|
|
|
|
// Suppress "warning C4090: 'function' : different 'const' qualifiers" as
|
|
// CamlIDL does not seem to get this right.
|
|
quote(c,"#pragma warning(disable:4090)");
|
|
|
|
|
|
#ifndef MLAPIV3
|
|
|
|
#define DEFINE_TYPE(T) typedef [abstract] void* T
|
|
#define DEFINE_VOID(T) typedef [abstract] void* T
|
|
|
|
#define BEGIN_MLAPI_EXCLUDE quote(mli,"(*");
|
|
#define END_MLAPI_EXCLUDE quote(mli,"*)");
|
|
|
|
#ifdef LEAK_CONTEXTS
|
|
|
|
// Declare pointer type with custom conversion functions.
|
|
#define DEFINE_CUST_TYPE(T) \
|
|
typedef [abstract, ml2c(ml2c_Z3_ ## T), c2ml(c2ml_Z3_ ## T)] void* Z3_ ## T
|
|
|
|
#else
|
|
|
|
// Declare pointer type with custom conversion functions.
|
|
// Register an OCaml closure that just calls a C finalization function.
|
|
#define DEFINE_CUST_TYPE(T) \
|
|
quote(ml,xstr(\
|
|
external finalize_Z3_ ## T : T -> unit = xstr(finalize_Z3_ ## T);; \
|
|
let _ = Callback.register xstr(finalize_Z3_ ## T) finalize_Z3_ ## T \
|
|
)); \
|
|
typedef [abstract, ml2c(ml2c_Z3_ ## T), c2ml(c2ml_Z3_ ## T)] void* Z3_ ## T
|
|
|
|
#endif
|
|
|
|
|
|
// Z3_context
|
|
quote(c,"
|
|
void check_error_code (Z3_context c);
|
|
|
|
Z3_context last_ctx;
|
|
");
|
|
|
|
#ifdef LEAK_CONTEXTS
|
|
|
|
quote(c,"
|
|
value c2ml_Z3_context(Z3_context* c)
|
|
{
|
|
value v;
|
|
v = caml_alloc_small(1, Abstract_tag);
|
|
*((Z3_context *) Bp_val(v)) = *c;
|
|
return v;
|
|
}
|
|
|
|
void ml2c_Z3_context(value v, Z3_context* c)
|
|
{
|
|
*c = *((Z3_context *) Bp_val(v));
|
|
last_ctx = *c;
|
|
}
|
|
");
|
|
|
|
#else
|
|
|
|
quote(c,"
|
|
// caml_final_register is the implementation of Gc.finalize
|
|
value caml_final_register (value f, value v);
|
|
|
|
void register_finalizer(value** closure, char* name, Z3_context ctx, value v)
|
|
{
|
|
if (*closure == NULL) {
|
|
*closure = caml_named_value(name);
|
|
if (*closure == NULL) {
|
|
Z3_set_error(ctx, Z3_INTERNAL_FATAL);
|
|
return;
|
|
}
|
|
}
|
|
caml_final_register(**closure, v);
|
|
}
|
|
|
|
value c2ml_Z3_context (Z3_context* c)
|
|
{
|
|
static value* finalize_Z3_context_closure = NULL;
|
|
value v;
|
|
v = caml_alloc_small(1, Abstract_tag);
|
|
Field(v, 0) = (value) *c;
|
|
register_finalizer(&finalize_Z3_context_closure, \"finalize_Z3_context\",
|
|
(Z3_context) *c, v);
|
|
return v;
|
|
}
|
|
|
|
void ml2c_Z3_context (value v, Z3_context* c)
|
|
{
|
|
*c = (Z3_context) Field(v, 0);
|
|
last_ctx = *c;
|
|
}
|
|
|
|
value finalize_Z3_context (value v)
|
|
{
|
|
Z3_context c;
|
|
c = (Z3_context) Field(v, 0);
|
|
Z3_del_context(c);
|
|
return Val_unit;
|
|
}
|
|
");
|
|
|
|
#endif
|
|
|
|
DEFINE_CUST_TYPE(context);
|
|
|
|
|
|
// Z3_symbol
|
|
typedef [abstract] void* Z3_symbol;
|
|
|
|
|
|
// Z3_ast: reference counted type with hashing and comparison
|
|
quote(c,"
|
|
typedef struct _Z3_ast_context {
|
|
Z3_ast ast;
|
|
Z3_context ctx;
|
|
} Z3_ast_context;
|
|
|
|
void ml2c_Z3_ast (value v, Z3_ast* c)
|
|
{
|
|
*c = ((Z3_ast_context*) Data_custom_val(v))->ast;
|
|
}
|
|
|
|
static int compare_Z3_ast (value v1, value v2)
|
|
{
|
|
Z3_ast_context* ac1;
|
|
Z3_ast_context* ac2;
|
|
unsigned id1, id2;
|
|
ac1 = Data_custom_val(v1);
|
|
ac2 = Data_custom_val(v2);
|
|
id1 = Z3_get_ast_id(ac1->ctx, ac1->ast);
|
|
check_error_code(ac1->ctx);
|
|
id2 = Z3_get_ast_id(ac2->ctx, ac2->ast);
|
|
check_error_code(ac2->ctx);
|
|
return id2 - id1;
|
|
}
|
|
|
|
static intnat hash_Z3_ast (value v)
|
|
{
|
|
Z3_ast_context* ac;
|
|
unsigned hash;
|
|
ac = Data_custom_val(v);
|
|
hash = Z3_get_ast_hash(ac->ctx, ac->ast);
|
|
check_error_code(ac->ctx);
|
|
return hash;
|
|
}
|
|
");
|
|
|
|
#ifdef LEAK_CONTEXTS
|
|
|
|
quote(c,"
|
|
static void finalize_Z3_ast (value v)
|
|
{
|
|
Z3_ast_context* ac;
|
|
ac = Data_custom_val(v);
|
|
Z3_dec_ref(ac->ctx, ac->ast);
|
|
check_error_code(ac->ctx);
|
|
}
|
|
|
|
static struct custom_operations cops_Z3_ast = {
|
|
NULL,
|
|
finalize_Z3_ast,
|
|
compare_Z3_ast,
|
|
hash_Z3_ast,
|
|
custom_serialize_default,
|
|
custom_deserialize_default
|
|
};
|
|
|
|
value c2ml_Z3_ast (Z3_ast* c)
|
|
{
|
|
value v;
|
|
Z3_ast_context* ac;
|
|
check_error_code(last_ctx);
|
|
v = alloc_custom(&cops_Z3_ast, sizeof(Z3_ast_context), 0, 1);
|
|
ac = Data_custom_val(v);
|
|
ac->ctx = last_ctx;
|
|
ac->ast = *c;
|
|
Z3_inc_ref(ac->ctx, ac->ast);
|
|
return v;
|
|
}
|
|
");
|
|
|
|
#else
|
|
|
|
quote(c,"
|
|
value finalize_Z3_ast (value v)
|
|
{
|
|
Z3_ast_context* ac;
|
|
ac = Data_custom_val(v);
|
|
Z3_dec_ref(ac->ctx, ac->ast);
|
|
check_error_code(ac->ctx);
|
|
return Val_unit;
|
|
}
|
|
|
|
static struct custom_operations cops_Z3_ast = {
|
|
NULL,
|
|
custom_finalize_default,
|
|
compare_Z3_ast,
|
|
hash_Z3_ast,
|
|
custom_serialize_default,
|
|
custom_deserialize_default
|
|
};
|
|
|
|
value c2ml_Z3_ast (Z3_ast* c)
|
|
{
|
|
static value* finalize_Z3_ast_closure = NULL;
|
|
value v;
|
|
Z3_ast_context* ac;
|
|
check_error_code(last_ctx);
|
|
v = caml_alloc_custom(&cops_Z3_ast, sizeof(Z3_ast_context), 0, 1);
|
|
ac = Data_custom_val(v);
|
|
ac->ast = *c;
|
|
ac->ctx = last_ctx;
|
|
register_finalizer(&finalize_Z3_ast_closure, \"finalize_Z3_ast\",
|
|
(Z3_context) *c, v);
|
|
Z3_inc_ref(last_ctx, *c);
|
|
return v;
|
|
}
|
|
");
|
|
|
|
#endif
|
|
|
|
DEFINE_CUST_TYPE(ast);
|
|
|
|
|
|
// subtypes of Z3_ast
|
|
quote(c,"\
|
|
#define DEFINE_SUBAST_OPS(T) \
|
|
void ml2c_ ## T (value v, T * a) \
|
|
{ \
|
|
ml2c_Z3_ast(v, (Z3_ast*) a); \
|
|
} \
|
|
\
|
|
value c2ml_ ## T (T * a) \
|
|
{ \
|
|
return c2ml_Z3_ast((Z3_ast*) a); \
|
|
} \
|
|
");
|
|
|
|
#define DEFINE_SUBAST(T) \
|
|
typedef [mltype("private ast"), ml2c(ml2c_ ## T), c2ml(c2ml_ ## T)] Z3_ast T
|
|
|
|
quote(c,"DEFINE_SUBAST_OPS(Z3_sort)"); DEFINE_SUBAST(Z3_sort);
|
|
quote(c,"DEFINE_SUBAST_OPS(Z3_func_decl)"); DEFINE_SUBAST(Z3_func_decl);
|
|
quote(c,"DEFINE_SUBAST_OPS(Z3_app)"); DEFINE_SUBAST(Z3_app);
|
|
quote(c,"DEFINE_SUBAST_OPS(Z3_pattern)"); DEFINE_SUBAST(Z3_pattern);
|
|
|
|
|
|
// reference counted types without hashing and comparison
|
|
#ifdef LEAK_CONTEXTS
|
|
|
|
quote(c,"\
|
|
#define DEFINE_RC_OPS(T) \
|
|
typedef struct _ ## T ## _context { \
|
|
T dat; \
|
|
Z3_context ctx; \
|
|
} T ## _context; \
|
|
\
|
|
static void finalize_ ## T (value v) \
|
|
{ \
|
|
T ## _context* ac; \
|
|
ac = Data_custom_val(v); \
|
|
T ## _dec_ref(ac->ctx, ac->dat); \
|
|
check_error_code(ac->ctx); \
|
|
} \
|
|
\
|
|
static struct custom_operations cops_ ## T = { \
|
|
NULL, \
|
|
finalize_ ## T, \
|
|
custom_compare_default, \
|
|
custom_hash_default, \
|
|
custom_serialize_default, \
|
|
custom_deserialize_default \
|
|
}; \
|
|
\
|
|
value c2ml_ ## T (T * c) \
|
|
{ \
|
|
value v; \
|
|
T ## _context* ac; \
|
|
check_error_code(last_ctx); \
|
|
v = alloc_custom(&cops_ ## T, sizeof(T ## _context), 0, 1); \
|
|
ac = Data_custom_val(v); \
|
|
ac->dat = *c; \
|
|
ac->ctx = last_ctx; \
|
|
T ## _inc_ref(ac->ctx, ac->dat); \
|
|
return v; \
|
|
} \
|
|
\
|
|
void ml2c_ ## T (value v, T * c) \
|
|
{ \
|
|
*c = ((T ## _context*) Data_custom_val(v))->dat; \
|
|
} \
|
|
");
|
|
|
|
#else
|
|
|
|
quote(c,"\
|
|
#define DEFINE_RC_OPS(T) \
|
|
value c2ml_ ## T (T * c) \
|
|
{ \
|
|
static value* finalize_ ## T ## _closure = NULL; \
|
|
value v; \
|
|
check_error_code(last_ctx); \
|
|
v = caml_alloc_small(2, Abstract_tag); \
|
|
Field(v, 0) = (value) *c; \
|
|
Field(v, 1) = (value) last_ctx; \
|
|
register_finalizer(&finalize_ ## T ## _closure, xstr(finalize_ ## T), \
|
|
(Z3_context) *c, v); \
|
|
T ## _inc_ref(last_ctx, *c); \
|
|
return v; \
|
|
} \
|
|
\
|
|
void ml2c_ ## T (value v, T * c) \
|
|
{ \
|
|
*c = (T) Field(v, 0); \
|
|
} \
|
|
\
|
|
value finalize_ ## T (value v) \
|
|
{ \
|
|
Z3_context c; \
|
|
c = (Z3_context) Field(v, 1); \
|
|
T ## _dec_ref(c, (T) Field(v, 0)); \
|
|
check_error_code(c); \
|
|
return Val_unit; \
|
|
} \
|
|
");
|
|
|
|
#endif
|
|
|
|
quote(c,"DEFINE_RC_OPS(Z3_params)"); DEFINE_CUST_TYPE(params);
|
|
quote(c,"DEFINE_RC_OPS(Z3_param_descrs)"); DEFINE_CUST_TYPE(param_descrs);
|
|
quote(c,"DEFINE_RC_OPS(Z3_model)"); DEFINE_CUST_TYPE(model);
|
|
quote(c,"DEFINE_RC_OPS(Z3_func_interp)"); DEFINE_CUST_TYPE(func_interp);
|
|
quote(c,"DEFINE_RC_OPS(Z3_func_entry)"); DEFINE_CUST_TYPE(func_entry);
|
|
quote(c,"DEFINE_RC_OPS(Z3_fixedpoint)"); DEFINE_CUST_TYPE(fixedpoint);
|
|
quote(c,"DEFINE_RC_OPS(Z3_ast_vector)"); DEFINE_CUST_TYPE(ast_vector);
|
|
quote(c,"DEFINE_RC_OPS(Z3_ast_map)"); DEFINE_CUST_TYPE(ast_map);
|
|
quote(c,"DEFINE_RC_OPS(Z3_goal)"); DEFINE_CUST_TYPE(goal);
|
|
quote(c,"DEFINE_RC_OPS(Z3_tactic)"); DEFINE_CUST_TYPE(tactic);
|
|
quote(c,"DEFINE_RC_OPS(Z3_probe)"); DEFINE_CUST_TYPE(probe);
|
|
quote(c,"DEFINE_RC_OPS(Z3_apply_result)"); DEFINE_CUST_TYPE(apply_result);
|
|
quote(c,"DEFINE_RC_OPS(Z3_solver)"); DEFINE_CUST_TYPE(solver);
|
|
quote(c,"DEFINE_RC_OPS(Z3_stats)"); DEFINE_CUST_TYPE(stats);
|
|
|
|
|
|
// possibly-NULL pointer types, translated to OCaml option types
|
|
quote(c,"\
|
|
#define DEFINE_OPT_OPS(T) \
|
|
void ml2c_ ## T ## _opt (value v, T* c) \
|
|
{ \
|
|
struct camlidl_ctx_struct _ctxs = { CAMLIDL_TRANSIENT, NULL }; \
|
|
camlidl_ctx _ctx = &_ctxs; \
|
|
if (v != Val_int(0)) { \
|
|
camlidl_ml2c_z3_ ## T(Field(v, 0), c, _ctx); \
|
|
} else { \
|
|
*c = NULL; \
|
|
} \
|
|
} \
|
|
\
|
|
value c2ml_ ## T ## _opt (T* c) \
|
|
{ \
|
|
struct camlidl_ctx_struct _ctxs = { CAMLIDL_TRANSIENT, NULL }; \
|
|
camlidl_ctx _ctx = &_ctxs; \
|
|
value v; \
|
|
value a; \
|
|
if (*c) { \
|
|
a = camlidl_c2ml_z3_ ## T(c, _ctx); \
|
|
Begin_root(a) \
|
|
v = caml_alloc_small(1, 0); \
|
|
Field(v, 0) = a; \
|
|
End_roots(); \
|
|
} else { \
|
|
v = Val_int(0); \
|
|
} \
|
|
return v; \
|
|
}
|
|
");
|
|
|
|
#define DEFINE_OPT_TYPE(T) \
|
|
typedef [mltype(xstr(T option)), \
|
|
ml2c(ml2c_Z3_ ## T ## _opt), \
|
|
c2ml(c2ml_Z3_ ## T ## _opt) \
|
|
] Z3_ ## T Z3_ ## T ## _opt
|
|
|
|
quote(c,"DEFINE_OPT_OPS(Z3_ast)");
|
|
DEFINE_OPT_TYPE(ast);
|
|
|
|
quote(c,"DEFINE_OPT_OPS(Z3_sort)");
|
|
DEFINE_OPT_TYPE(sort);
|
|
|
|
quote(c,"DEFINE_OPT_OPS(Z3_func_interp)");
|
|
DEFINE_OPT_TYPE(func_interp);
|
|
|
|
|
|
// ToDo: these unnecessarily appear in the API documentation
|
|
DEFINE_TYPE(Z3_constructor);
|
|
DEFINE_TYPE(Z3_constructor_list);
|
|
|
|
|
|
// shadow delete operations with nops
|
|
quote(ml,"
|
|
let del_constructor _ _ = ()
|
|
let del_constructor_list _ _ = ()
|
|
let del_model _ _ = ()
|
|
let del_context _ = ()
|
|
let reset_memory () = ()
|
|
");
|
|
|
|
|
|
#else // MLAPIV3
|
|
|
|
// Provide custom error handler:
|
|
quote (c,"Z3_error_handler caml_z3_error_handler;");
|
|
quote (c,"void caml_z3_error_handler(Z3_context c, Z3_error_code e) { static char buffer[128]; char * msg = Z3_get_error_msg_ex(c, e); if (strlen(msg) > 100) { failwith(\"Z3: error message is too big to fit in buffer\"); } else { sprintf(buffer, \"Z3: %s\", msg); failwith(buffer); } }");
|
|
|
|
|
|
#define DEFINE_TYPE(T) typedef [abstract] void* T
|
|
#define DEFINE_VOID(T) typedef [abstract] void* T
|
|
|
|
#define BEGIN_MLAPI_EXCLUDE
|
|
#define END_MLAPI_EXCLUDE
|
|
|
|
#endif // MLAPIV3
|
|
|
|
|
|
|
|
#ifndef __in
|
|
#define __in [in]
|
|
#endif
|
|
|
|
#ifndef __out
|
|
#define __out [out]
|
|
#endif
|
|
|
|
#ifndef __out_opt
|
|
#define __out_opt [out,unique]
|
|
#endif
|
|
|
|
#ifndef __ecount
|
|
#define __ecount(num_args) [NOT_SUPPORTED]
|
|
#endif
|
|
|
|
#ifndef __in_ecount
|
|
#define __in_ecount(num_args) [in, size_is(num_args), length_is(num_args)]
|
|
#endif
|
|
|
|
#ifndef __out_ecount
|
|
#define __out_ecount(num_args) [out, size_is(num_args), length_is(num_args)]
|
|
#endif
|
|
|
|
#ifndef __inout_ecount
|
|
#define __inout_ecount(num_args) [in, out, size_is(num_args), length_is(num_args)]
|
|
#endif
|
|
|
|
#ifndef __inout
|
|
#define __inout [in, out]
|
|
#endif
|
|
|
|
#ifndef Z3_bool_opt
|
|
#define Z3_bool_opt void
|
|
#endif
|
|
|
|
|
|
#define Z3_API
|
|
|
|
#ifdef MLAPIV3
|
|
|
|
#include "z3V3_api.idl"
|
|
#include "x3V3.mli"
|
|
#include "x3V3.ml"
|
|
|
|
#else
|
|
|
|
#include "z3_api.idl"
|
|
#include "x3.ml"
|
|
|
|
quote(ml,"
|
|
let _ =
|
|
Printexc.register_printer (function
|
|
| Error(c,e) -> Some (\"Z3 \"^(get_error_msg c e))
|
|
| _ -> None
|
|
)
|
|
");
|
|
|
|
|
|
quote(mli,"
|
|
(**
|
|
{2 {L Legacy V3 API}}
|
|
*)
|
|
|
|
module V3 : sig
|
|
(**
|
|
{2 {L Legacy V3 API}}
|
|
*)
|
|
");
|
|
|
|
quote(ml,"
|
|
module V3 = struct
|
|
");
|
|
|
|
#endif
|
|
|
|
#ifdef MLAPIV3
|
|
|
|
quote(mlmli,"
|
|
end
|
|
");
|
|
|
|
#endif
|