2

I'm using the C API of Z3. I've checked the examples, and Z3_mk_config() and Z3_mk_context (Z3_config c) are used to create contexts, e.g.,

Z3_config  cfg;
Z3_context ctx;
cfg = Z3_mk_config();
Z3_set_param_value(cfg, "model", "true");
//...
ctx = Z3_mk_context(cfg);

However, the documentation says that all these functions are deprecated, but don't mention which functions should be used now instead.

Does anyone know which functions should be used now to create configurations and contexts?

Any help would be greatly appreciated. Thank you!

Paul R
  • 208,748
  • 37
  • 389
  • 560
  • If you look into `Z3` sources, there is a remark in `Z3_mk_config()` description: "In previous versions of `Z3`, the `Z3_config` was used to store global and module configurations. Now, we should use `Z3_global_param_set`". It seems the reason why all `Z3 API` functions that works with `Z3_config` are deprecated now. Use `Z3_global_param_set` instead. – SergeyLebedev Jan 25 '18 at 11:29
  • Thanks. Yes, I saw that, but mk_context and mk_context_rc both require an argument of type Z3_config. How can I get such an argument then? – German Vidal Jan 25 '18 at 11:42
  • Apparently you should use `Z3_global_param_set` function. Here is from its description: "Set a global (or module) parameter. This setting is shared by all `Z3` contexts. When a `Z3` module is initialized it will use the value of these parameters when `Z3_params` objects are not provided. ... This function can be used to set parameters for a specific `Z3` module." – SergeyLebedev Jan 25 '18 at 11:48
  • 1
    Right, I can use `Z3_global_param_set` to set global parameters, but I still need an object of type `Z3_config` to create a context (either with `Z3_mk_context` or `Z3_mk_context_rc`, both require an argument of type `Z3_config`). That was my point above... `Z3_global_param_set` does not return an object of type `Z3_config`, so I don't see a way to create a context without the old `Z3_mk_config`... – German Vidal Jan 25 '18 at 12:13

1 Answers1

0

TLDR:

  • Use Z3_mk_config to create a Z3_config.
  • Use Z3_global_param_set to set configs globally.
  • Use Z3_set_param_value to set context-specific configs.
  • If you don't have any config to set, simply
    1. make a Z3_config with Z3_mk_config,
    2. create your context with it,
    3. and then delete it with Z3_del_config.

The comment by SergeyLebedev is probably referencing to this remark in the doc

In previous versions of Z3, the Z3_config was used to store global and module configurations. Now, we should use Z3_global_param_set.

I was going through the source repo the past few days. In the example file test_capi.c, they use this mk_context function a lot, which is defined in the same file with

Z3_context mk_context()
{
    Z3_config  cfg;
    Z3_context ctx;
    cfg = Z3_mk_config();
    ctx = mk_context_custom(cfg, error_handler);
    Z3_del_config(cfg);
    return ctx;
}

where mk_context_custom is defined in the same file with

Z3_context mk_context_custom(Z3_config cfg, Z3_error_handler err)
{
    Z3_context ctx;

    Z3_set_param_value(cfg, "model", "true");
    ctx = Z3_mk_context(cfg);
    Z3_set_error_handler(ctx, err);

    return ctx;
}

So it looks like Z3_mk_config and Z3_set_param_value are still used.

The doc says that stuff like Z3_config and Z3_context are supposed to be "opaque pointers". However, chasing it to the source, it looks like Z3_config is declared as a struct without the specific content but seems like wherever it's used it's cast to context_params which is defined clearly as a class.

The type Z3_config is declared in z3_api_h with

DEFINE_TYPE(Z3_config);

where DEFINE_TYPE(T) is a macro defined in z3_macros.h and expands to typedef struct _ ## T *T. So really Z3_config is only prototypically declared as a struct.

The function Z3_mk_config, the constructor for a Z3_config, declared in z3_api.h with

Z3_config Z3_API Z3_mk_config(void);

, is defined in api_config_params.cpp as

Z3_config Z3_API Z3_mk_config(void) {
    try {
        memory::initialize(UINT_MAX);
        LOG_Z3_mk_config();
        Z3_config r = reinterpret_cast<Z3_config>(alloc(context_params));
        RETURN_Z3(r);
    } catch (z3_exception & ex) {
        // The error handler is only available for contexts
        // Just throw a warning.
        warning_msg("%s", ex.msg());
        return nullptr;
    }
}

within an extern "C" block. Note that Z3_API is a macro also defined in z3_macros.h to set __attribute__ ((visibility ("default"))) and alloc is a macro defined in memory_manager.h with #define alloc(T,...) new (memory::allocate(sizeof(T))) T(__VA_ARGS__) and RETURN_Z3 is a macro defined in some file generated by the file update_api.py with #define RETURN_Z3(Z3RES) if (_LOG_CTX.enabled()) { SetR(Z3RES); } return Z3RES.

It appears that, when a Z3_config is created, a memory block of the size of a context_params is allocated and cast as Z3_config, and context_params is in fact a class defined clearly in context_params.h. I omit the content because it's not relevant right now.

class context_params {
    ....
};

Next, Z3_set_param_value is defined in api_config_params.cpp with

void Z3_API Z3_set_param_value(Z3_config c, char const * param_id, char const * param_value) {
    LOG_Z3_set_param_value(c, param_id, param_value);
    try {
        context_params * p = reinterpret_cast<context_params*>(c);
        p->set(param_id, param_value);
    }
    catch (z3_exception & ex) {
        // The error handler is only available for contexts
        // Just throw a warning.
        warning_msg("%s", ex.msg());
    }
}

So it appears that the function first cast Z3_config back to context_params before using it like a normal object. I'm not an expert but this probably works because struct and class are almost the same underneath the hood.

On the other hand, in that exmaple file test_capi.c, they used Z3_global_param_set like so

....
cfg = Z3_mk_config();
....
/*
   The current model finder for quantified formulas cannot handle injectivity.
   So, we are limiting the number of iterations to avoid a long "wait".
*/
Z3_global_param_set("smt.mbqi.max_iterations", "10");
ctx = mk_context_custom(cfg, error_handler);
Z3_del_config(cfg);
s = mk_solver(ctx);

They did still use Z3_mk_config before making a context while using Z3_global_param_set to set configs. So I'd say there's nothing wrong with using Z3_mk_config to construct a Z3_config, and what the doc is saying is when you want to set something globally, don't access the Z3_config struct itself since you can't before casting it to a context_params anyway; use Z3_global_param_set instead. And when you want to set something specific to some specific context, use Z3_set_param_value.


The question remains that why would we want to switch between structs and classes? I don't know. I'm not at all familiar with C++, but I'm guessing, if not for the C API itself, it's for making the extern "C" part works for the Python API bindings.

RexYuan
  • 280
  • 7
  • 13