You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

621 lines
19 KiB

  1. #include <argp.h>
  2. #include <inttypes.h>
  3. #include <locale.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <sys/time.h>
  8. #ifdef HAVE_PROFILER
  9. #include <gperftools/profiler.h>
  10. #endif
  11. #include <sylvan.h>
  12. #include <sylvan_table.h>
  13. /* Configuration */
  14. static int report_levels = 0; // report states at end of every level
  15. static int report_table = 0; // report table size at end of every level
  16. static int report_nodes = 0; // report number of nodes of BDDs
  17. static int strategy = 1; // set to 1 = use PAR strategy; set to 0 = use BFS strategy
  18. static int check_deadlocks = 0; // set to 1 to check for deadlocks
  19. static int merge_relations = 0; // merge relations to 1 relation
  20. static int print_transition_matrix = 0; // print transition relation matrix
  21. static int workers = 0; // autodetect
  22. static char* model_filename = NULL; // filename of model
  23. #ifdef HAVE_PROFILER
  24. static char* profile_filename = NULL; // filename for profiling
  25. #endif
  26. /* argp configuration */
  27. static struct argp_option options[] =
  28. {
  29. {"workers", 'w', "<workers>", 0, "Number of workers (default=0: autodetect)", 0},
  30. {"strategy", 's', "<bfs|par|sat>", 0, "Strategy for reachability (default=par)", 0},
  31. #ifdef HAVE_PROFILER
  32. {"profiler", 'p', "<filename>", 0, "Filename for profiling", 0},
  33. #endif
  34. {"deadlocks", 3, 0, 0, "Check for deadlocks", 1},
  35. {"count-nodes", 5, 0, 0, "Report #nodes for BDDs", 1},
  36. {"count-states", 1, 0, 0, "Report #states at each level", 1},
  37. {"count-table", 2, 0, 0, "Report table usage at each level", 1},
  38. {"merge-relations", 6, 0, 0, "Merge transition relations into one transition relation", 1},
  39. {"print-matrix", 4, 0, 0, "Print transition matrix", 1},
  40. {0, 0, 0, 0, 0, 0}
  41. };
  42. static error_t
  43. parse_opt(int key, char *arg, struct argp_state *state)
  44. {
  45. switch (key) {
  46. case 'w':
  47. workers = atoi(arg);
  48. break;
  49. case 's':
  50. if (strcmp(arg, "bfs")==0) strategy = 0;
  51. else if (strcmp(arg, "par")==0) strategy = 1;
  52. else if (strcmp(arg, "sat")==0) strategy = 2;
  53. else argp_usage(state);
  54. break;
  55. case 4:
  56. print_transition_matrix = 1;
  57. break;
  58. case 3:
  59. check_deadlocks = 1;
  60. break;
  61. case 1:
  62. report_levels = 1;
  63. break;
  64. case 2:
  65. report_table = 1;
  66. break;
  67. case 5:
  68. report_nodes = 1;
  69. break;
  70. case 6:
  71. merge_relations = 1;
  72. break;
  73. #ifdef HAVE_PROFILER
  74. case 'p':
  75. profile_filename = arg;
  76. break;
  77. #endif
  78. case ARGP_KEY_ARG:
  79. if (state->arg_num >= 1) argp_usage(state);
  80. model_filename = arg;
  81. break;
  82. case ARGP_KEY_END:
  83. if (state->arg_num < 1) argp_usage(state);
  84. break;
  85. default:
  86. return ARGP_ERR_UNKNOWN;
  87. }
  88. return 0;
  89. }
  90. static struct argp argp = { options, parse_opt, "<model>", 0, 0, 0, 0 };
  91. /* Globals */
  92. typedef struct set
  93. {
  94. BDD bdd;
  95. BDD variables; // all variables in the set (used by satcount)
  96. } *set_t;
  97. typedef struct relation
  98. {
  99. BDD bdd;
  100. BDD variables; // all variables in the relation (used by relprod)
  101. } *rel_t;
  102. static int vector_size; // size of vector
  103. static int statebits, actionbits; // number of bits for state, number of bits for action
  104. static int bits_per_integer; // number of bits per integer in the vector
  105. static int next_count; // number of partitions of the transition relation
  106. static rel_t *next; // each partition of the transition relation
  107. /* Obtain current wallclock time */
  108. static double
  109. wctime()
  110. {
  111. struct timeval tv;
  112. gettimeofday(&tv, NULL);
  113. return (tv.tv_sec + 1E-6 * tv.tv_usec);
  114. }
  115. static double t_start;
  116. #define INFO(s, ...) fprintf(stdout, "[% 8.2f] " s, wctime()-t_start, ##__VA_ARGS__)
  117. #define Abort(...) { fprintf(stderr, __VA_ARGS__); exit(-1); }
  118. /* Load a set from file */
  119. #define set_load(f) CALL(set_load, f)
  120. TASK_1(set_t, set_load, FILE*, f)
  121. {
  122. sylvan_serialize_fromfile(f);
  123. size_t set_bdd, set_vector_size, set_state_vars;
  124. if ((fread(&set_bdd, sizeof(size_t), 1, f) != 1) ||
  125. (fread(&set_vector_size, sizeof(size_t), 1, f) != 1) ||
  126. (fread(&set_state_vars, sizeof(size_t), 1, f) != 1)) {
  127. Abort("Invalid input file!\n");
  128. }
  129. set_t set = (set_t)malloc(sizeof(struct set));
  130. set->bdd = sylvan_serialize_get_reversed(set_bdd);
  131. set->variables = sylvan_support(sylvan_serialize_get_reversed(set_state_vars));
  132. sylvan_protect(&set->bdd);
  133. sylvan_protect(&set->variables);
  134. return set;
  135. }
  136. /* Load a relation from file */
  137. #define rel_load(f) CALL(rel_load, f)
  138. TASK_1(rel_t, rel_load, FILE*, f)
  139. {
  140. sylvan_serialize_fromfile(f);
  141. size_t rel_bdd, rel_vars;
  142. if ((fread(&rel_bdd, sizeof(size_t), 1, f) != 1) ||
  143. (fread(&rel_vars, sizeof(size_t), 1, f) != 1)) {
  144. Abort("Invalid input file!\n");
  145. }
  146. rel_t rel = (rel_t)malloc(sizeof(struct relation));
  147. rel->bdd = sylvan_serialize_get_reversed(rel_bdd);
  148. rel->variables = sylvan_support(sylvan_serialize_get_reversed(rel_vars));
  149. sylvan_protect(&rel->bdd);
  150. sylvan_protect(&rel->variables);
  151. return rel;
  152. }
  153. #define print_example(example, variables) CALL(print_example, example, variables)
  154. VOID_TASK_2(print_example, BDD, example, BDDSET, variables)
  155. {
  156. uint8_t str[vector_size * bits_per_integer];
  157. if (example != sylvan_false) {
  158. sylvan_sat_one(example, variables, str);
  159. printf("[");
  160. for (int i=0; i<vector_size; i++) {
  161. uint32_t res = 0;
  162. for (int j=0; j<bits_per_integer; j++) {
  163. if (str[bits_per_integer*i+j] == 1) res++;
  164. res<<=1;
  165. }
  166. if (i>0) printf(",");
  167. printf("%" PRIu32, res);
  168. }
  169. printf("]");
  170. }
  171. }
  172. /* Straight-forward implementation of parallel reduction */
  173. TASK_5(BDD, go_par, BDD, cur, BDD, visited, size_t, from, size_t, len, BDD*, deadlocks)
  174. {
  175. if (len == 1) {
  176. // Calculate NEW successors (not in visited)
  177. BDD succ = sylvan_relnext(cur, next[from]->bdd, next[from]->variables);
  178. bdd_refs_push(succ);
  179. if (deadlocks) {
  180. // check which BDDs in deadlocks do not have a successor in this relation
  181. BDD anc = sylvan_relprev(next[from]->bdd, succ, next[from]->variables);
  182. bdd_refs_push(anc);
  183. *deadlocks = sylvan_diff(*deadlocks, anc);
  184. bdd_refs_pop(1);
  185. }
  186. BDD result = sylvan_diff(succ, visited);
  187. bdd_refs_pop(1);
  188. return result;
  189. } else {
  190. BDD deadlocks_left;
  191. BDD deadlocks_right;
  192. if (deadlocks) {
  193. deadlocks_left = *deadlocks;
  194. deadlocks_right = *deadlocks;
  195. sylvan_protect(&deadlocks_left);
  196. sylvan_protect(&deadlocks_right);
  197. }
  198. // Recursively calculate left+right
  199. bdd_refs_spawn(SPAWN(go_par, cur, visited, from, (len+1)/2, deadlocks ? &deadlocks_left: NULL));
  200. BDD right = bdd_refs_push(CALL(go_par, cur, visited, from+(len+1)/2, len/2, deadlocks ? &deadlocks_right : NULL));
  201. BDD left = bdd_refs_push(bdd_refs_sync(SYNC(go_par)));
  202. // Merge results of left+right
  203. BDD result = sylvan_or(left, right);
  204. bdd_refs_pop(2);
  205. if (deadlocks) {
  206. bdd_refs_push(result);
  207. *deadlocks = sylvan_and(deadlocks_left, deadlocks_right);
  208. sylvan_unprotect(&deadlocks_left);
  209. sylvan_unprotect(&deadlocks_right);
  210. bdd_refs_pop(1);
  211. }
  212. return result;
  213. }
  214. }
  215. /* PAR strategy, parallel strategy (operations called in parallel *and* parallelized by Sylvan) */
  216. VOID_TASK_1(par, set_t, set)
  217. {
  218. BDD visited = set->bdd;
  219. BDD next_level = visited;
  220. BDD cur_level = sylvan_false;
  221. BDD deadlocks = sylvan_false;
  222. sylvan_protect(&visited);
  223. sylvan_protect(&next_level);
  224. sylvan_protect(&cur_level);
  225. sylvan_protect(&deadlocks);
  226. int iteration = 1;
  227. do {
  228. // calculate successors in parallel
  229. cur_level = next_level;
  230. deadlocks = cur_level;
  231. next_level = CALL(go_par, cur_level, visited, 0, next_count, check_deadlocks ? &deadlocks : NULL);
  232. if (check_deadlocks && deadlocks != sylvan_false) {
  233. INFO("Found %'0.0f deadlock states... ", sylvan_satcount(deadlocks, set->variables));
  234. if (deadlocks != sylvan_false) {
  235. printf("example: ");
  236. print_example(deadlocks, set->variables);
  237. check_deadlocks = 0;
  238. }
  239. printf("\n");
  240. }
  241. // visited = visited + new
  242. visited = sylvan_or(visited, next_level);
  243. if (report_table && report_levels) {
  244. size_t filled, total;
  245. sylvan_table_usage(&filled, &total);
  246. INFO("Level %d done, %'0.0f states explored, table: %0.1f%% full (%'zu nodes)\n",
  247. iteration, sylvan_satcount(visited, set->variables),
  248. 100.0*(double)filled/total, filled);
  249. } else if (report_table) {
  250. size_t filled, total;
  251. sylvan_table_usage(&filled, &total);
  252. INFO("Level %d done, table: %0.1f%% full (%'zu nodes)\n",
  253. iteration,
  254. 100.0*(double)filled/total, filled);
  255. } else if (report_levels) {
  256. INFO("Level %d done, %'0.0f states explored\n", iteration, sylvan_satcount(visited, set->variables));
  257. } else {
  258. INFO("Level %d done\n", iteration);
  259. }
  260. iteration++;
  261. } while (next_level != sylvan_false);
  262. set->bdd = visited;
  263. sylvan_unprotect(&visited);
  264. sylvan_unprotect(&next_level);
  265. sylvan_unprotect(&cur_level);
  266. sylvan_unprotect(&deadlocks);
  267. }
  268. /* Sequential version of merge-reduction */
  269. TASK_5(BDD, go_bfs, BDD, cur, BDD, visited, size_t, from, size_t, len, BDD*, deadlocks)
  270. {
  271. if (len == 1) {
  272. // Calculate NEW successors (not in visited)
  273. BDD succ = sylvan_relnext(cur, next[from]->bdd, next[from]->variables);
  274. bdd_refs_push(succ);
  275. if (deadlocks) {
  276. // check which BDDs in deadlocks do not have a successor in this relation
  277. BDD anc = sylvan_relprev(next[from]->bdd, succ, next[from]->variables);
  278. bdd_refs_push(anc);
  279. *deadlocks = sylvan_diff(*deadlocks, anc);
  280. bdd_refs_pop(1);
  281. }
  282. BDD result = sylvan_diff(succ, visited);
  283. bdd_refs_pop(1);
  284. return result;
  285. } else {
  286. BDD deadlocks_left;
  287. BDD deadlocks_right;
  288. if (deadlocks) {
  289. deadlocks_left = *deadlocks;
  290. deadlocks_right = *deadlocks;
  291. sylvan_protect(&deadlocks_left);
  292. sylvan_protect(&deadlocks_right);
  293. }
  294. // Recursively calculate left+right
  295. BDD left = CALL(go_bfs, cur, visited, from, (len+1)/2, deadlocks ? &deadlocks_left : NULL);
  296. bdd_refs_push(left);
  297. BDD right = CALL(go_bfs, cur, visited, from+(len+1)/2, len/2, deadlocks ? &deadlocks_right : NULL);
  298. bdd_refs_push(right);
  299. // Merge results of left+right
  300. BDD result = sylvan_or(left, right);
  301. bdd_refs_pop(2);
  302. if (deadlocks) {
  303. bdd_refs_push(result);
  304. *deadlocks = sylvan_and(deadlocks_left, deadlocks_right);
  305. sylvan_unprotect(&deadlocks_left);
  306. sylvan_unprotect(&deadlocks_right);
  307. bdd_refs_pop(1);
  308. }
  309. return result;
  310. }
  311. }
  312. /* BFS strategy, sequential strategy (but operations are parallelized by Sylvan) */
  313. VOID_TASK_1(bfs, set_t, set)
  314. {
  315. BDD visited = set->bdd;
  316. BDD next_level = visited;
  317. BDD cur_level = sylvan_false;
  318. BDD deadlocks = sylvan_false;
  319. sylvan_protect(&visited);
  320. sylvan_protect(&next_level);
  321. sylvan_protect(&cur_level);
  322. sylvan_protect(&deadlocks);
  323. int iteration = 1;
  324. do {
  325. // calculate successors in parallel
  326. cur_level = next_level;
  327. deadlocks = cur_level;
  328. next_level = CALL(go_bfs, cur_level, visited, 0, next_count, check_deadlocks ? &deadlocks : NULL);
  329. if (check_deadlocks && deadlocks != sylvan_false) {
  330. INFO("Found %'0.0f deadlock states... ", sylvan_satcount(deadlocks, set->variables));
  331. if (deadlocks != sylvan_false) {
  332. printf("example: ");
  333. print_example(deadlocks, set->variables);
  334. check_deadlocks = 0;
  335. }
  336. printf("\n");
  337. }
  338. // visited = visited + new
  339. visited = sylvan_or(visited, next_level);
  340. if (report_table && report_levels) {
  341. size_t filled, total;
  342. sylvan_table_usage(&filled, &total);
  343. INFO("Level %d done, %'0.0f states explored, table: %0.1f%% full (%'zu nodes)\n",
  344. iteration, sylvan_satcount(visited, set->variables),
  345. 100.0*(double)filled/total, filled);
  346. } else if (report_table) {
  347. size_t filled, total;
  348. sylvan_table_usage(&filled, &total);
  349. INFO("Level %d done, table: %0.1f%% full (%'zu nodes)\n",
  350. iteration,
  351. 100.0*(double)filled/total, filled);
  352. } else if (report_levels) {
  353. INFO("Level %d done, %'0.0f states explored\n", iteration, sylvan_satcount(visited, set->variables));
  354. } else {
  355. INFO("Level %d done\n", iteration);
  356. }
  357. iteration++;
  358. } while (next_level != sylvan_false);
  359. set->bdd = visited;
  360. sylvan_unprotect(&visited);
  361. sylvan_unprotect(&next_level);
  362. sylvan_unprotect(&cur_level);
  363. sylvan_unprotect(&deadlocks);
  364. }
  365. /**
  366. * Extend a transition relation to a larger domain (using s=s')
  367. */
  368. #define extend_relation(rel, vars) CALL(extend_relation, rel, vars)
  369. TASK_2(BDD, extend_relation, BDD, relation, BDDSET, variables)
  370. {
  371. /* first determine which state BDD variables are in rel */
  372. int has[statebits];
  373. for (int i=0; i<statebits; i++) has[i] = 0;
  374. BDDSET s = variables;
  375. while (!sylvan_set_isempty(s)) {
  376. BDDVAR v = sylvan_set_first(s);
  377. if (v/2 >= (unsigned)statebits) break; // action labels
  378. has[v/2] = 1;
  379. s = sylvan_set_next(s);
  380. }
  381. /* create "s=s'" for all variables not in rel */
  382. BDD eq = sylvan_true;
  383. for (int i=statebits-1; i>=0; i--) {
  384. if (has[i]) continue;
  385. BDD low = sylvan_makenode(2*i+1, eq, sylvan_false);
  386. bdd_refs_push(low);
  387. BDD high = sylvan_makenode(2*i+1, sylvan_false, eq);
  388. bdd_refs_pop(1);
  389. eq = sylvan_makenode(2*i, low, high);
  390. }
  391. bdd_refs_push(eq);
  392. BDD result = sylvan_and(relation, eq);
  393. bdd_refs_pop(1);
  394. return result;
  395. }
  396. /**
  397. * Compute \BigUnion ( sets[i] )
  398. */
  399. #define big_union(first, count) CALL(big_union, first, count)
  400. TASK_2(BDD, big_union, int, first, int, count)
  401. {
  402. if (count == 1) return next[first]->bdd;
  403. bdd_refs_spawn(SPAWN(big_union, first, count/2));
  404. BDD right = bdd_refs_push(CALL(big_union, first+count/2, count-count/2));
  405. BDD left = bdd_refs_push(bdd_refs_sync(SYNC(big_union)));
  406. BDD result = sylvan_or(left, right);
  407. bdd_refs_pop(2);
  408. return result;
  409. }
  410. static void
  411. print_matrix(BDD vars)
  412. {
  413. for (int i=0; i<vector_size; i++) {
  414. if (sylvan_set_isempty(vars)) {
  415. fprintf(stdout, "-");
  416. } else {
  417. BDDVAR next_s = 2*((i+1)*bits_per_integer);
  418. if (sylvan_set_first(vars) < next_s) {
  419. fprintf(stdout, "+");
  420. for (;;) {
  421. vars = sylvan_set_next(vars);
  422. if (sylvan_set_isempty(vars)) break;
  423. if (sylvan_set_first(vars) >= next_s) break;
  424. }
  425. } else {
  426. fprintf(stdout, "-");
  427. }
  428. }
  429. }
  430. }
  431. VOID_TASK_0(gc_start)
  432. {
  433. INFO("(GC) Starting garbage collection...\n");
  434. }
  435. VOID_TASK_0(gc_end)
  436. {
  437. INFO("(GC) Garbage collection done.\n");
  438. }
  439. int
  440. main(int argc, char **argv)
  441. {
  442. argp_parse(&argp, argc, argv, 0, 0, 0);
  443. setlocale(LC_NUMERIC, "en_US.utf-8");
  444. t_start = wctime();
  445. FILE *f = fopen(model_filename, "r");
  446. if (f == NULL) {
  447. fprintf(stderr, "Cannot open file '%s'!\n", model_filename);
  448. return -1;
  449. }
  450. // Init Lace
  451. lace_init(workers, 1000000); // auto-detect number of workers, use a 1,000,000 size task queue
  452. lace_startup(0, NULL, NULL); // auto-detect program stack, do not use a callback for startup
  453. LACE_ME;
  454. // Init Sylvan
  455. // Nodes table size: 24 bytes * 2**N_nodes
  456. // Cache table size: 36 bytes * 2**N_cache
  457. // With: N_nodes=25, N_cache=24: 1.3 GB memory
  458. sylvan_set_sizes(1LL<<21, 1LL<<27, 1LL<<20, 1LL<<26);
  459. sylvan_init_package();
  460. sylvan_set_granularity(6); // granularity 6 is decent default value - 1 means "use cache for every operation"
  461. sylvan_init_bdd();
  462. sylvan_gc_hook_pregc(TASK(gc_start));
  463. sylvan_gc_hook_postgc(TASK(gc_end));
  464. /* Load domain information */
  465. if ((fread(&vector_size, sizeof(int), 1, f) != 1) ||
  466. (fread(&statebits, sizeof(int), 1, f) != 1) ||
  467. (fread(&actionbits, sizeof(int), 1, f) != 1)) {
  468. Abort("Invalid input file!\n");
  469. }
  470. bits_per_integer = statebits;
  471. statebits *= vector_size;
  472. // Read initial state
  473. set_t states = set_load(f);
  474. // Read transitions
  475. if (fread(&next_count, sizeof(int), 1, f) != 1) Abort("Invalid input file!\n");
  476. next = (rel_t*)malloc(sizeof(rel_t) * next_count);
  477. int i;
  478. for (i=0; i<next_count; i++) {
  479. next[i] = rel_load(f);
  480. }
  481. /* Done */
  482. fclose(f);
  483. if (print_transition_matrix) {
  484. for (i=0; i<next_count; i++) {
  485. INFO("");
  486. print_matrix(next[i]->variables);
  487. fprintf(stdout, "\n");
  488. }
  489. }
  490. // Report statistics
  491. INFO("Read file '%s'\n", model_filename);
  492. INFO("%d integers per state, %d bits per integer, %d transition groups\n", vector_size, bits_per_integer, next_count);
  493. if (merge_relations) {
  494. BDD prime_variables = sylvan_set_empty();
  495. for (int i=statebits-1; i>=0; i--) {
  496. bdd_refs_push(prime_variables);
  497. prime_variables = sylvan_set_add(prime_variables, i*2+1);
  498. bdd_refs_pop(1);
  499. }
  500. bdd_refs_push(prime_variables);
  501. INFO("Extending transition relations to full domain.\n");
  502. for (int i=0; i<next_count; i++) {
  503. next[i]->bdd = extend_relation(next[i]->bdd, next[i]->variables);
  504. next[i]->variables = prime_variables;
  505. }
  506. INFO("Taking union of all transition relations.\n");
  507. next[0]->bdd = big_union(0, next_count);
  508. next_count = 1;
  509. }
  510. if (report_nodes) {
  511. INFO("BDD nodes:\n");
  512. INFO("Initial states: %zu BDD nodes\n", sylvan_nodecount(states->bdd));
  513. for (i=0; i<next_count; i++) {
  514. INFO("Transition %d: %zu BDD nodes\n", i, sylvan_nodecount(next[i]->bdd));
  515. }
  516. }
  517. #ifdef HAVE_PROFILER
  518. if (profile_filename != NULL) ProfilerStart(profile_filename);
  519. #endif
  520. if (strategy == 1) {
  521. double t1 = wctime();
  522. CALL(par, states);
  523. double t2 = wctime();
  524. INFO("PAR Time: %f\n", t2-t1);
  525. } else {
  526. double t1 = wctime();
  527. CALL(bfs, states);
  528. double t2 = wctime();
  529. INFO("BFS Time: %f\n", t2-t1);
  530. }
  531. #ifdef HAVE_PROFILER
  532. if (profile_filename != NULL) ProfilerStop();
  533. #endif
  534. // Now we just have states
  535. INFO("Final states: %'0.0f states\n", sylvan_satcount(states->bdd, states->variables));
  536. if (report_nodes) {
  537. INFO("Final states: %'zu BDD nodes\n", sylvan_nodecount(states->bdd));
  538. }
  539. sylvan_stats_report(stdout);
  540. return 0;
  541. }