/* Copyright (c) 2006 - 2008, Armin Biere, Johannes Kepler University. */ /* Copyright (c) 2009, Thomas Hribernig, Johannes Kepler University. */ #define USAGE \ "usage: mtcnfdd [-h|-t|-T |-s |-mmw |-mmr ] src dst cmd [ ...]\n" \ "\n" \ " -h print this command line option summary\n" \ " -t thorough mode, e.g. iterate same widths multiple times\n" \ " -m mask out signals when calculating exit code\n" \ " -e set expected exit code to \n" \ " -T \n" \ " -s \n" \ " -mmw \n" \ " -mmr \n" \ "\n" \ " src file name of an existing CNF in DIMACS format\n" \ " dst file name of generated minimized CNF\n" \ " cmd command to debug (expects a CNF file as argument)\n" \ "\n" \ "The delta debugger copies 'src' to 'dst' and tries to remove\n" \ "as many clauses and literals without changing the exit code\n" \ "of 'cmd dst'. Then unused variables are removed, as long the\n" \ "exit code does not change.\n" #include #include #include #include #include #include #include #include #include #include #include //#define HELGRINDSAFE #define TRUE INT_MAX #define FALSE -INT_MAX typedef struct WorkerInfo { pthread_t thread; int id; int position; int start; int end; int ** test; int removed; int width; char tmp[100]; } WorkerInfo; #define VERSION_NUMBER 2 static const char * src; static const char * dst; static char * cmd; static int ** headclauses; static int size_clauses; static int maxidx; static int * movedto; static int expected; static char tmp[100]; static int round; static int changed; static volatile int calls; static int thorough; static int masksignals; static int threads; static int start_reduce_width; static int min_merge_width; static int max_merge_rounds; static int merge_succeeded; static int shrink_succeeded; static int merge_failed; static int shrink_failed; static WorkerInfo* workerinfo; static pthread_mutex_t stdout_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t headclauses_mutex = PTHREAD_MUTEX_INITIALIZER; static void die (const char * fmt, ...) { va_list ap; fputs ("*** mtcnfdd: ", stderr); va_start (ap, fmt); vfprintf (stderr, fmt, ap); va_end (ap); fputc ('\n', stderr); fflush (stderr); if (tmp[0] == '/') unlink (tmp); exit (1); } static void msg (const char * fmt, ...) { va_list ap; fputs ("[mtcnfdd] ", stderr); va_start (ap, fmt); vfprintf (stderr, fmt, ap); va_end (ap); fputc ('\n', stderr); fflush (stderr); } static void parse (void) { int i, ch, * clause, lit, sign, size_clause, count_clause, count_clauses; FILE * file; int zipped; if (strlen (src) > 3 && !strcmp (src + strlen (src) - 3, ".gz")) { const char * gunzip = "gunzip -c %s 2>/dev/null"; char * cmd = malloc (strlen (src) + strlen (gunzip)); sprintf (cmd, gunzip, src); file = popen (cmd, "r"); free (cmd); zipped = 1; } else { file = fopen (src, "r"); zipped = 0; } if (!file) die ("can not read from '%s'", src); SKIP: ch = getc (file); if (isspace (ch)) goto SKIP; if (ch == 'c') { while ((ch = getc (file)) != '\n' && ch != EOF) ; goto SKIP; } if (ch != 'p' || fscanf (file, " cnf %d %d", &maxidx, &size_clauses) != 2) die ("expected 'p cnf ...' header"); movedto = malloc ((maxidx + 1) * sizeof (movedto[0])); for (i = 1; i <= maxidx; i++) movedto[i] = i; headclauses = malloc (size_clauses * sizeof (headclauses[0])); clause = 0; size_clause = count_clause = count_clauses = 0; NEXT: ch = getc (file); if (isspace (ch)) goto NEXT; if (ch == 'c') { while ((ch = getc (file)) != '\n' && ch != EOF) ; goto NEXT; } if (ch == '-') { sign = -1; ch = getc (file); if (ch == EOF) die ("EOF after '-'"); } else sign = 1; if (ch != EOF && !isdigit (ch)) die ("invalid character %02x", ch); if (isdigit (ch)) { lit = ch - '0'; while (isdigit (ch = getc (file))) lit = 10 * lit + (ch - '0'); lit *= sign; if (count_clause == size_clause) { size_clause = size_clause ? 2 * size_clause : 8; clause = realloc (clause, size_clause * sizeof (clause[0])); } clause[count_clause++] = lit; if (!lit) { if (count_clauses == size_clauses) die ("too many clauses"); headclauses[count_clauses++] = clause; count_clause = size_clause = 0; clause = 0; } goto NEXT; } assert (ch == EOF); if (count_clause) die ("missing '0' after clause"); if (count_clauses < size_clauses) die ("%d clauses missing", size_clauses - count_clauses); assert (!clause); if (zipped) pclose (file); else fclose (file); msg ("parsed %d variables", maxidx); msg ("parsed %d clauses", size_clauses); } static int maskstatus (int status) { int res = status; if (masksignals) res = WEXITSTATUS (res); return res; } static int run (const char * name) { char * buffer = malloc (strlen (cmd) + strlen (name) + 100); int res; __sync_fetch_and_add(&calls, 1); /* TODO if this command produces a lot of output, e.g. a solution * to a SAT problem, then flushing the associated output buffers of * the process generated by 'system' seems to take quiet some time. * It is probably better to directly use 'exec' and redirect output * in such a way that it does not have to go through the pipe. Of course * users can avoid this effect by not letting the command produce much * output through adding appropriate command line options. */ sprintf (buffer, "exec %s %s 1>/dev/null 2>/dev/null", cmd, name); res = maskstatus (system (buffer)); free (buffer); return res; } static int deref (int lit) { int idx, res; if (!lit) return 0; idx = abs (lit); if (idx == INT_MAX) return lit; idx = movedto[idx]; res = (lit < 0) ? -idx : idx; return res; } static int clausesatisfied (int ** clauses, int i) { int j, lit; if (!clauses[i]) return 1; j = 0; while ((lit = clauses[i][j++])) if (deref (lit) == TRUE) return 1; return 0; } static int keptvariables (int ** clauses) { int i, j, idx, lit, res; res = 0; for (i = 0; i < size_clauses; i++) { if (clausesatisfied (clauses,i)) continue; j = 0; while ((lit = deref (clauses[i][j++]))) { if (lit == FALSE) continue; assert (lit != TRUE); idx = abs (lit); if (idx > res) res = idx; } } return res; } static int keptclauses (int ** clauses) { int i, res; res = 0; for (i = 0; i < size_clauses; i++) if (!clausesatisfied (clauses,i)) res++; return res; } static void print (int ** clauses, const char * name) { FILE * file = fopen (name, "w"); int i, j, lit; if (!file) die ("can not write to '%s'", name); fprintf (file, "p cnf %d %d\n", keptvariables (clauses), keptclauses (clauses)); for (i = 0; i < size_clauses; i++) { if (clausesatisfied (clauses,i)) continue; j = 0; while ((lit = deref (clauses[i][j++]))) { if (lit == FALSE) continue; fprintf (file, "%d ", lit); } fputs ("0\n", file); } fclose (file); } int runs_expected(int** clauses, const char* tmp_file) { print (clauses,tmp_file); return run (tmp_file) == expected; } #define ASSERT_RUNS_EXPECTED(clauses, tmp_file) \ {\ print (clauses,tmp_file);\ int run_result = run (tmp_file);\ if(run_result != expected)\ {\ fprintf(stderr,"Result was %d, but expected %d\n",run_result,expected);\ assert(0);\ }\ } static void setup (int compute_expected) { msg ("copied '%s' to '%s'", src, dst); print (headclauses, dst); if (compute_expected) expected = run (dst); msg ("expected exit code %s masking out signals is %d", masksignals ? "after" : "without", expected); sprintf (tmp, "/tmp/mtcnfdd-%u", (unsigned) getpid ()); workerinfo = (WorkerInfo*) calloc(threads,sizeof(WorkerInfo)); int i; for(i=0; iwidth, removed, sum, size_clauses); fflush (stderr); pthread_mutex_unlock(&stdout_mutex); } /** * Tries to merge the 'failed_test' with the 'headclauses' starting with clauses * from thread 'baseid' in steps of 'width' */ int try_merge(int** failed_test, int baseid, int max_remove) { int i; int removes = workerinfo[baseid].removed; int* lastremoved = malloc(max_remove*sizeof(int)); /* copy result from thread 'baseid' */ int** test = malloc(size_clauses * sizeof (int*)); memcpy(test,headclauses,size_clauses * sizeof (int*)); for(i=workerinfo[baseid].start; i=size_clauses) break; } if(test[i]!=failed_test[i]) { assert(failed_test[i]==NULL); lastremoved[actual_removes] = i; actual_removes++; } } max_remove = actual_removes; int width = max_remove/2; //printf("Starting with width %d\n",width); int merge_round = 0; /* try removing clauses */ while (width>min_merge_width && merge_round!=max_merge_rounds) { int start = 0; int end; assert(max_merge_rounds==-1 || merge_roundmax_remove) end = max_remove; fprintf (stderr,"[mtcnfdd] merge(%d,%d) width %d removed %d completed %d/%d \r", round,merge_round,width, removes,start,max_remove); fflush (stderr); actual_removes = 0; for(i=start; i 1) width = (width+1)/2; else width = 0; merge_round++; } free(lastremoved); free(test); return removes; } /* * Tries to reduce clauses between 'start' and 'end' (parallel) */ void* reduce_worker (void* vp) { WorkerInfo* info = (WorkerInfo*) vp; int j; info->position = info->start; int ** test = info->test; do { if (isatty (2)) { print_reduce_message(); } int end = info->position + info->width; if (end > info->end) end = info->end; int found = 0; for (j = info->position; j < end; j++) { if (test[j]) { found++; test[j] = NULL; } } if (found) { if (runs_expected(test,info->tmp)) { info->removed += found; } else { for (j = info->position; j < end; j++) { if (test[j]!=headclauses[j]) { found--; test[j] = headclauses[j]; } } assert(found==0); } } info->position = end; } while (info->position < info->end); return NULL; } /* * Starts worker to reduce clauses and then tries to merge the reduced clauses */ static void reduce (void) { int bytes = size_clauses * sizeof (int*); int i, j, width, removed, total; if(start_reduce_width==-1) width = size_clauses; else { width = start_reduce_width; start_reduce_width = -1; } total = 0; int** test = malloc(bytes); /* prepare workers */ for(i=0; i0); /* start worker threads */ int threadWidth = size_clauses/maxThreads; for(i=0; iwidth); fflush (stderr); removed = workerinfo[0].removed; int max_removed = workerinfo[0].removed; int max_removed_id = 0; for(i=1; imax_removed) { max_removed = workerinfo[i].removed; max_removed_id = i; } } } if(removed) { if (runs_expected(test,tmp)) { /* all threads results could be merged */ for(i=0; i 1) width = (width+1)/2; else width = 0; /* reduce 'size_clauses' by moving clauses */ j = 0; for (i = 0; i < size_clauses; i++) if (headclauses[i]) headclauses[j++] = headclauses[i]; size_clauses = j; } for(i=0; itest; int* original_clause; for (info->position = info->start; info->position < info->end; info->position++) { if (!test[info->position]) continue; original_clause = test[info->position]; /* save variables */ for (j = 0; (lit = original_clause[j]); j++) { tmpClause[j] = original_clause[j]; } tmpClause[j] = original_clause[j]; assert(!tmpClause[j]); int removed = 0; test[info->position] = tmpClause; /* test local remove */ for (j = 0; (lit = tmpClause[j]); j++) { if (lit == FALSE) continue; tmpClause[j] = FALSE; if (runs_expected(test,info->tmp)) removed++; else tmpClause[j] = lit; } /* synchronize */ if(removed) { pthread_mutex_lock(&headclauses_mutex); for (j = 0; (lit = tmpClause[j]); j++) { if(original_clause[j] != lit) { tmpClause[j] = original_clause[j]; original_clause[j] = lit; } } if (runs_expected(headclauses,info->tmp)) { info->removed+=removed; shrink_succeeded++; } else { for (j = 0; (lit = tmpClause[j]); j++) { if(original_clause[j] != lit) { original_clause[j] = lit; } } shrink_failed++; } pthread_mutex_unlock(&headclauses_mutex); } test[info->position] = original_clause; print_shrink_message(); } free(tmpClause); return NULL; } /** * Starts workers to remove variables from clauses */ static void shrink (void) { int i, removed; int bytes = size_clauses * sizeof (int*); if(!size_clauses) return; /* prepare workers*/ for(i=0; i0); int threadWidth = size_clauses/maxThreads; for(i=0; i movedtomaxidx) movedtomaxidx = movedto[i]; count++; } moved = movedtomaxidx - count; if (count && moved) { saved = malloc ((maxidx + 1) * sizeof (saved[0])); for (i = 1; i <= maxidx; i++) saved[i] = movedto[i]; j = 0; for (i = 1; i <= maxidx; i++) if (used[i]) movedto[i] = ++j; assert (j == count); if (!runs_expected(headclauses, tmp)) { moved = 0; for (i = 1; i <= maxidx; i++) movedto[i] = saved[i]; } else assert (run (dst) == expected); free (saved); } free (used); if (moved) { msg ("removed %d variables", moved); save (); } } static void reset (void) { int i; for (i = 0; i < size_clauses; i++) free (headclauses[i]); free (headclauses); free (movedto); free (cmd); for(i=0; i