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.
		
		
		
		
		
			
		
			
				
					
					
						
							864 lines
						
					
					
						
							24 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							864 lines
						
					
					
						
							24 KiB
						
					
					
				| #include <argp.h> | |
| #include <inttypes.h> | |
| #include <locale.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/time.h> | |
|  | |
| #ifdef HAVE_PROFILER | |
| #include <gperftools/profiler.h> | |
| #endif | |
|  | |
| #include <getrss.h> | |
|  | |
| #include <sylvan_int.h> | |
|  | |
| /* Configuration (via argp) */ | |
| static int report_levels = 0; // report states at start of every level | |
| static int report_table = 0; // report table size at end of every level | |
| static int report_nodes = 0; // report number of nodes of LDDs | |
| static int strategy = 2; // 0 = BFS, 1 = PAR, 2 = SAT, 3 = CHAINING | |
| static int check_deadlocks = 0; // set to 1 to check for deadlocks on-the-fly | |
| static int print_transition_matrix = 0; // print transition relation matrix | |
| static int workers = 0; // autodetect | |
| static char* model_filename = NULL; // filename of model | |
| static char* out_filename = NULL; // filename of output | |
| #ifdef HAVE_PROFILER | |
| static char* profile_filename = NULL; // filename for profiling | |
| #endif | |
|  | |
| /* argp configuration */ | |
| static struct argp_option options[] = | |
| { | |
|     {"workers", 'w', "<workers>", 0, "Number of workers (default=0: autodetect)", 0}, | |
|     {"strategy", 's', "<bfs|par|sat|chaining>", 0, "Strategy for reachability (default=par)", 0}, | |
| #ifdef HAVE_PROFILER | |
|     {"profiler", 'p', "<filename>", 0, "Filename for profiling", 0}, | |
| #endif | |
|     {"deadlocks", 3, 0, 0, "Check for deadlocks", 1}, | |
|     {"count-nodes", 5, 0, 0, "Report #nodes for LDDs", 1}, | |
|     {"count-states", 1, 0, 0, "Report #states at each level", 1}, | |
|     {"count-table", 2, 0, 0, "Report table usage at each level", 1}, | |
|     {"print-matrix", 4, 0, 0, "Print transition matrix", 1}, | |
|     {0, 0, 0, 0, 0, 0} | |
| }; | |
| 
 | |
| static error_t | |
| parse_opt(int key, char *arg, struct argp_state *state) | |
| { | |
|     switch (key) { | |
|     case 'w': | |
|         workers = atoi(arg); | |
|         break; | |
|     case 's': | |
|         if (strcmp(arg, "bfs")==0) strategy = 0; | |
|         else if (strcmp(arg, "par")==0) strategy = 1; | |
|         else if (strcmp(arg, "sat")==0) strategy = 2; | |
|         else if (strcmp(arg, "chaining")==0) strategy = 3; | |
|         else argp_usage(state); | |
|         break; | |
|     case 4: | |
|         print_transition_matrix = 1; | |
|         break; | |
|     case 3: | |
|         check_deadlocks = 1; | |
|         break; | |
|     case 1: | |
|         report_levels = 1; | |
|         break; | |
|     case 2: | |
|         report_table = 1; | |
|         break; | |
|     case 5: | |
|         report_nodes = 1; | |
|         break; | |
| #ifdef HAVE_PROFILER | |
|     case 'p': | |
|         profile_filename = arg; | |
|         break; | |
| #endif | |
|     case ARGP_KEY_ARG: | |
|         if (state->arg_num == 0) model_filename = arg; | |
|         if (state->arg_num == 1) out_filename = arg; | |
|         if (state->arg_num >= 2) argp_usage(state); | |
|         break;  | |
|     case ARGP_KEY_END: | |
|         if (state->arg_num < 1) argp_usage(state); | |
|         break; | |
|     default: | |
|         return ARGP_ERR_UNKNOWN; | |
|     } | |
|     return 0; | |
| } | |
| 
 | |
| static struct argp argp = { options, parse_opt, "<model> [<output-bdd>]", 0, 0, 0, 0 }; | |
| 
 | |
| /** | |
|  * Types (set and relation) | |
|  */ | |
| typedef struct set | |
| { | |
|     MDD dd; | |
| } *set_t; | |
| 
 | |
| typedef struct relation | |
| { | |
|     MDD dd; | |
|     MDD meta; // for relprod | |
|     int r_k, w_k, *r_proj, *w_proj; | |
|     int firstvar; // for saturation/chaining | |
|     MDD topmeta; // for saturation | |
| } *rel_t; | |
| 
 | |
| static int vector_size; // size of vector in integers | |
| static int next_count; // number of partitions of the transition relation | |
| static rel_t *next; // each partition of the transition relation | |
|  | |
| /** | |
|  * Obtain current wallclock time | |
|  */ | |
| static double | |
| wctime() | |
| { | |
|     struct timeval tv; | |
|     gettimeofday(&tv, NULL); | |
|     return (tv.tv_sec + 1E-6 * tv.tv_usec); | |
| } | |
| 
 | |
| static double t_start; | |
| #define INFO(s, ...) fprintf(stdout, "[% 8.2f] " s, wctime()-t_start, ##__VA_ARGS__) | |
| #define Abort(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "Abort at line %d!\n", __LINE__); exit(-1); } | |
|  | |
| /** | |
|  * Load a set from file | |
|  */ | |
| static set_t | |
| set_load(FILE* f) | |
| { | |
|     set_t set = (set_t)malloc(sizeof(struct set)); | |
| 
 | |
|     /* read projection (actually we don't support projection) */ | |
|     int k; | |
|     if (fread(&k, sizeof(int), 1, f) != 1) Abort("Invalid input file!\n"); | |
|     if (k != -1) Abort("Invalid input file!\n"); // only support full vector | |
|  | |
|     /* read dd */ | |
|     lddmc_serialize_fromfile(f); | |
|     size_t dd; | |
|     if (fread(&dd, sizeof(size_t), 1, f) != 1) Abort("Invalid input file!\n"); | |
|     set->dd = lddmc_serialize_get_reversed(dd); | |
|     lddmc_protect(&set->dd); | |
| 
 | |
|     return set; | |
| } | |
| 
 | |
| /** | |
|  * Save a set to file | |
|  */ | |
| static void | |
| set_save(FILE* f, set_t set) | |
| { | |
|     int k = -1; | |
|     fwrite(&k, sizeof(int), 1, f); | |
|     size_t dd = lddmc_serialize_add(set->dd); | |
|     lddmc_serialize_tofile(f); | |
|     fwrite(&dd, sizeof(size_t), 1, f); | |
| } | |
| 
 | |
| /** | |
|  * Load a relation from file | |
|  */ | |
| #define rel_load_proj(f) CALL(rel_load_proj, f) | |
| TASK_1(rel_t, rel_load_proj, FILE*, f) | |
| { | |
|     int r_k, w_k; | |
|     if (fread(&r_k, sizeof(int), 1, f) != 1) Abort("Invalid file format."); | |
|     if (fread(&w_k, sizeof(int), 1, f) != 1) Abort("Invalid file format."); | |
| 
 | |
|     rel_t rel = (rel_t)malloc(sizeof(struct relation)); | |
|     rel->r_k = r_k; | |
|     rel->w_k = w_k; | |
|     rel->r_proj = (int*)malloc(sizeof(int[rel->r_k])); | |
|     rel->w_proj = (int*)malloc(sizeof(int[rel->w_k])); | |
| 
 | |
|     if (fread(rel->r_proj, sizeof(int), rel->r_k, f) != (size_t)rel->r_k) Abort("Invalid file format."); | |
|     if (fread(rel->w_proj, sizeof(int), rel->w_k, f) != (size_t)rel->w_k) Abort("Invalid file format."); | |
| 
 | |
|     int *r_proj = rel->r_proj; | |
|     int *w_proj = rel->w_proj; | |
| 
 | |
|     rel->firstvar = -1; | |
| 
 | |
|     /* Compute the meta */ | |
|     uint32_t meta[vector_size*2+2]; | |
|     memset(meta, 0, sizeof(uint32_t[vector_size*2+2])); | |
|     int r_i=0, w_i=0, i=0, j=0; | |
|     for (;;) { | |
|         int type = 0; | |
|         if (r_i < r_k && r_proj[r_i] == i) { | |
|             r_i++; | |
|             type += 1; // read | |
|         } | |
|         if (w_i < w_k && w_proj[w_i] == i) { | |
|             w_i++; | |
|             type += 2; // write | |
|         } | |
|         if (type == 0) meta[j++] = 0; | |
|         else if (type == 1) { meta[j++] = 3; } | |
|         else if (type == 2) { meta[j++] = 4; } | |
|         else if (type == 3) { meta[j++] = 1; meta[j++] = 2; } | |
|         if (type != 0 && rel->firstvar == -1) rel->firstvar = i; | |
|         if (r_i == r_k && w_i == w_k) { | |
|             meta[j++] = 5; // action label | |
|             meta[j++] = (uint32_t)-1; | |
|             break; | |
|         } | |
|         i++; | |
|     } | |
| 
 | |
|     rel->meta = lddmc_cube((uint32_t*)meta, j); | |
|     lddmc_protect(&rel->meta); | |
|     if (rel->firstvar != -1) { | |
|         rel->topmeta = lddmc_cube((uint32_t*)meta+rel->firstvar, j-rel->firstvar); | |
|         lddmc_protect(&rel->topmeta); | |
|     } | |
|     rel->dd = lddmc_false; | |
|     lddmc_protect(&rel->dd); | |
| 
 | |
|     return rel; | |
| } | |
| 
 | |
| #define rel_load(f, rel) CALL(rel_load, f, rel) | |
| VOID_TASK_2(rel_load, FILE*, f, rel_t, rel) | |
| { | |
|     lddmc_serialize_fromfile(f); | |
|     size_t dd; | |
|     if (fread(&dd, sizeof(size_t), 1, f) != 1) Abort("Invalid input file!"); | |
|     rel->dd = lddmc_serialize_get_reversed(dd); | |
| } | |
| 
 | |
| /** | |
|  * Save a relation to file | |
|  */ | |
| static void | |
| rel_save_proj(FILE* f, rel_t rel) | |
| { | |
|     fwrite(&rel->r_k, sizeof(int), 1, f); | |
|     fwrite(&rel->w_k, sizeof(int), 1, f); | |
|     fwrite(rel->r_proj, sizeof(int), rel->r_k, f); | |
|     fwrite(rel->w_proj, sizeof(int), rel->w_k, f); | |
| } | |
| 
 | |
| static void | |
| rel_save(FILE* f, rel_t rel) | |
| { | |
|     size_t dd = lddmc_serialize_add(rel->dd); | |
|     lddmc_serialize_tofile(f); | |
|     fwrite(&dd, sizeof(size_t), 1, f); | |
| } | |
| 
 | |
| /** | |
|  * Clone a set | |
|  */ | |
| static set_t | |
| set_clone(set_t source) | |
| { | |
|     set_t set = (set_t)malloc(sizeof(struct set)); | |
|     set->dd = source->dd; | |
|     lddmc_protect(&set->dd); | |
|     return set; | |
| } | |
| 
 | |
| static char* | |
| to_h(double size, char *buf) | |
| { | |
|     const char* units[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; | |
|     int i = 0; | |
|     for (;size>1024;size/=1024) i++; | |
|     sprintf(buf, "%.*f %s", i, size, units[i]); | |
|     return buf; | |
| } | |
| 
 | |
| static void | |
| print_memory_usage(void) | |
| { | |
|     char buf[32]; | |
|     to_h(getCurrentRSS(), buf); | |
|     INFO("Memory usage: %s\n", buf); | |
| } | |
| 
 | |
| /** | |
|  * Get the first variable of the transition relation | |
|  */ | |
| static int | |
| get_first(MDD meta) | |
| { | |
|     uint32_t val = lddmc_getvalue(meta); | |
|     if (val != 0) return 0; | |
|     return 1+get_first(lddmc_follow(meta, val)); | |
| } | |
| 
 | |
| /** | |
|  * Print a single example of a set to stdout | |
|  */ | |
| static void | |
| print_example(MDD example) | |
| { | |
|     if (example != lddmc_false) { | |
|         LACE_ME; | |
|         uint32_t vec[vector_size]; | |
|         lddmc_sat_one(example, vec, vector_size); | |
| 
 | |
|         printf("["); | |
|         for (int i=0; i<vector_size; i++) { | |
|             if (i>0) printf(","); | |
|             printf("%" PRIu32, vec[i]); | |
|         } | |
|         printf("]"); | |
|     } | |
| } | |
| 
 | |
| static void | |
| print_matrix(size_t size, MDD meta) | |
| { | |
|     if (size == 0) return; | |
|     uint32_t val = lddmc_getvalue(meta); | |
|     if (val == 1) { | |
|         printf("+"); | |
|         print_matrix(size-1, lddmc_follow(lddmc_follow(meta, 1), 2)); | |
|     } else { | |
|         if (val == (uint32_t)-1) printf("-"); | |
|         else if (val == 0) printf("-"); | |
|         else if (val == 3) printf("r"); | |
|         else if (val == 4) printf("w"); | |
|         print_matrix(size-1, lddmc_follow(meta, val)); | |
|     } | |
| } | |
| 
 | |
| /** | |
|  * Implement parallel strategy (that performs the relprod operations in parallel) | |
|  */ | |
| TASK_5(MDD, go_par, MDD, cur, MDD, visited, size_t, from, size_t, len, MDD*, deadlocks) | |
| { | |
|     if (len == 1) { | |
|         // Calculate NEW successors (not in visited) | |
|         MDD succ = lddmc_relprod(cur, next[from]->dd, next[from]->meta); | |
|         lddmc_refs_push(succ); | |
|         if (deadlocks) { | |
|             // check which MDDs in deadlocks do not have a successor in this relation | |
|             MDD anc = lddmc_relprev(succ, next[from]->dd, next[from]->meta, cur); | |
|             lddmc_refs_push(anc); | |
|             *deadlocks = lddmc_minus(*deadlocks, anc); | |
|             lddmc_refs_pop(1); | |
|         } | |
|         MDD result = lddmc_minus(succ, visited); | |
|         lddmc_refs_pop(1); | |
|         return result; | |
|     } else if (deadlocks != NULL) { | |
|         MDD deadlocks_left = *deadlocks; | |
|         MDD deadlocks_right = *deadlocks; | |
|         lddmc_refs_pushptr(&deadlocks_left); | |
|         lddmc_refs_pushptr(&deadlocks_right); | |
| 
 | |
|         // Recursively compute left+right | |
|         lddmc_refs_spawn(SPAWN(go_par, cur, visited, from, len/2, &deadlocks_left)); | |
|         MDD right = CALL(go_par, cur, visited, from+len/2, len-len/2, &deadlocks_right); | |
|         lddmc_refs_push(right); | |
|         MDD left = lddmc_refs_sync(SYNC(go_par)); | |
|         lddmc_refs_push(left); | |
| 
 | |
|         // Merge results of left+right | |
|         MDD result = lddmc_union(left, right); | |
|         lddmc_refs_pop(2); | |
| 
 | |
|         // Intersect deadlock sets | |
|         lddmc_refs_push(result); | |
|         *deadlocks = lddmc_intersect(deadlocks_left, deadlocks_right); | |
|         lddmc_refs_pop(1); | |
|         lddmc_refs_popptr(2); | |
| 
 | |
|         // Return result | |
|         return result; | |
|     } else { | |
|         // Recursively compute left+right | |
|         lddmc_refs_spawn(SPAWN(go_par, cur, visited, from, len/2, NULL)); | |
|         MDD right = CALL(go_par, cur, visited, from+len/2, len-len/2, NULL); | |
|         lddmc_refs_push(right); | |
|         MDD left = lddmc_refs_sync(SYNC(go_par)); | |
|         lddmc_refs_push(left); | |
| 
 | |
|         // Merge results of left+right | |
|         MDD result = lddmc_union(left, right); | |
|         lddmc_refs_pop(2); | |
| 
 | |
|         // Return result | |
|         return result; | |
|     } | |
| } | |
| 
 | |
| /** | |
|  * Implementation of the PAR strategy | |
|  */ | |
| VOID_TASK_1(par, set_t, set) | |
| { | |
|     /* Prepare variables */ | |
|     MDD visited = set->dd; | |
|     MDD front = visited; | |
|     lddmc_refs_pushptr(&visited); | |
|     lddmc_refs_pushptr(&front); | |
| 
 | |
|     int iteration = 1; | |
|     do { | |
|         if (check_deadlocks) { | |
|             // compute successors in parallel | |
|             MDD deadlocks = front; | |
|             lddmc_refs_pushptr(&deadlocks); | |
|             front = CALL(go_par, front, visited, 0, next_count, &deadlocks); | |
|             lddmc_refs_popptr(1); | |
| 
 | |
|             if (deadlocks != lddmc_false) { | |
|                 INFO("Found %'0.0f deadlock states... ", lddmc_satcount_cached(deadlocks)); | |
|                 printf("example: "); | |
|                 print_example(deadlocks); | |
|                 printf("\n"); | |
|                 check_deadlocks = 0; | |
|             } | |
|         } else { | |
|             // compute successors in parallel | |
|             front = CALL(go_par, front, visited, 0, next_count, NULL); | |
|         } | |
| 
 | |
|         // visited = visited + front | |
|         visited = lddmc_union(visited, front); | |
| 
 | |
|         INFO("Level %d done", iteration); | |
|         if (report_levels) { | |
|             printf(", %'0.0f states explored", lddmc_satcount_cached(visited)); | |
|         } | |
|         if (report_table) { | |
|             size_t filled, total; | |
|             sylvan_table_usage(&filled, &total); | |
|             printf(", table: %0.1f%% full (%'zu nodes)", 100.0*(double)filled/total, filled); | |
|         } | |
|         char buf[32]; | |
|         to_h(getCurrentRSS(), buf); | |
|         printf(", rss=%s.\n", buf); | |
|         iteration++; | |
|     } while (front != lddmc_false); | |
| 
 | |
|     set->dd = visited; | |
|     lddmc_refs_popptr(2); | |
| } | |
| 
 | |
| /** | |
|  * Implement sequential strategy (that performs the relprod operations one by one) | |
|  */ | |
| TASK_5(MDD, go_bfs, MDD, cur, MDD, visited, size_t, from, size_t, len, MDD*, deadlocks) | |
| { | |
|     if (len == 1) { | |
|         // Calculate NEW successors (not in visited) | |
|         MDD succ = lddmc_relprod(cur, next[from]->dd, next[from]->meta); | |
|         lddmc_refs_push(succ); | |
|         if (deadlocks) { | |
|             // check which MDDs in deadlocks do not have a successor in this relation | |
|             MDD anc = lddmc_relprev(succ, next[from]->dd, next[from]->meta, cur); | |
|             lddmc_refs_push(anc); | |
|             *deadlocks = lddmc_minus(*deadlocks, anc); | |
|             lddmc_refs_pop(1); | |
|         } | |
|         MDD result = lddmc_minus(succ, visited); | |
|         lddmc_refs_pop(1); | |
|         return result; | |
|     } else if (deadlocks != NULL) { | |
|         MDD deadlocks_left = *deadlocks; | |
|         MDD deadlocks_right = *deadlocks; | |
|         lddmc_refs_pushptr(&deadlocks_left); | |
|         lddmc_refs_pushptr(&deadlocks_right); | |
| 
 | |
|         // Recursively compute left+right | |
|         MDD left = CALL(go_par, cur, visited, from, len/2, &deadlocks_left); | |
|         lddmc_refs_push(left); | |
|         MDD right = CALL(go_par, cur, visited, from+len/2, len-len/2, &deadlocks_right); | |
|         lddmc_refs_push(right); | |
| 
 | |
|         // Merge results of left+right | |
|         MDD result = lddmc_union(left, right); | |
|         lddmc_refs_pop(2); | |
| 
 | |
|         // Intersect deadlock sets | |
|         lddmc_refs_push(result); | |
|         *deadlocks = lddmc_intersect(deadlocks_left, deadlocks_right); | |
|         lddmc_refs_pop(1); | |
|         lddmc_refs_popptr(2); | |
| 
 | |
|         // Return result | |
|         return result; | |
|     } else { | |
|         // Recursively compute left+right | |
|         MDD left = CALL(go_par, cur, visited, from, len/2, NULL); | |
|         lddmc_refs_push(left); | |
|         MDD right = CALL(go_par, cur, visited, from+len/2, len-len/2, NULL); | |
|         lddmc_refs_push(right); | |
| 
 | |
|         // Merge results of left+right | |
|         MDD result = lddmc_union(left, right); | |
|         lddmc_refs_pop(2); | |
| 
 | |
|         // Return result | |
|         return result; | |
|     } | |
| } | |
| 
 | |
| /* BFS strategy, sequential strategy (but operations are parallelized by Sylvan) */ | |
| VOID_TASK_1(bfs, set_t, set) | |
| { | |
|     /* Prepare variables */ | |
|     MDD visited = set->dd; | |
|     MDD front = visited; | |
|     lddmc_refs_pushptr(&visited); | |
|     lddmc_refs_pushptr(&front); | |
| 
 | |
|     int iteration = 1; | |
|     do { | |
|         if (check_deadlocks) { | |
|             // compute successors | |
|             MDD deadlocks = front; | |
|             lddmc_refs_pushptr(&deadlocks); | |
|             front = CALL(go_bfs, front, visited, 0, next_count, &deadlocks); | |
|             lddmc_refs_popptr(1); | |
| 
 | |
|             if (deadlocks != lddmc_false) { | |
|                 INFO("Found %'0.0f deadlock states... ", lddmc_satcount_cached(deadlocks)); | |
|                 printf("example: "); | |
|                 print_example(deadlocks); | |
|                 printf("\n"); | |
|                 check_deadlocks = 0; | |
|             } | |
|         } else { | |
|             // compute successors | |
|             front = CALL(go_bfs, front, visited, 0, next_count, NULL); | |
|         } | |
| 
 | |
|         // visited = visited + front | |
|         visited = lddmc_union(visited, front); | |
| 
 | |
|         INFO("Level %d done", iteration); | |
|         if (report_levels) { | |
|             printf(", %'0.0f states explored", lddmc_satcount_cached(visited)); | |
|         } | |
|         if (report_table) { | |
|             size_t filled, total; | |
|             sylvan_table_usage(&filled, &total); | |
|             printf(", table: %0.1f%% full (%'zu nodes)", 100.0*(double)filled/total, filled); | |
|         } | |
|         char buf[32]; | |
|         to_h(getCurrentRSS(), buf); | |
|         printf(", rss=%s.\n", buf); | |
|         iteration++; | |
|     } while (front != lddmc_false); | |
| 
 | |
|     set->dd = visited; | |
|     lddmc_refs_popptr(2); | |
| } | |
| 
 | |
| /** | |
|  * Implementation of (parallel) saturation | |
|  * (assumes relations are ordered on first variable) | |
|  */ | |
| TASK_3(MDD, go_sat, MDD, set, int, idx, int, depth) | |
| { | |
|     /* Terminal cases */ | |
|     if (set == lddmc_false) return lddmc_false; | |
|     if (idx == next_count) return set; | |
| 
 | |
|     /* Consult the cache */ | |
|     MDD result; | |
|     const MDD _set = set; | |
|     if (cache_get3(201LL<<40, _set, idx, 0, &result)) return result; | |
|     lddmc_refs_pushptr(&_set); | |
| 
 | |
|     /** | |
|      * Possible improvement: cache more things (like intermediate results?) | |
|      *   and chain-apply more of the current level before going deeper? | |
|      */ | |
| 
 | |
|     /* Check if the relation should be applied */ | |
|     const int var = next[idx]->firstvar; | |
|     assert(depth <= var); | |
|     if (depth == var) { | |
|         /* Count the number of relations starting here */ | |
|         int n = 1; | |
|         while ((idx + n) < next_count && var == next[idx + n]->firstvar) n++; | |
|         /* | |
|          * Compute until fixpoint: | |
|          * - SAT deeper | |
|          * - chain-apply all current level once | |
|          */ | |
|         MDD prev = lddmc_false; | |
|         lddmc_refs_pushptr(&set); | |
|         lddmc_refs_pushptr(&prev); | |
|         while (prev != set) { | |
|             prev = set; | |
|             // SAT deeper | |
|             set = CALL(go_sat, set, idx + n, depth); | |
|             // chain-apply all current level once | |
|             for (int i=0; i<n; i++) { | |
|                 set = lddmc_relprod_union(set, next[idx+i]->dd, next[idx+i]->topmeta, set); | |
|             } | |
|         } | |
|         lddmc_refs_popptr(2); | |
|         result = set; | |
|     } else { | |
|         /* Recursive computation */ | |
|         lddmc_refs_spawn(SPAWN(go_sat, lddmc_getright(set), idx, depth)); | |
|         MDD down = lddmc_refs_push(CALL(go_sat, lddmc_getdown(set), idx, depth+1)); | |
|         MDD right = lddmc_refs_sync(SYNC(go_sat)); | |
|         lddmc_refs_pop(1); | |
|         result = lddmc_makenode(lddmc_getvalue(set), down, right); | |
|     } | |
| 
 | |
|     /* Store in cache */ | |
|     cache_put3(201LL<<40, _set, idx, 0, result); | |
|     lddmc_refs_popptr(1); | |
|     return result; | |
| } | |
| 
 | |
| /** | |
|  * Wrapper for the Saturation strategy | |
|  */ | |
| VOID_TASK_1(sat, set_t, set) | |
| { | |
|     set->dd = CALL(go_sat, set->dd, 0, 0); | |
| } | |
| 
 | |
| /** | |
|  * Implementation of the Chaining strategy (does not support deadlock detection) | |
|  */ | |
| VOID_TASK_1(chaining, set_t, set) | |
| { | |
|     MDD visited = set->dd; | |
|     MDD front = visited; | |
|     MDD succ = sylvan_false; | |
| 
 | |
|     lddmc_refs_pushptr(&visited); | |
|     lddmc_refs_pushptr(&front); | |
|     lddmc_refs_pushptr(&succ); | |
| 
 | |
|     int iteration = 1; | |
|     do { | |
|         // calculate successors in parallel | |
|         for (int i=0; i<next_count; i++) { | |
|             succ = lddmc_relprod(front, next[i]->dd, next[i]->meta); | |
|             front = lddmc_union(front, succ); | |
|             succ = lddmc_false; // reset, for gc | |
|         } | |
| 
 | |
|         // front = front - visited | |
|         // visited = visited + front | |
|         front = lddmc_minus(front, visited); | |
|         visited = lddmc_union(visited, front); | |
| 
 | |
|         INFO("Level %d done", iteration); | |
|         if (report_levels) { | |
|             printf(", %'0.0f states explored", lddmc_satcount_cached(visited)); | |
|         } | |
|         if (report_table) { | |
|             size_t filled, total; | |
|             sylvan_table_usage(&filled, &total); | |
|             printf(", table: %0.1f%% full (%'zu nodes)", 100.0*(double)filled/total, filled); | |
|         } | |
|         char buf[32]; | |
|         to_h(getCurrentRSS(), buf); | |
|         printf(", rss=%s.\n", buf); | |
|         iteration++; | |
|     } while (front != lddmc_false); | |
| 
 | |
|     set->dd = visited; | |
|     lddmc_refs_popptr(3); | |
| } | |
| 
 | |
| VOID_TASK_0(gc_start) | |
| { | |
|     char buf[32]; | |
|     to_h(getCurrentRSS(), buf); | |
|     INFO("(GC) Starting garbage collection... (rss: %s)\n", buf); | |
| } | |
| 
 | |
| VOID_TASK_0(gc_end) | |
| { | |
|     char buf[32]; | |
|     to_h(getCurrentRSS(), buf); | |
|     INFO("(GC) Garbage collection done.       (rss: %s)\n", buf); | |
| } | |
| 
 | |
| int | |
| main(int argc, char **argv) | |
| { | |
|     /** | |
|      * Parse command line, set locale, set startup time for INFO messages. | |
|      */ | |
|     argp_parse(&argp, argc, argv, 0, 0, 0); | |
|     setlocale(LC_NUMERIC, "en_US.utf-8"); | |
|     t_start = wctime(); | |
| 
 | |
|     /** | |
|      * Initialize Lace. | |
|      * | |
|      * First: setup with given number of workers (0 for autodetect) and some large size task queue. | |
|      * Second: start all worker threads with default settings. | |
|      * Third: setup local variables using the LACE_ME macro. | |
|      */ | |
|     lace_init(workers, 1000000); | |
|     lace_startup(0, NULL, NULL); | |
|     LACE_ME; | |
| 
 | |
|     /** | |
|      * Initialize Sylvan. | |
|      * | |
|      * First: set memory limits | |
|      * - 2 GB memory, nodes table twice as big as cache, initial size halved 6x | |
|      *   (that means it takes 6 garbage collections to get to the maximum nodes&cache size) | |
|      * Second: initialize package and subpackages | |
|      * Third: add hooks to report garbage collection | |
|      */ | |
|     sylvan_set_limits(2LL<<30, 1, 6); | |
|     sylvan_init_package(); | |
|     sylvan_init_ldd(); | |
|     sylvan_gc_hook_pregc(TASK(gc_start)); | |
|     sylvan_gc_hook_postgc(TASK(gc_end)); | |
| 
 | |
|     /** | |
|      * Read the model from file | |
|      */ | |
| 
 | |
|     FILE *f = fopen(model_filename, "r"); | |
|     if (f == NULL) { | |
|         Abort("Cannot open file '%s'!\n", model_filename); | |
|         return -1; | |
|     } | |
| 
 | |
|     /* Read domain data */ | |
|     if (fread(&vector_size, sizeof(int), 1, f) != 1) Abort("Invalid input file!\n"); | |
| 
 | |
|     /* Read initial state */ | |
|     set_t initial = set_load(f); | |
| 
 | |
|     /* Read number of transition relations */ | |
|     if (fread(&next_count, sizeof(int), 1, f) != 1) Abort("Invalid input file!\n"); | |
|     next = (rel_t*)malloc(sizeof(rel_t) * next_count); | |
| 
 | |
|     /* Read transition relations */ | |
|     for (int i=0; i<next_count; i++) next[i] = rel_load_proj(f); | |
|     for (int i=0; i<next_count; i++) rel_load(f, next[i]); | |
| 
 | |
|     /* We ignore the reachable states and action labels that are stored after the relations */ | |
| 
 | |
|     /* Close the file */ | |
|     fclose(f); | |
| 
 | |
|     /** | |
|      * Pre-processing and some statistics reporting | |
|      */ | |
| 
 | |
|     if (strategy == 2 || strategy == 3) { | |
|         // for SAT and CHAINING, sort the transition relations (gnome sort because I like gnomes) | |
|         int i = 1, j = 2; | |
|         rel_t t; | |
|         while (i < next_count) { | |
|             rel_t *p = &next[i], *q = p-1; | |
|             if ((*q)->firstvar > (*p)->firstvar) { | |
|                 t = *q; | |
|                 *q = *p; | |
|                 *p = t; | |
|                 if (--i) continue; | |
|             } | |
|             i = j++; | |
|         } | |
|     } | |
| 
 | |
|     INFO("Read file '%s'\n", model_filename); | |
|     INFO("%d integers per state, %d transition groups\n", vector_size, next_count); | |
| 
 | |
|     if (print_transition_matrix) { | |
|         for (int i=0; i<next_count; i++) { | |
|             INFO(""); | |
|             print_matrix(vector_size, next[i]->meta); | |
|             printf(" (%d)\n", get_first(next[i]->meta)); | |
|         } | |
|     } | |
| 
 | |
|     set_t states = set_clone(initial); | |
| 
 | |
| #ifdef HAVE_PROFILER | |
|     if (profile_filename != NULL) ProfilerStart(profile_filename); | |
| #endif | |
|  | |
|     if (strategy == 0) { | |
|         double t1 = wctime(); | |
|         CALL(bfs, states); | |
|         double t2 = wctime(); | |
|         INFO("BFS Time: %f\n", t2-t1); | |
|     } else if (strategy == 1) { | |
|         double t1 = wctime(); | |
|         CALL(par, states); | |
|         double t2 = wctime(); | |
|         INFO("PAR Time: %f\n", t2-t1); | |
|     } else if (strategy == 2) { | |
|         double t1 = wctime(); | |
|         CALL(sat, states); | |
|         double t2 = wctime(); | |
|         INFO("SAT Time: %f\n", t2-t1); | |
|     } else if (strategy == 3) { | |
|         double t1 = wctime(); | |
|         CALL(chaining, states); | |
|         double t2 = wctime(); | |
|         INFO("CHAINING Time: %f\n", t2-t1); | |
|     } else { | |
|         Abort("Invalid strategy set?!\n"); | |
|     } | |
| 
 | |
| #ifdef HAVE_PROFILER | |
|     if (profile_filename != NULL) ProfilerStop(); | |
| #endif | |
|  | |
|     // Now we just have states | |
|     INFO("Final states: %'0.0f states\n", lddmc_satcount_cached(states->dd)); | |
|     if (report_nodes) { | |
|         INFO("Final states: %'zu MDD nodes\n", lddmc_nodecount(states->dd)); | |
|     } | |
| 
 | |
|     if (out_filename != NULL) { | |
|         INFO("Writing to %s.\n", out_filename); | |
| 
 | |
|         // Create LDD file | |
|         FILE *f = fopen(out_filename, "w"); | |
|         lddmc_serialize_reset(); | |
| 
 | |
|         // Write domain... | |
|         fwrite(&vector_size, sizeof(int), 1, f); | |
| 
 | |
|         // Write initial state... | |
|         set_save(f, initial); | |
| 
 | |
|         // Write number of transitions | |
|         fwrite(&next_count, sizeof(int), 1, f); | |
| 
 | |
|         // Write transitions | |
|         for (int i=0; i<next_count; i++) rel_save_proj(f, next[i]); | |
|         for (int i=0; i<next_count; i++) rel_save(f, next[i]); | |
| 
 | |
|         // Write reachable states | |
|         int has_reachable = 1; | |
|         fwrite(&has_reachable, sizeof(int), 1, f); | |
|         set_save(f, states); | |
| 
 | |
|         // Write action labels | |
|         fclose(f); | |
|     } | |
| 
 | |
|     print_memory_usage(); | |
|     sylvan_stats_report(stdout); | |
| 
 | |
|     return 0; | |
| }
 |