spot/iface/ltsmin/ltsmin.cc
Alexandre Duret-Lutz cb39210166 kill the ltl namespace
* NEWS: Mention it.
* bench/stutter/stutter_invariance_formulas.cc,
bench/stutter/stutter_invariance_randomgraph.cc, doc/mainpage.dox,
doc/org/tut01.org, doc/org/tut02.org, doc/org/tut10.org, doc/tl/tl.tex,
iface/ltsmin/ltsmin.cc, iface/ltsmin/ltsmin.hh,
iface/ltsmin/modelcheck.cc, src/bin/autfilt.cc,
src/bin/common_aoutput.cc, src/bin/common_aoutput.hh,
src/bin/common_finput.cc, src/bin/common_finput.hh,
src/bin/common_output.cc, src/bin/common_output.hh, src/bin/common_r.hh,
src/bin/common_trans.cc, src/bin/common_trans.hh, src/bin/dstar2tgba.cc,
src/bin/genltl.cc, src/bin/ltl2tgba.cc, src/bin/ltl2tgta.cc,
src/bin/ltlcross.cc, src/bin/ltldo.cc, src/bin/ltlfilt.cc,
src/bin/ltlgrind.cc, src/bin/randaut.cc, src/bin/randltl.cc,
src/kripke/kripkeexplicit.cc, src/kripke/kripkeexplicit.hh,
src/kripkeparse/kripkeparse.yy, src/kripkeparse/public.hh,
src/ltlparse/fmterror.cc, src/ltlparse/ltlparse.yy,
src/ltlparse/ltlscan.ll, src/ltlparse/parsedecl.hh,
src/ltlparse/public.hh, src/parseaut/parseaut.yy,
src/parseaut/public.hh, src/tests/checkpsl.cc, src/tests/checkta.cc,
src/tests/complementation.cc, src/tests/consterm.cc,
src/tests/emptchk.cc, src/tests/equalsf.cc, src/tests/ikwiad.cc,
src/tests/kind.cc, src/tests/length.cc, src/tests/ltlprod.cc,
src/tests/ltlrel.cc, src/tests/parse.test,
src/tests/parse_print_test.cc, src/tests/randtgba.cc,
src/tests/readltl.cc, src/tests/reduc.cc, src/tests/syntimpl.cc,
src/tests/taatgba.cc, src/tests/tostring.cc, src/tests/tostring.test,
src/tl/apcollect.cc, src/tl/apcollect.hh, src/tl/contain.cc,
src/tl/contain.hh, src/tl/declenv.cc, src/tl/declenv.hh,
src/tl/defaultenv.cc, src/tl/defaultenv.hh, src/tl/dot.cc,
src/tl/dot.hh, src/tl/environment.hh, src/tl/exclusive.cc,
src/tl/exclusive.hh, src/tl/formula.cc, src/tl/formula.hh,
src/tl/length.cc, src/tl/length.hh, src/tl/mark.cc, src/tl/mark.hh,
src/tl/mutation.cc, src/tl/mutation.hh, src/tl/nenoform.cc,
src/tl/nenoform.hh, src/tl/print.cc, src/tl/print.hh,
src/tl/randomltl.cc, src/tl/randomltl.hh, src/tl/relabel.cc,
src/tl/relabel.hh, src/tl/remove_x.cc, src/tl/remove_x.hh,
src/tl/simpfg.cc, src/tl/simpfg.hh, src/tl/simplify.cc,
src/tl/simplify.hh, src/tl/snf.cc, src/tl/snf.hh, src/tl/unabbrev.cc,
src/tl/unabbrev.hh, src/twa/bdddict.cc, src/twa/bdddict.hh,
src/twa/bddprint.cc, src/twa/formula2bdd.cc, src/twa/formula2bdd.hh,
src/twa/taatgba.cc, src/twa/taatgba.hh, src/twa/twa.hh,
src/twa/twagraph.cc, src/twa/twagraph.hh, src/twaalgos/compsusp.cc,
src/twaalgos/compsusp.hh, src/twaalgos/ltl2taa.cc,
src/twaalgos/ltl2taa.hh, src/twaalgos/ltl2tgba_fm.cc,
src/twaalgos/ltl2tgba_fm.hh, src/twaalgos/minimize.cc,
src/twaalgos/minimize.hh, src/twaalgos/neverclaim.cc,
src/twaalgos/postproc.cc, src/twaalgos/postproc.hh,
src/twaalgos/powerset.cc, src/twaalgos/powerset.hh,
src/twaalgos/randomgraph.cc, src/twaalgos/randomgraph.hh,
src/twaalgos/relabel.cc, src/twaalgos/relabel.hh,
src/twaalgos/remprop.cc, src/twaalgos/remprop.hh, src/twaalgos/stats.cc,
src/twaalgos/stats.hh, src/twaalgos/stutter.cc, src/twaalgos/stutter.hh,
src/twaalgos/translate.cc, src/twaalgos/translate.hh,
wrap/python/spot_impl.i: Remove the ltl namespace.
2015-09-28 16:20:53 +02:00

1147 lines
28 KiB
C++

// -*- coding: utf-8 -*-
// Copyright (C) 2011, 2012, 2014, 2015 Laboratoire de Recherche et
// Développement de l'Epita (LRDE)
//
// This file is part of Spot, a model checking library.
//
// Spot is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Spot is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <ltdl.h>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <sstream>
#include <sys/stat.h>
#include <unistd.h>
// MinGW does not define this.
#ifndef WEXITSTATUS
# define WEXITSTATUS(x) ((x) & 0xff)
#endif
#include "ltsmin.hh"
#include "misc/hashfunc.hh"
#include "misc/fixpool.hh"
#include "misc/mspool.hh"
#include "misc/intvcomp.hh"
#include "misc/intvcmp2.hh"
namespace spot
{
namespace
{
////////////////////////////////////////////////////////////////////////
// spins interface
typedef struct transition_info {
int* labels; // edge labels, NULL, or pointer to the edge label(s)
int group; // holds transition group or -1 if unknown
} transition_info_t;
typedef void (*TransitionCB)(void *ctx,
transition_info_t *transition_info,
int *dst);
struct spins_interface
{
lt_dlhandle handle; // handle to the dynamic library
void (*get_initial_state)(void *to);
int (*have_property)();
int (*get_successors)(void* m, int *in, TransitionCB, void *arg);
int (*get_state_size)();
const char* (*get_state_variable_name)(int var);
int (*get_state_variable_type)(int var);
int (*get_type_count)();
const char* (*get_type_name)(int type);
int (*get_type_value_count)(int type);
const char* (*get_type_value_name)(int type, int value);
};
////////////////////////////////////////////////////////////////////////
// STATE
struct spins_state: public state
{
spins_state(int s, fixed_size_pool* p)
: pool(p), size(s), count(1)
{
}
void compute_hash()
{
hash_value = 0;
for (int i = 0; i < size; ++i)
hash_value = wang32_hash(hash_value ^ vars[i]);
}
spins_state* clone() const
{
++count;
return const_cast<spins_state*>(this);
}
void destroy() const
{
if (--count)
return;
pool->deallocate(this);
}
size_t hash() const
{
return hash_value;
}
int compare(const state* other) const
{
if (this == other)
return 0;
const spins_state* o = down_cast<const spins_state*>(other);
assert(o);
if (hash_value < o->hash_value)
return -1;
if (hash_value > o->hash_value)
return 1;
return memcmp(vars, o->vars, size * sizeof(*vars));
}
private:
~spins_state()
{
}
public:
fixed_size_pool* pool;
size_t hash_value: 32;
int size: 16;
mutable unsigned count: 16;
int vars[0];
};
struct spins_compressed_state: public state
{
spins_compressed_state(int s, multiple_size_pool* p)
: pool(p), size(s), count(1)
{
}
void compute_hash()
{
hash_value = 0;
for (int i = 0; i < size; ++i)
hash_value = wang32_hash(hash_value ^ vars[i]);
}
spins_compressed_state* clone() const
{
++count;
return const_cast<spins_compressed_state*>(this);
}
void destroy() const
{
if (--count)
return;
pool->deallocate(this, sizeof(*this) + size * sizeof(*vars));
}
size_t hash() const
{
return hash_value;
}
int compare(const state* other) const
{
if (this == other)
return 0;
const spins_compressed_state* o =
down_cast<const spins_compressed_state*>(other);
assert(o);
if (hash_value < o->hash_value)
return -1;
if (hash_value > o->hash_value)
return 1;
if (size < o->size)
return -1;
if (size > o->size)
return 1;
return memcmp(vars, o->vars, size * sizeof(*vars));
}
private:
~spins_compressed_state()
{
}
public:
multiple_size_pool* pool;
size_t hash_value: 32;
int size: 16;
mutable unsigned count: 16;
int vars[0];
};
////////////////////////////////////////////////////////////////////////
// CALLBACK FUNCTION for transitions.
struct callback_context
{
typedef std::list<state*> transitions_t;
transitions_t transitions;
int state_size;
void* pool;
int* compressed;
void (*compress)(const int*, size_t, int*, size_t&);
~callback_context()
{
for (auto t: transitions)
t->destroy();
}
};
void transition_callback(void* arg, transition_info_t*, int *dst)
{
callback_context* ctx = static_cast<callback_context*>(arg);
fixed_size_pool* p = static_cast<fixed_size_pool*>(ctx->pool);
spins_state* out =
new(p->allocate()) spins_state(ctx->state_size, p);
memcpy(out->vars, dst, ctx->state_size * sizeof(int));
out->compute_hash();
ctx->transitions.push_back(out);
}
void transition_callback_compress(void* arg, transition_info_t*, int *dst)
{
callback_context* ctx = static_cast<callback_context*>(arg);
multiple_size_pool* p = static_cast<multiple_size_pool*>(ctx->pool);
size_t csize = ctx->state_size * 2;
ctx->compress(dst, ctx->state_size, ctx->compressed, csize);
void* mem = p->allocate(sizeof(spins_compressed_state)
+ sizeof(int) * csize);
spins_compressed_state* out = new(mem) spins_compressed_state(csize, p);
memcpy(out->vars, ctx->compressed, csize * sizeof(int));
out->compute_hash();
ctx->transitions.push_back(out);
}
////////////////////////////////////////////////////////////////////////
// SUCC_ITERATOR
class spins_succ_iterator: public kripke_succ_iterator
{
public:
spins_succ_iterator(const callback_context* cc,
bdd cond)
: kripke_succ_iterator(cond), cc_(cc)
{
}
void recycle(const callback_context* cc, bdd cond)
{
delete cc_;
cc_ = cc;
kripke_succ_iterator::recycle(cond);
}
~spins_succ_iterator()
{
delete cc_;
}
virtual
bool first()
{
it_ = cc_->transitions.begin();
return it_ != cc_->transitions.end();
}
virtual
bool next()
{
++it_;
return it_ != cc_->transitions.end();
}
virtual
bool done() const
{
return it_ == cc_->transitions.end();
}
virtual
state* current_state() const
{
return (*it_)->clone();
}
private:
const callback_context* cc_;
callback_context::transitions_t::const_iterator it_;
};
////////////////////////////////////////////////////////////////////////
// PREDICATE EVALUATION
typedef enum { OP_EQ, OP_NE, OP_LT, OP_GT, OP_LE, OP_GE } relop;
struct one_prop
{
int var_num;
relop op;
int val;
int bddvar; // if "var_num op val" is true, output bddvar,
// else its negation
};
typedef std::vector<one_prop> prop_set;
struct var_info
{
int num;
int type;
};
int
convert_aps(const atomic_prop_set* aps,
const spins_interface* d,
bdd_dict_ptr dict,
formula dead,
prop_set& out)
{
int errors = 0;
int state_size = d->get_state_size();
typedef std::map<std::string, var_info> val_map_t;
val_map_t val_map;
for (int i = 0; i < state_size; ++i)
{
const char* name = d->get_state_variable_name(i);
int type = d->get_state_variable_type(i);
var_info v = { i , type };
val_map[name] = v;
}
int type_count = d->get_type_count();
typedef std::map<std::string, int> enum_map_t;
std::vector<enum_map_t> enum_map(type_count);
for (int i = 0; i < type_count; ++i)
{
int enum_count = d->get_type_value_count(i);
for (int j = 0; j < enum_count; ++j)
enum_map[i].emplace(d->get_type_value_name(i, j), j);
}
for (atomic_prop_set::const_iterator ap = aps->begin();
ap != aps->end(); ++ap)
{
if (*ap == dead)
continue;
const std::string& str = ap->ap_name();
const char* s = str.c_str();
// Skip any leading blank.
while (*s && (*s == ' ' || *s == '\t'))
++s;
if (!*s)
{
std::cerr << "Proposition `" << str
<< "' cannot be parsed." << std::endl;
++errors;
continue;
}
char* name = (char*) malloc(str.size() + 1);
char* name_p = name;
char* lastdot = nullptr;
while (*s && (*s != '=') && *s != '<' && *s != '!' && *s != '>')
{
if (*s == ' ' || *s == '\t')
++s;
else
{
if (*s == '.')
lastdot = name_p;
*name_p++ = *s++;
}
}
*name_p = 0;
if (name == name_p)
{
std::cerr << "Proposition `" << str
<< "' cannot be parsed." << std::endl;
free(name);
++errors;
continue;
}
// Lookup the name
val_map_t::const_iterator ni = val_map.find(name);
if (ni == val_map.end())
{
// We may have a name such as X.Y.Z
// If it is not a known variable, it might mean
// an enumerated variable X.Y with value Z.
if (lastdot)
{
*lastdot++ = 0;
ni = val_map.find(name);
}
if (ni == val_map.end())
{
std::cerr << "No variable `" << name
<< "' found in model (for proposition `"
<< str << "')." << std::endl;
free(name);
++errors;
continue;
}
// We have found the enumerated variable, and lastdot is
// pointing to its expected value.
int type_num = ni->second.type;
enum_map_t::const_iterator ei = enum_map[type_num].find(lastdot);
if (ei == enum_map[type_num].end())
{
std::cerr << "No state `" << lastdot
<< "' known for variable `"
<< name << "'." << std::endl;
std::cerr << "Possible states are:";
for (ei = enum_map[type_num].begin();
ei != enum_map[type_num].end(); ++ei)
std::cerr << ' ' << ei->first;
std::cerr << '\n';
free(name);
++errors;
continue;
}
// At this point, *s should be 0.
if (*s)
{
std::cerr << "Trailing garbage `" << s
<< "' at end of proposition `"
<< str << "'." << std::endl;
free(name);
++errors;
continue;
}
// Record that X.Y must be equal to Z.
int v = dict->register_proposition(*ap, d);
one_prop p = { ni->second.num, OP_EQ, ei->second, v };
out.push_back(p);
free(name);
continue;
}
int var_num = ni->second.num;
if (!*s) // No operator? Assume "!= 0".
{
int v = dict->register_proposition(*ap, d);
one_prop p = { var_num, OP_NE, 0, v };
out.push_back(p);
free(name);
continue;
}
relop op;
switch (*s)
{
case '!':
if (s[1] != '=')
goto report_error;
op = OP_NE;
s += 2;
break;
case '=':
if (s[1] != '=')
goto report_error;
op = OP_EQ;
s += 2;
break;
case '<':
if (s[1] == '=')
{
op = OP_LE;
s += 2;
}
else
{
op = OP_LT;
++s;
}
break;
case '>':
if (s[1] == '=')
{
op = OP_GE;
s += 2;
}
else
{
op = OP_GT;
++s;
}
break;
default:
report_error:
std::cerr << "Unexpected `" << s
<< "' while parsing atomic proposition `" << str
<< "'." << std::endl;
++errors;
free(name);
continue;
}
while (*s && (*s == ' ' || *s == '\t'))
++s;
int val = 0; // Initialize to kill a warning from old compilers.
int type_num = ni->second.type;
if (type_num == 0 || (*s >= '0' && *s <= '9') || *s == '-')
{
char* s_end;
val = strtol(s, &s_end, 10);
if (s == s_end)
{
std::cerr << "Failed to parse `" << s
<< "' as an integer." << std::endl;
++errors;
free(name);
continue;
}
s = s_end;
}
else
{
// We are in a case such as P_0 == S, trying to convert
// the string S into an integer.
const char* end = s;
while (*end && *end != ' ' && *end != '\t')
++end;
std::string st(s, end);
// Lookup the string.
enum_map_t::const_iterator ei = enum_map[type_num].find(st);
if (ei == enum_map[type_num].end())
{
std::cerr << "No state `" << st
<< "' known for variable `"
<< name << "'." << std::endl;
std::cerr << "Possible states are:";
for (ei = enum_map[type_num].begin();
ei != enum_map[type_num].end(); ++ei)
std::cerr << ' ' << ei->first;
std::cerr << '\n';
free(name);
++errors;
continue;
}
s = end;
val = ei->second;
}
free(name);
while (*s && (*s == ' ' || *s == '\t'))
++s;
if (*s)
{
std::cerr << "Unexpected `" << s
<< "' while parsing atomic proposition `" << str
<< "'." << std::endl;
++errors;
continue;
}
int v = dict->register_proposition(*ap, d);
one_prop p = { var_num, op, val, v };
out.push_back(p);
}
return errors;
}
////////////////////////////////////////////////////////////////////////
// KRIPKE
class spins_kripke: public kripke
{
public:
spins_kripke(const spins_interface* d, const bdd_dict_ptr& dict,
const spot::prop_set* ps, formula dead,
int compress)
: kripke(dict),
d_(d),
state_size_(d_->get_state_size()),
dict_(dict), ps_(ps),
compress_(compress == 0 ? nullptr
: compress == 1 ? int_array_array_compress
: int_array_array_compress2),
decompress_(compress == 0 ? nullptr
: compress == 1 ? int_array_array_decompress
: int_array_array_decompress2),
uncompressed_(compress ? new int[state_size_ + 30] : nullptr),
compressed_(compress ? new int[state_size_ * 2] : nullptr),
statepool_(compress ? sizeof(spins_compressed_state) :
(sizeof(spins_state) + state_size_ * sizeof(int))),
state_condition_last_state_(nullptr),
state_condition_last_cc_(nullptr)
{
vname_ = new const char*[state_size_];
format_filter_ = new bool[state_size_];
for (int i = 0; i < state_size_; ++i)
{
vname_[i] = d_->get_state_variable_name(i);
// We don't want to print variables that can take a single
// value (e.g. process with a single state) to shorten the
// output.
int type = d->get_state_variable_type(i);
format_filter_[i] =
(d->get_type_value_count(type) != 1);
}
// Register the "dead" proposition. There are three cases to
// consider:
// * If DEAD is "false", it means we are not interested in finite
// sequences of the system.
// * If DEAD is "true", we want to check finite sequences as well
// as infinite sequences, but do not need to distinguish them.
// * If DEAD is any other string, this is the name a property
// that should be true when looping on a dead state, and false
// otherwise.
// We handle these three cases by setting ALIVE_PROP and DEAD_PROP
// appropriately. ALIVE_PROP is the bdd that should be ANDed
// to all transitions leaving a live state, while DEAD_PROP should
// be ANDed to all transitions leaving a dead state.
if (dead.is_ff())
{
alive_prop = bddtrue;
dead_prop = bddfalse;
}
else if (dead.is_tt())
{
alive_prop = bddtrue;
dead_prop = bddtrue;
}
else
{
int var = dict->register_proposition(dead, d_);
dead_prop = bdd_ithvar(var);
alive_prop = bdd_nithvar(var);
}
}
~spins_kripke()
{
if (iter_cache_)
{
delete iter_cache_;
iter_cache_ = nullptr;
}
delete[] format_filter_;
delete[] vname_;
if (compress_)
{
delete[] uncompressed_;
delete[] compressed_;
}
lt_dlclose(d_->handle);
dict_->unregister_all_my_variables(d_);
delete d_;
delete ps_;
lt_dlexit();
if (state_condition_last_state_)
state_condition_last_state_->destroy();
delete state_condition_last_cc_; // Might be 0 already.
}
virtual
state* get_init_state() const
{
if (compress_)
{
d_->get_initial_state(uncompressed_);
size_t csize = state_size_ * 2;
compress_(uncompressed_, state_size_, compressed_, csize);
multiple_size_pool* p =
const_cast<multiple_size_pool*>(&compstatepool_);
void* mem = p->allocate(sizeof(spins_compressed_state)
+ sizeof(int) * csize);
spins_compressed_state* res = new(mem)
spins_compressed_state(csize, p);
memcpy(res->vars, compressed_, csize * sizeof(int));
res->compute_hash();
return res;
}
else
{
fixed_size_pool* p = const_cast<fixed_size_pool*>(&statepool_);
spins_state* res = new(p->allocate()) spins_state(state_size_, p);
d_->get_initial_state(res->vars);
res->compute_hash();
return res;
}
}
bdd
compute_state_condition_aux(const int* vars) const
{
bdd res = bddtrue;
for (auto& i: *ps_)
{
int l = vars[i.var_num];
int r = i.val;
bool cond = false;
switch (i.op)
{
case OP_EQ:
cond = (l == r);
break;
case OP_NE:
cond = (l != r);
break;
case OP_LT:
cond = (l < r);
break;
case OP_GT:
cond = (l > r);
break;
case OP_LE:
cond = (l <= r);
break;
case OP_GE:
cond = (l >= r);
break;
}
if (cond)
res &= bdd_ithvar(i.bddvar);
else
res &= bdd_nithvar(i.bddvar);
}
return res;
}
callback_context* build_cc(const int* vars, int& t) const
{
callback_context* cc = new callback_context;
cc->state_size = state_size_;
cc->pool =
const_cast<void*>(compress_
? static_cast<const void*>(&compstatepool_)
: static_cast<const void*>(&statepool_));
cc->compress = compress_;
cc->compressed = compressed_;
t = d_->get_successors(nullptr, const_cast<int*>(vars),
compress_
? transition_callback_compress
: transition_callback,
cc);
assert((unsigned)t == cc->transitions.size());
return cc;
}
bdd
compute_state_condition(const state* st) const
{
// If we just computed it, don't do it twice.
if (st == state_condition_last_state_)
return state_condition_last_cond_;
if (state_condition_last_state_)
{
state_condition_last_state_->destroy();
delete state_condition_last_cc_; // Might be 0 already.
state_condition_last_cc_ = nullptr;
}
const int* vars = get_vars(st);
bdd res = compute_state_condition_aux(vars);
int t;
callback_context* cc = build_cc(vars, t);
if (t)
{
res &= alive_prop;
}
else
{
res &= dead_prop;
// Add a self-loop to dead-states if we care about these.
if (res != bddfalse)
cc->transitions.push_back(st->clone());
}
state_condition_last_cc_ = cc;
state_condition_last_cond_ = res;
state_condition_last_state_ = st->clone();
return res;
}
const int*
get_vars(const state* st) const
{
const int* vars;
if (compress_)
{
const spins_compressed_state* s =
down_cast<const spins_compressed_state*>(st);
assert(s);
decompress_(s->vars, s->size, uncompressed_, state_size_);
vars = uncompressed_;
}
else
{
const spins_state* s = down_cast<const spins_state*>(st);
assert(s);
vars = s->vars;
}
return vars;
}
virtual
spins_succ_iterator*
succ_iter(const state* st) const
{
// This may also compute successors in state_condition_last_cc
bdd scond = compute_state_condition(st);
callback_context* cc;
if (state_condition_last_cc_)
{
cc = state_condition_last_cc_;
state_condition_last_cc_ = nullptr; // Now owned by the iterator.
}
else
{
int t;
cc = build_cc(get_vars(st), t);
// Add a self-loop to dead-states if we care about these.
if (t == 0 && scond != bddfalse)
cc->transitions.push_back(st->clone());
}
if (iter_cache_)
{
spins_succ_iterator* it =
down_cast<spins_succ_iterator*>(iter_cache_);
it->recycle(cc, scond);
iter_cache_ = nullptr;
return it;
}
return new spins_succ_iterator(cc, scond);
}
virtual
bdd
state_condition(const state* st) const
{
return compute_state_condition(st);
}
virtual
std::string format_state(const state *st) const
{
const int* vars = get_vars(st);
std::stringstream res;
if (state_size_ == 0)
return "empty state";
int i = 0;
for (;;)
{
if (!format_filter_[i])
{
++i;
continue;
}
res << vname_[i] << '=' << vars[i];
++i;
if (i == state_size_)
break;
res << ", ";
}
return res.str();
}
virtual
spot::bdd_dict_ptr get_dict() const
{
return dict_;
}
private:
const spins_interface* d_;
int state_size_;
bdd_dict_ptr dict_;
const char** vname_;
bool* format_filter_;
const spot::prop_set* ps_;
bdd alive_prop;
bdd dead_prop;
void (*compress_)(const int*, size_t, int*, size_t&);
void (*decompress_)(const int*, size_t, int*, size_t);
int* uncompressed_;
int* compressed_;
fixed_size_pool statepool_;
multiple_size_pool compstatepool_;
// This cache is used to speedup repeated calls to state_condition()
// and get_succ().
// If state_condition_last_state_ != 0, then state_condition_last_cond_
// contain its (recently computed) condition. If additionally
// state_condition_last_cc_ != 0, then it contains the successors.
mutable const state* state_condition_last_state_;
mutable bdd state_condition_last_cond_;
mutable callback_context* state_condition_last_cc_;
};
}
////////////////////////////////////////////////////////////////////////////
// LOADER
// Call spins to compile "foo.prom" as "foo.prom.spins" if the latter
// does not exist already or is older.
bool
compile_model(std::string& filename, std::string& ext, bool verbose)
{
std::string command;
std::string compiled_ext;
if (ext == ".prom" || ext == ".pm" || ext == ".pml")
{
command = "spins " + filename;
compiled_ext = ".spins";
}
else if (ext == ".dve")
{
command = "divine compile --ltsmin " + filename;
compiled_ext = "2C";
}
else
{
if (verbose)
std::cerr << "Unknown extension `" << ext
<< "'. Use `.prom', `.pm', `.pml', `.dve', `.dve2C' or"\
"`.prom.spins'." << std::endl;
return false;
}
struct stat s;
if (stat(filename.c_str(), &s) != 0)
{
if (verbose)
{
std::cerr << "Cannot open " << filename << std::endl;
return true;
}
}
std::string old = filename;
filename += compiled_ext;
// Remove any directory, because the new file will
// be compiled in the current directory.
size_t pos = filename.find_last_of("/\\");
if (pos != std::string::npos)
filename = "./" + filename.substr(pos + 1);
struct stat d;
if (stat(filename.c_str(), &d) == 0)
if (s.st_mtime < d.st_mtime)
// The .spins or .dve2C is up-to-date, no need to recompile it.
return false;
int res = system(command.c_str());
if (res)
{
if (verbose)
std::cerr << "Execution of `" << command.c_str()
<< "' returned exit code " << WEXITSTATUS(res)
<< ".\n";
return true;
}
return false;
}
kripke_ptr
load_ltsmin(const std::string& file_arg, const bdd_dict_ptr& dict,
const atomic_prop_set* to_observe,
const formula dead, int compress, bool verbose)
{
std::string file;
if (file_arg.find_first_of("/\\") != std::string::npos)
file = file_arg;
else
file = "./" + file_arg;
std::string ext = file.substr(file.find_last_of("."));
if (ext != ".spins" && ext != ".dve2C")
{
if (compile_model(file, ext, verbose))
{
if (verbose)
std::cerr << "Failed to compile `" << file_arg
<< "'." << std::endl;
return nullptr;
}
}
if (lt_dlinit())
{
if (verbose)
std::cerr << "Failed to initialize libltdl." << std::endl;
return nullptr;
}
lt_dlhandle h = lt_dlopen(file.c_str());
if (!h)
{
if (verbose)
std::cerr << "Failed to load `" << file << "'." << std::endl;
lt_dlexit();
return nullptr;
}
spins_interface* d = new spins_interface;
d->handle = h;
// SpinS interface.
if ((d->get_initial_state = (void (*)(void*))
lt_dlsym(h, "spins_get_initial_state")))
{
d->have_property = nullptr;
d->get_successors = (int (*)(void*, int*, TransitionCB, void*))
lt_dlsym(h, "spins_get_successor_all");
d->get_state_size = (int (*)())
lt_dlsym(h, "spins_get_state_size");
d->get_state_variable_name = (const char* (*)(int))
lt_dlsym(h, "spins_get_state_variable_name");
d->get_state_variable_type = (int (*)(int))
lt_dlsym(h, "spins_get_state_variable_type");
d->get_type_count = (int (*)())
lt_dlsym(h, "spins_get_type_count");
d->get_type_name = (const char* (*)(int))
lt_dlsym(h, "spins_get_type_name");
d->get_type_value_count = (int (*)(int))
lt_dlsym(h, "spins_get_type_value_count");
d->get_type_value_name = (const char* (*)(int, int))
lt_dlsym(h, "spins_get_type_value_name");
}
// dve2 interface.
else
{
d->get_initial_state = (void (*)(void*))
lt_dlsym(h, "get_initial_state");
d->have_property = (int (*)())
lt_dlsym(h, "have_property");
d->get_successors = (int (*)(void*, int*, TransitionCB, void*))
lt_dlsym(h, "get_successors");
d->get_state_size = (int (*)())
lt_dlsym(h, "get_state_variable_count");
d->get_state_variable_name = (const char* (*)(int))
lt_dlsym(h, "get_state_variable_name");
d->get_state_variable_type = (int (*)(int))
lt_dlsym(h, "get_state_variable_type");
d->get_type_count = (int (*)())
lt_dlsym(h, "get_state_variable_type_count");
d->get_type_name = (const char* (*)(int))
lt_dlsym(h, "get_state_variable_type_name");
d->get_type_value_count = (int (*)(int))
lt_dlsym(h, "get_state_variable_type_value_count");
d->get_type_value_name = (const char* (*)(int, int))
lt_dlsym(h, "get_state_variable_type_value");
}
if (!(d->get_initial_state
&& d->get_successors
&& d->get_state_size
&& d->get_state_variable_name
&& d->get_state_variable_type
&& d->get_type_count
&& d->get_type_name
&& d->get_type_value_count
&& d->get_type_value_name))
{
if (verbose)
std::cerr << "Failed to resolve some symbol while loading `"
<< file << "'\n";
delete d;
lt_dlexit();
return nullptr;
}
if (d->have_property && d->have_property())
{
if (verbose)
std::cerr << "Model with an embedded property are not supported."
<< std::endl;
delete d;
lt_dlexit();
return nullptr;
}
spot::prop_set* ps = new spot::prop_set;
int errors = convert_aps(to_observe, d, dict, dead, *ps);
if (errors)
{
delete ps;
dict->unregister_all_my_variables(d);
delete d;
lt_dlexit();
return nullptr;
}
return std::make_shared<spins_kripke>(d, dict, ps, dead, compress);
}
}