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 <stdlib.h> 25 #include <thread> 26 #include <vector> 27 #include <utility> 28 #include <spot/bricks/brick-hashset> 29 #include <spot/kripke/kripke.hh> 30 #include <spot/misc/common.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/mc.hh> 36 37 namespace spot 38 { 39 template<typename State, 40 typename StateHash, 41 typename StateEqual> 42 class iterable_uf 43 { 44 45 public: 46 enum class uf_status { LIVE, LOCK, DEAD }; 47 enum class list_status { BUSY, LOCK, DONE }; 48 enum class claim_status { CLAIM_FOUND, CLAIM_NEW, CLAIM_DEAD }; 49 50 /// \brief Represents a Union-Find element 51 struct uf_element 52 { 53 /// \brief the state handled by the element 54 State st_; 55 /// \brief reference to the pointer 56 std::atomic<uf_element*> parent; 57 /// The set of worker for a given state 58 std::atomic<unsigned> worker_; 59 /// \brief next element for work stealing 60 std::atomic<uf_element*> next_; 61 /// \brief current status for the element 62 std::atomic<uf_status> uf_status_; 63 ///< \brief current status for the list 64 std::atomic<list_status> list_status_; 65 }; 66 67 /// \brief The haser for the previous uf_element. 68 struct uf_element_hasher 69 { uf_element_hasherspot::iterable_uf::uf_element_hasher70 uf_element_hasher(const uf_element*) 71 { } 72 73 uf_element_hasher() = default; 74 75 brick::hash::hash128_t hashspot::iterable_uf::uf_element_hasher76 hash(const uf_element* lhs) const 77 { 78 StateHash hash; 79 // Not modulo 31 according to brick::hashset specifications. 80 unsigned u = hash(lhs->st_) % (1<<30); 81 return {u, u}; 82 } 83 equalspot::iterable_uf::uf_element_hasher84 bool equal(const uf_element* lhs, 85 const uf_element* rhs) const 86 { 87 StateEqual equal; 88 return equal(lhs->st_, rhs->st_); 89 } 90 }; 91 92 ///< \brief Shortcut to ease shared map manipulation 93 using shared_map = brick::hashset::FastConcurrent <uf_element*, 94 uf_element_hasher>; 95 iterable_uf(const iterable_uf<State,StateHash,StateEqual> & uf)96 iterable_uf(const iterable_uf<State, StateHash, StateEqual>& uf): 97 map_(uf.map_), tid_(uf.tid_), size_(std::thread::hardware_concurrency()), 98 nb_th_(std::thread::hardware_concurrency()), inserted_(0), 99 p_(sizeof(uf_element)) 100 { } 101 102 iterable_uf(shared_map & map,unsigned tid)103 iterable_uf(shared_map& map, unsigned tid): 104 map_(map), tid_(tid), size_(std::thread::hardware_concurrency()), 105 nb_th_(std::thread::hardware_concurrency()), inserted_(0), 106 p_(sizeof(uf_element)) 107 { 108 } 109 ~iterable_uf()110 ~iterable_uf() {} 111 112 std::pair<claim_status, uf_element*> make_claim(State a)113 make_claim(State a) 114 { 115 unsigned w_id = (1U << tid_); 116 117 // Setup and try to insert the new state in the shared map. 118 uf_element* v = (uf_element*) p_.allocate(); 119 v->st_ = a; 120 v->parent = v; 121 v->next_ = v; 122 v->worker_ = 0; 123 v->uf_status_ = uf_status::LIVE; 124 v->list_status_ = list_status::BUSY; 125 126 auto it = map_.insert({v}); 127 bool b = it.isnew(); 128 129 // Insertion failed, delete element 130 // FIXME Should we add a local cache to avoid useless allocations? 131 if (!b) 132 p_.deallocate(v); 133 else 134 ++inserted_; 135 136 uf_element* a_root = find(*it); 137 if (a_root->uf_status_.load() == uf_status::DEAD) 138 return {claim_status::CLAIM_DEAD, *it}; 139 140 if ((a_root->worker_.load() & w_id) != 0) 141 return {claim_status::CLAIM_FOUND, *it}; 142 143 atomic_fetch_or(&(a_root->worker_), w_id); 144 while (a_root->parent.load() != a_root) 145 { 146 a_root = find(a_root); 147 atomic_fetch_or(&(a_root->worker_), w_id); 148 } 149 150 return {claim_status::CLAIM_NEW, *it}; 151 } 152 find(uf_element * a)153 uf_element* find(uf_element* a) 154 { 155 uf_element* parent = a->parent.load(); 156 uf_element* x = a; 157 uf_element* y; 158 159 while (x != parent) 160 { 161 y = parent; 162 parent = y->parent.load(); 163 if (parent == y) 164 return y; 165 x->parent.store(parent); 166 x = parent; 167 parent = x->parent.load(); 168 } 169 return x; 170 } 171 sameset(uf_element * a,uf_element * b)172 bool sameset(uf_element* a, uf_element* b) 173 { 174 while (true) 175 { 176 uf_element* a_root = find(a); 177 uf_element* b_root = find(b); 178 if (a_root == b_root) 179 return true; 180 181 if (a_root->parent.load() == a_root) 182 return false; 183 } 184 } 185 lock_root(uf_element * a)186 bool lock_root(uf_element* a) 187 { 188 uf_status expected = uf_status::LIVE; 189 if (a->uf_status_.load() == expected) 190 { 191 if (std::atomic_compare_exchange_strong 192 (&(a->uf_status_), &expected, uf_status::LOCK)) 193 { 194 if (a->parent.load() == a) 195 return true; 196 unlock_root(a); 197 } 198 } 199 return false; 200 } 201 unlock_root(uf_element * a)202 inline void unlock_root(uf_element* a) 203 { 204 a->uf_status_.store(uf_status::LIVE); 205 } 206 lock_list(uf_element * a)207 uf_element* lock_list(uf_element* a) 208 { 209 uf_element* a_list = a; 210 while (true) 211 { 212 bool dontcare = false; 213 a_list = pick_from_list(a_list, &dontcare); 214 if (a_list == nullptr) 215 { 216 return nullptr; 217 } 218 219 auto expected = list_status::BUSY; 220 bool b = std::atomic_compare_exchange_strong 221 (&(a_list->list_status_), &expected, list_status::LOCK); 222 223 if (b) 224 return a_list; 225 226 a_list = a_list->next_.load(); 227 } 228 } 229 unlock_list(uf_element * a)230 void unlock_list(uf_element* a) 231 { 232 a->list_status_.store(list_status::BUSY); 233 } 234 unite(uf_element * a,uf_element * b)235 void unite(uf_element* a, uf_element* b) 236 { 237 uf_element* a_root; 238 uf_element* b_root; 239 uf_element* q; 240 uf_element* r; 241 242 while (true) 243 { 244 a_root = find(a); 245 b_root = find(b); 246 247 if (a_root == b_root) 248 return; 249 250 r = std::max(a_root, b_root); 251 q = std::min(a_root, b_root); 252 253 if (!lock_root(q)) 254 continue; 255 256 break; 257 } 258 259 uf_element* a_list = lock_list(a); 260 if (a_list == nullptr) 261 { 262 unlock_root(q); 263 return; 264 } 265 266 uf_element* b_list = lock_list(b); 267 if (b_list == nullptr) 268 { 269 unlock_list(a_list); 270 unlock_root(q); 271 return; 272 } 273 274 SPOT_ASSERT(a_list->list_status_.load() == list_status::LOCK); 275 SPOT_ASSERT(b_list->list_status_.load() == list_status::LOCK); 276 277 // Swapping 278 uf_element* a_next = a_list->next_.load(); 279 uf_element* b_next = b_list->next_.load(); 280 SPOT_ASSERT(a_next != nullptr); 281 SPOT_ASSERT(b_next != nullptr); 282 283 a_list->next_.store(b_next); 284 b_list->next_.store(a_next); 285 q->parent.store(r); 286 287 // Update workers 288 unsigned q_worker = q->worker_.load(); 289 unsigned r_worker = r->worker_.load(); 290 if ((q_worker|r_worker) != r_worker) 291 { 292 atomic_fetch_or(&(r->worker_), q_worker); 293 while (r->parent.load() != r) 294 { 295 r = find(r); 296 atomic_fetch_or(&(r->worker_), q_worker); 297 } 298 } 299 300 unlock_list(a_list); 301 unlock_list(b_list); 302 unlock_root(q); 303 } 304 pick_from_list(uf_element * u,bool * sccfound)305 uf_element* pick_from_list(uf_element* u, bool* sccfound) 306 { 307 uf_element* a = u; 308 while (true) 309 { 310 list_status a_status; 311 while (true) 312 { 313 a_status = a->list_status_.load(); 314 315 if (a_status == list_status::BUSY) 316 { 317 return a; 318 } 319 320 if (a_status == list_status::DONE) 321 break; 322 } 323 324 uf_element* b = a->next_.load(); 325 326 // ------------------------------ NO LAZY : start 327 // if (b == u) 328 // { 329 // uf_element* a_root = find(a); 330 // uf_status status = a_root->uf_status_.load(); 331 // while (status != uf_status::DEAD) 332 // { 333 // if (status == uf_status::LIVE) 334 // *sccfound = std::atomic_compare_exchange_strong 335 // (&(a_root->uf_status_), &status, uf_status::DEAD); 336 // status = a_root->uf_status_.load(); 337 // } 338 // return nullptr; 339 // } 340 // a = b; 341 // ------------------------------ NO LAZY : end 342 343 if (a == b) 344 { 345 uf_element* a_root = find(u); 346 uf_status status = a_root->uf_status_.load(); 347 while (status != uf_status::DEAD) 348 { 349 if (status == uf_status::LIVE) 350 *sccfound = std::atomic_compare_exchange_strong 351 (&(a_root->uf_status_), &status, uf_status::DEAD); 352 status = a_root->uf_status_.load(); 353 } 354 return nullptr; 355 } 356 357 list_status b_status; 358 while (true) 359 { 360 b_status = b->list_status_.load(); 361 362 if (b_status == list_status::BUSY) 363 { 364 return b; 365 } 366 367 if (b_status == list_status::DONE) 368 break; 369 } 370 371 SPOT_ASSERT(b_status == list_status::DONE); 372 SPOT_ASSERT(a_status == list_status::DONE); 373 374 uf_element* c = b->next_.load(); 375 a->next_.store(c); 376 a = c; 377 } 378 } 379 remove_from_list(uf_element * a)380 void remove_from_list(uf_element* a) 381 { 382 while (true) 383 { 384 list_status a_status = a->list_status_.load(); 385 386 if (a_status == list_status::DONE) 387 break; 388 389 if (a_status == list_status::BUSY) 390 std::atomic_compare_exchange_strong 391 (&(a->list_status_), &a_status, list_status::DONE); 392 } 393 } 394 inserted()395 unsigned inserted() 396 { 397 return inserted_; 398 } 399 400 private: 401 iterable_uf() = default; 402 403 shared_map map_; ///< \brief Map shared by threads copy! 404 unsigned tid_; ///< \brief The Id of the current thread 405 unsigned size_; ///< \brief Maximum number of thread 406 unsigned nb_th_; ///< \brief Current number of threads 407 unsigned inserted_; ///< \brief The number of insert succes 408 fixed_size_pool<pool_type::Unsafe> p_; ///< \brief The allocator 409 }; 410 411 /// \brief This class implements the SCC decomposition algorithm of bloemen 412 /// as described in PPOPP'16. It uses a shared union-find augmented to manage 413 /// work stealing between threads. 414 template<typename State, typename SuccIterator, 415 typename StateHash, typename StateEqual> 416 class swarmed_bloemen 417 { 418 private: 419 swarmed_bloemen() = delete; 420 421 public: 422 423 using uf = iterable_uf<State, StateHash, StateEqual>; 424 using uf_element = typename uf::uf_element; 425 426 using shared_struct = uf; 427 using shared_map = typename uf::shared_map; 428 make_shared_structure(shared_map m,unsigned i)429 static shared_struct* make_shared_structure(shared_map m, unsigned i) 430 { 431 return new uf(m, i); 432 } 433 swarmed_bloemen(kripkecube<State,SuccIterator> & sys,twacube_ptr,shared_map & map,iterable_uf<State,StateHash,StateEqual> * uf,unsigned tid,std::atomic<bool> & stop)434 swarmed_bloemen(kripkecube<State, SuccIterator>& sys, 435 twacube_ptr, /* useless here */ 436 shared_map& map, /* useless here */ 437 iterable_uf<State, StateHash, StateEqual>* uf, 438 unsigned tid, 439 std::atomic<bool>& stop): 440 sys_(sys), uf_(*uf), tid_(tid), 441 nb_th_(std::thread::hardware_concurrency()), 442 stop_(stop) 443 { 444 static_assert(spot::is_a_kripkecube_ptr<decltype(&sys), 445 State, SuccIterator>::value, 446 "error: does not match the kripkecube requirements"); 447 } 448 run()449 void run() 450 { 451 setup(); 452 State init = sys_.initial(tid_); 453 auto pair = uf_.make_claim(init); 454 todo_.push_back(pair.second); 455 Rp_.push_back(pair.second); 456 ++states_; 457 458 while (!todo_.empty()) 459 { 460 bloemen_recursive_start: 461 while (!stop_.load(std::memory_order_relaxed)) 462 { 463 bool sccfound = false; 464 uf_element* v_prime = uf_.pick_from_list(todo_.back(), &sccfound); 465 if (v_prime == nullptr) 466 { 467 // The SCC has been explored! 468 sccs_ += sccfound; 469 break; 470 } 471 472 auto it = sys_.succ(v_prime->st_, tid_); 473 while (!it->done()) 474 { 475 auto w = uf_.make_claim(it->state()); 476 it->next(); 477 ++transitions_; 478 if (w.first == uf::claim_status::CLAIM_NEW) 479 { 480 todo_.push_back(w.second); 481 Rp_.push_back(w.second); 482 ++states_; 483 sys_.recycle(it, tid_); 484 goto bloemen_recursive_start; 485 } 486 else if (w.first == uf::claim_status::CLAIM_FOUND) 487 { 488 while (!uf_.sameset(todo_.back(), w.second)) 489 { 490 uf_element* r = Rp_.back(); 491 Rp_.pop_back(); 492 uf_.unite(r, Rp_.back()); 493 } 494 } 495 } 496 uf_.remove_from_list(v_prime); 497 sys_.recycle(it, tid_); 498 } 499 500 if (todo_.back() == Rp_.back()) 501 Rp_.pop_back(); 502 todo_.pop_back(); 503 } 504 finalize(); 505 } 506 setup()507 void setup() 508 { 509 tm_.start("DFS thread " + std::to_string(tid_)); 510 } 511 finalize()512 void finalize() 513 { 514 bool tst_val = false; 515 bool new_val = true; 516 bool exchanged = stop_.compare_exchange_strong(tst_val, new_val); 517 if (exchanged) 518 finisher_ = true; 519 tm_.stop("DFS thread " + std::to_string(tid_)); 520 } 521 finisher()522 bool finisher() 523 { 524 return finisher_; 525 } 526 states()527 unsigned states() 528 { 529 return states_; 530 } 531 transitions()532 unsigned transitions() 533 { 534 return transitions_; 535 } 536 walltime()537 unsigned walltime() 538 { 539 return tm_.timer("DFS thread " + std::to_string(tid_)).walltime(); 540 } 541 name()542 std::string name() 543 { 544 return "bloemen_scc"; 545 } 546 sccs()547 int sccs() 548 { 549 return sccs_; 550 } 551 result()552 mc_rvalue result() 553 { 554 return mc_rvalue::SUCCESS; 555 } 556 trace()557 std::string trace() 558 { 559 // Returning a trace has no sense in this algorithm 560 return ""; 561 } 562 563 private: 564 kripkecube<State, SuccIterator>& sys_; ///< \brief The system to check 565 std::vector<uf_element*> todo_; ///< \brief The "recursive" stack 566 std::vector<uf_element*> Rp_; ///< \brief The DFS stack 567 iterable_uf<State, StateHash, StateEqual> uf_; ///< Copy! 568 unsigned tid_; 569 unsigned nb_th_; 570 unsigned inserted_ = 0; ///< \brief Number of states inserted 571 unsigned states_ = 0; ///< \brief Number of states visited 572 unsigned transitions_ = 0; ///< \brief Number of transitions visited 573 unsigned sccs_ = 0; ///< \brief Number of SCC visited 574 spot::timer_map tm_; ///< \brief Time execution 575 std::atomic<bool>& stop_; 576 bool finisher_ = false; 577 }; 578 } 579