1 // -*- coding: utf-8 -*-
2 // Copyright (C) 2015, 2016, 2017, 2018, 2019, 2020 Laboratoire de Recherche et
3 // Developpement de l'Epita
4 //
5 // This file is part of Spot, a model checking library.
6 //
7 // Spot is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 3 of the License, or
10 // (at your option) any later version.
11 //
12 // Spot is distributed in the hope that it will be useful, but WITHOUT
13 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
15 // License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 
20 #pragma once
21 
22 #include <atomic>
23 #include <chrono>
24 #include <spot/bricks/brick-hashset>
25 #include <stdlib.h>
26 #include <thread>
27 #include <vector>
28 #include <utility>
29 #include <spot/misc/common.hh>
30 #include <spot/kripke/kripke.hh>
31 #include <spot/misc/fixpool.hh>
32 #include <spot/misc/timer.hh>
33 #include <spot/twacube/twacube.hh>
34 #include <spot/twacube/fwd.hh>
35 #include <spot/mc/intersect.hh>
36 #include <spot/mc/mc.hh>
37 
38 namespace spot
39 {
40   template<typename State,
41            typename StateHash,
42            typename StateEqual>
43   class iterable_uf_ec
44   {
45 
46   public:
47     enum class uf_status  { LIVE, LOCK, DEAD };
48     enum class list_status  { BUSY, LOCK, DONE };
49     enum class claim_status  { CLAIM_FOUND, CLAIM_NEW, CLAIM_DEAD };
50 
51     /// \brief Represents a Union-Find element
52     struct uf_element
53     {
54       /// \brief the kripke state handled by the element
55       State st_kripke;
56       /// \brief the prop state handled by the element
57       unsigned st_prop;
58       /// \brief acceptance conditions of the union
59       acc_cond::mark_t acc;
60       /// \brief mutex for acceptance condition
61       std::mutex acc_mutex_;
62       /// \brief reference to the pointer
63       std::atomic<uf_element*> parent;
64       /// The set of worker for a given state
65       std::atomic<unsigned> worker_;
66       /// \brief next element for work stealing
67       std::atomic<uf_element*> next_;
68       /// \brief current status for the element
69       std::atomic<uf_status> uf_status_;
70       ///< \brief current status for the list
71       std::atomic<list_status> list_status_;
72     };
73 
74     /// \brief The haser for the previous uf_element.
75     struct uf_element_hasher
76     {
uf_element_hasherspot::iterable_uf_ec::uf_element_hasher77       uf_element_hasher(const uf_element*)
78       { }
79 
80       uf_element_hasher() = default;
81 
82       brick::hash::hash128_t
hashspot::iterable_uf_ec::uf_element_hasher83       hash(const uf_element* lhs) const
84       {
85         StateHash hash;
86         // Not modulo 31 according to brick::hashset specifications.
87         unsigned u = hash(lhs->st_kripke) % (1<<30);
88         u = wang32_hash(lhs->st_prop) ^ u;
89         u = u % (1<<30);
90         return {u, u};
91       }
92 
equalspot::iterable_uf_ec::uf_element_hasher93       bool equal(const uf_element* lhs,
94                  const uf_element* rhs) const
95       {
96         StateEqual equal;
97         return (lhs->st_prop == rhs->st_prop)
98           && equal(lhs->st_kripke, rhs->st_kripke);
99       }
100     };
101 
102     ///< \brief Shortcut to ease shared map manipulation
103     using shared_map = brick::hashset::FastConcurrent <uf_element*,
104                                                        uf_element_hasher>;
105 
iterable_uf_ec(const iterable_uf_ec<State,StateHash,StateEqual> & uf)106     iterable_uf_ec(const iterable_uf_ec<State, StateHash, StateEqual>& uf):
107       map_(uf.map_), tid_(uf.tid_), size_(std::thread::hardware_concurrency()),
108       nb_th_(std::thread::hardware_concurrency()), inserted_(0),
109       p_(sizeof(uf_element))
110     { }
111 
iterable_uf_ec(shared_map & map,unsigned tid)112     iterable_uf_ec(shared_map& map, unsigned tid):
113       map_(map), tid_(tid), size_(std::thread::hardware_concurrency()),
114       nb_th_(std::thread::hardware_concurrency()), inserted_(0),
115       p_(sizeof(uf_element))
116     { }
117 
~iterable_uf_ec()118     ~iterable_uf_ec() {}
119 
120     std::pair<claim_status, uf_element*>
make_claim(State kripke,unsigned prop)121     make_claim(State kripke, unsigned prop)
122     {
123       unsigned w_id = (1U << tid_);
124 
125       // Setup and try to insert the new state in the shared map.
126       uf_element* v = (uf_element*) p_.allocate();
127       new (v) (uf_element); // required, otherwise the mutex is unitialized
128       v->st_kripke = kripke;
129       v->st_prop = prop;
130       v->acc = {};
131       v->parent = v;
132       v->next_ = v;
133       v->worker_ = 0;
134       v->uf_status_ = uf_status::LIVE;
135       v->list_status_ = list_status::BUSY;
136 
137       auto it = map_.insert({v});
138       bool b = it.isnew();
139 
140       // Insertion failed, delete element
141       // FIXME Should we add a local cache to avoid useless allocations?
142       if (!b)
143         p_.deallocate(v);
144       else
145         ++inserted_;
146 
147       uf_element* a_root = find(*it);
148       if (a_root->uf_status_.load() == uf_status::DEAD)
149         return {claim_status::CLAIM_DEAD, *it};
150 
151       if ((a_root->worker_.load() & w_id) != 0)
152         return {claim_status::CLAIM_FOUND, *it};
153 
154       atomic_fetch_or(&(a_root->worker_), w_id);
155       while (a_root->parent.load() != a_root)
156         {
157           a_root = find(a_root);
158           atomic_fetch_or(&(a_root->worker_), w_id);
159         }
160 
161       return {claim_status::CLAIM_NEW, *it};
162     }
163 
find(uf_element * a)164     uf_element* find(uf_element* a)
165     {
166       uf_element* parent = a->parent.load();
167       uf_element* x = a;
168       uf_element* y;
169 
170       while (x != parent)
171         {
172           y = parent;
173           parent = y->parent.load();
174           if (parent == y)
175             return y;
176           x->parent.store(parent);
177           x = parent;
178           parent = x->parent.load();
179         }
180       return x;
181     }
182 
sameset(uf_element * a,uf_element * b)183     bool sameset(uf_element* a, uf_element* b)
184     {
185       while (true)
186         {
187           uf_element* a_root = find(a);
188           uf_element* b_root = find(b);
189           if (a_root == b_root)
190             return true;
191 
192           if (a_root->parent.load() == a_root)
193             return false;
194         }
195     }
196 
lock_root(uf_element * a)197     bool lock_root(uf_element* a)
198     {
199       uf_status expected = uf_status::LIVE;
200       if (a->uf_status_.load() == expected)
201         {
202           if (std::atomic_compare_exchange_strong
203               (&(a->uf_status_), &expected, uf_status::LOCK))
204             {
205               if (a->parent.load() == a)
206                 return true;
207               unlock_root(a);
208             }
209         }
210       return false;
211     }
212 
unlock_root(uf_element * a)213     inline void unlock_root(uf_element* a)
214     {
215       a->uf_status_.store(uf_status::LIVE);
216     }
217 
lock_list(uf_element * a)218     uf_element* lock_list(uf_element* a)
219     {
220       uf_element* a_list = a;
221       while (true)
222         {
223           bool dontcare = false;
224           a_list = pick_from_list(a_list, &dontcare);
225           if (a_list == nullptr)
226             {
227               return nullptr;
228             }
229 
230           auto expected = list_status::BUSY;
231           bool b = std::atomic_compare_exchange_strong
232             (&(a_list->list_status_), &expected, list_status::LOCK);
233 
234           if (b)
235             return a_list;
236 
237           a_list = a_list->next_.load();
238         }
239     }
240 
unlock_list(uf_element * a)241     void unlock_list(uf_element* a)
242     {
243       a->list_status_.store(list_status::BUSY);
244     }
245 
246     acc_cond::mark_t
unite(uf_element * a,uf_element * b,acc_cond::mark_t acc)247     unite(uf_element* a, uf_element* b, acc_cond::mark_t acc)
248     {
249       uf_element* a_root;
250       uf_element* b_root;
251       uf_element* q;
252       uf_element* r;
253 
254       while (true)
255         {
256           a_root = find(a);
257           b_root = find(b);
258 
259           if (a_root == b_root)
260             {
261               // Update acceptance condition
262               {
263                 std::lock_guard<std::mutex> rlock(a_root->acc_mutex_);
264                 a_root->acc |= acc;
265                 acc |= a_root->acc;
266               }
267 
268               while (a_root->parent.load() != a_root)
269                 {
270                   a_root = find(a_root);
271                   std::lock_guard<std::mutex> rlock(a_root->acc_mutex_);
272                   a_root->acc |= acc;
273                   acc |= a_root->acc;
274                 }
275               return acc;
276             }
277 
278           r = std::max(a_root, b_root);
279           q = std::min(a_root, b_root);
280 
281           if (!lock_root(q))
282             continue;
283 
284           break;
285         }
286 
287       uf_element* a_list = lock_list(a);
288       if (a_list == nullptr)
289         {
290           unlock_root(q);
291           return acc;
292         }
293 
294       uf_element* b_list = lock_list(b);
295       if (b_list == nullptr)
296         {
297           unlock_list(a_list);
298           unlock_root(q);
299           return acc;
300         }
301 
302       SPOT_ASSERT(a_list->list_status_.load() == list_status::LOCK);
303       SPOT_ASSERT(b_list->list_status_.load() == list_status::LOCK);
304 
305       //  Swapping
306       uf_element* a_next =  a_list->next_.load();
307       uf_element* b_next =  b_list->next_.load();
308       SPOT_ASSERT(a_next != nullptr);
309       SPOT_ASSERT(b_next != nullptr);
310 
311       a_list->next_.store(b_next);
312       b_list->next_.store(a_next);
313       q->parent.store(r);
314 
315       // Update workers
316       unsigned q_worker = q->worker_.load();
317       unsigned r_worker = r->worker_.load();
318       if ((q_worker|r_worker) != r_worker)
319         {
320           atomic_fetch_or(&(r->worker_), q_worker);
321           while (r->parent.load() != r)
322             {
323               r = find(r);
324               atomic_fetch_or(&(r->worker_), q_worker);
325             }
326         }
327 
328       // Update acceptance condition
329       {
330         std::lock_guard<std::mutex> rlock(r->acc_mutex_);
331         std::lock_guard<std::mutex> qlock(q->acc_mutex_);
332         q->acc |= acc;
333         r->acc |= q->acc;
334         acc |= r->acc;
335       }
336 
337       while (r->parent.load() != r)
338         {
339           r = find(r);
340           std::lock_guard<std::mutex> rlock(r->acc_mutex_);
341           std::lock_guard<std::mutex> qlock(q->acc_mutex_);
342           r->acc |= q->acc;
343           acc |= r->acc;
344         }
345 
346       unlock_list(a_list);
347       unlock_list(b_list);
348       unlock_root(q);
349       return acc;
350     }
351 
pick_from_list(uf_element * u,bool * sccfound)352     uf_element* pick_from_list(uf_element* u, bool* sccfound)
353     {
354       uf_element* a = u;
355       while (true)
356         {
357           list_status a_status;
358           while (true)
359             {
360               a_status = a->list_status_.load();
361 
362               if (a_status == list_status::BUSY)
363                 {
364                   return a;
365                 }
366 
367               if (a_status == list_status::DONE)
368                 break;
369             }
370 
371           uf_element* b = a->next_.load();
372 
373           // ------------------------------ NO LAZY : start
374           // if (b == u)
375           //   {
376           //     uf_element* a_root = find(a);
377           //     uf_status status = a_root->uf_status_.load();
378           //     while (status != uf_status::DEAD)
379           //       {
380           //         if (status == uf_status::LIVE)
381           //           *sccfound = std::atomic_compare_exchange_strong
382           //             (&(a_root->uf_status_), &status, uf_status::DEAD);
383           //         status = a_root->uf_status_.load();
384           //       }
385           //     return nullptr;
386           //   }
387           // a = b;
388           // ------------------------------ NO LAZY : end
389 
390           if (a == b)
391             {
392               uf_element* a_root = find(u);
393               uf_status status = a_root->uf_status_.load();
394               while (status != uf_status::DEAD)
395                 {
396                   if (status == uf_status::LIVE)
397                     *sccfound = std::atomic_compare_exchange_strong
398                       (&(a_root->uf_status_), &status, uf_status::DEAD);
399                   status = a_root->uf_status_.load();
400                 }
401               return nullptr;
402             }
403 
404           list_status b_status;
405           while (true)
406             {
407               b_status = b->list_status_.load();
408 
409               if (b_status == list_status::BUSY)
410                 {
411                   return b;
412                 }
413 
414               if (b_status == list_status::DONE)
415                 break;
416             }
417 
418           SPOT_ASSERT(b_status == list_status::DONE);
419           SPOT_ASSERT(a_status == list_status::DONE);
420 
421           uf_element* c = b->next_.load();
422           a->next_.store(c);
423           a = c;
424         }
425     }
426 
remove_from_list(uf_element * a)427     void remove_from_list(uf_element* a)
428     {
429       while (true)
430         {
431           list_status a_status = a->list_status_.load();
432 
433           if (a_status == list_status::DONE)
434             break;
435 
436           if (a_status == list_status::BUSY)
437             std::atomic_compare_exchange_strong
438               (&(a->list_status_), &a_status, list_status::DONE);
439         }
440     }
441 
inserted()442     unsigned inserted()
443     {
444       return inserted_;
445     }
446 
447   private:
448     iterable_uf_ec() = default;
449 
450     shared_map map_;      ///< \brief Map shared by threads copy!
451     unsigned tid_;        ///< \brief The Id of the current thread
452     unsigned size_;       ///< \brief Maximum number of thread
453     unsigned nb_th_;      ///< \brief Current number of threads
454     unsigned inserted_;   ///< \brief The number of insert succes
455     fixed_size_pool<pool_type::Unsafe> p_; ///< \brief The allocator
456   };
457 
458   /// \brief This class implements the SCC decomposition algorithm of bloemen
459   /// as described in PPOPP'16. It uses a shared union-find augmented to manage
460   /// work stealing between threads.
461   template<typename State, typename SuccIterator,
462            typename StateHash, typename StateEqual>
463   class swarmed_bloemen_ec
464   {
465   private:
466     swarmed_bloemen_ec() = delete;
467   public:
468 
469     using uf = iterable_uf_ec<State, StateHash, StateEqual>;
470     using uf_element = typename uf::uf_element;
471 
472     using shared_struct = uf;
473     using shared_map = typename uf::shared_map;
474 
make_shared_structure(shared_map m,unsigned i)475     static shared_struct* make_shared_structure(shared_map m, unsigned i)
476     {
477       return new uf(m, i);
478     }
479 
swarmed_bloemen_ec(kripkecube<State,SuccIterator> & sys,twacube_ptr twa,shared_map & map,iterable_uf_ec<State,StateHash,StateEqual> * uf,unsigned tid,std::atomic<bool> & stop)480     swarmed_bloemen_ec(kripkecube<State, SuccIterator>& sys,
481                        twacube_ptr twa,
482                        shared_map& map, /* useless here */
483                        iterable_uf_ec<State, StateHash, StateEqual>* uf,
484                        unsigned tid,
485                        std::atomic<bool>& stop):
486       sys_(sys),  twa_(twa), uf_(*uf), tid_(tid),
487       nb_th_(std::thread::hardware_concurrency()),
488       stop_(stop)
489     {
490       static_assert(spot::is_a_kripkecube_ptr<decltype(&sys),
491                                              State, SuccIterator>::value,
492                     "error: does not match the kripkecube requirements");
493     }
494 
495     ~swarmed_bloemen_ec() = default;
496 
run()497     void run()
498     {
499       setup();
500       State init_kripke = sys_.initial(tid_);
501       unsigned init_twa = twa_->get_initial();
502       auto pair = uf_.make_claim(init_kripke, init_twa);
503       todo_.push_back(pair.second);
504       Rp_.push_back(pair.second);
505       ++states_;
506 
507       while (!todo_.empty())
508         {
509         bloemen_recursive_start:
510           while (!stop_.load(std::memory_order_relaxed))
511             {
512               bool sccfound = false;
513               uf_element* v_prime = uf_.pick_from_list(todo_.back(), &sccfound);
514               if (v_prime == nullptr)
515                 {
516                   // The SCC has been explored!
517                   sccs_ += sccfound;
518                   break;
519                 }
520 
521               auto it_kripke = sys_.succ(v_prime->st_kripke, tid_);
522               auto it_prop = twa_->succ(v_prime->st_prop);
523               forward_iterators(sys_, twa_, it_kripke, it_prop, true, tid_);
524               while (!it_kripke->done())
525                 {
526                   auto w = uf_.make_claim(it_kripke->state(),
527                                           twa_->trans_storage(it_prop, tid_)
528                                           .dst);
529                   auto trans_acc = twa_->trans_storage(it_prop, tid_).acc_;
530                   ++transitions_;
531                   if (w.first == uf::claim_status::CLAIM_NEW)
532                     {
533                       todo_.push_back(w.second);
534                       Rp_.push_back(w.second);
535                       ++states_;
536                       sys_.recycle(it_kripke, tid_);
537                       goto bloemen_recursive_start;
538                     }
539                   else if (w.first == uf::claim_status::CLAIM_FOUND)
540                     {
541                       acc_cond::mark_t scc_acc = trans_acc;
542 
543                       // This operation is mandatory to update acceptance marks.
544                       // Otherwise, when w.second and todo.back() are
545                       // already in the same set, the acceptance condition will
546                       // not be added.
547                       scc_acc |= uf_.unite(w.second, w.second, scc_acc);
548 
549                       while (!uf_.sameset(todo_.back(), w.second))
550                         {
551                           uf_element* r = Rp_.back();
552                           Rp_.pop_back();
553                           uf_.unite(r, Rp_.back(), scc_acc);
554                         }
555 
556 
557                       {
558                         auto root = uf_.find(w.second);
559                         std::lock_guard<std::mutex> lock(w.second->acc_mutex_);
560                         scc_acc = w.second->acc;
561                       }
562 
563                       // cycle found in SCC and it contains acceptance condition
564                       if (twa_->acc().accepting(scc_acc))
565                         {
566                           sys_.recycle(it_kripke, tid_);
567                           stop_ = true;
568                           is_empty_ = false;
569                           tm_.stop("DFS thread " + std::to_string(tid_));
570                           return;
571                         }
572                     }
573                   forward_iterators(sys_, twa_, it_kripke, it_prop,
574                                     false, tid_);
575                 }
576               uf_.remove_from_list(v_prime);
577               sys_.recycle(it_kripke, tid_);
578             }
579 
580           if (todo_.back() == Rp_.back())
581             Rp_.pop_back();
582           todo_.pop_back();
583         }
584       finalize();
585     }
586 
setup()587     void setup()
588     {
589       tm_.start("DFS thread " + std::to_string(tid_));
590     }
591 
finalize()592     void finalize()
593     {
594       bool tst_val = false;
595       bool new_val = true;
596       bool exchanged = stop_.compare_exchange_strong(tst_val, new_val);
597       if (exchanged)
598         finisher_ = true;
599       tm_.stop("DFS thread " + std::to_string(tid_));
600     }
601 
finisher()602     bool finisher()
603     {
604       return finisher_;
605     }
606 
states()607     unsigned states()
608     {
609       return states_;
610     }
611 
transitions()612     unsigned transitions()
613     {
614       return transitions_;
615     }
616 
walltime()617     unsigned walltime()
618     {
619       return tm_.timer("DFS thread " + std::to_string(tid_)).walltime();
620     }
621 
name()622     std::string name()
623     {
624       return "bloemen_ec";
625     }
626 
sccs()627     int sccs()
628     {
629       return sccs_;
630     }
631 
result()632     mc_rvalue result()
633     {
634       return is_empty_ ? mc_rvalue::EMPTY : mc_rvalue::NOT_EMPTY;
635     }
636 
trace()637     std::string trace()
638     {
639       return "Not implemented";
640     }
641 
642   private:
643     kripkecube<State, SuccIterator>& sys_;   ///< \brief The system to check
644     twacube_ptr twa_;                        ///< \brief The formula to check
645     std::vector<uf_element*> todo_;          ///< \brief The "recursive" stack
646     std::vector<uf_element*> Rp_;            ///< \brief The DFS stack
647     iterable_uf_ec<State, StateHash, StateEqual> uf_; ///< Copy!
648     unsigned tid_;
649     unsigned nb_th_;
650     unsigned inserted_ = 0;           ///< \brief Number of states inserted
651     unsigned states_  = 0;            ///< \brief Number of states visited
652     unsigned transitions_ = 0;        ///< \brief Number of transitions visited
653     unsigned sccs_ = 0;               ///< \brief Number of SCC visited
654     bool is_empty_ = true;
655     spot::timer_map tm_;              ///< \brief Time execution
656     std::atomic<bool>& stop_;
657     bool finisher_ = false;
658   };
659 }
660