1 /*
2  * This file is part of the Yices SMT Solver.
3  * Copyright (C) 2017 SRI International.
4  *
5  * Yices is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * Yices is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with Yices.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /*
20  * PARSE A QF_BV BENCHMARK IN SMT-LIB FORMAT THEN CONVERT IT TO CNF
21  */
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <stdbool.h>
26 #include <inttypes.h>
27 
28 #include "api/smt_logic_codes.h"
29 #include "api/yices_globals.h"
30 #include "context/context.h"
31 #include "frontend/smt1/smt_lexer.h"
32 #include "frontend/smt1/smt_parser.h"
33 #include "frontend/smt1/smt_term_stack.h"
34 #include "solvers/bv/bvsolver.h"
35 #include "solvers/bv/dimacs_printer.h"
36 #include "utils/command_line.h"
37 #include "utils/cputime.h"
38 #include "utils/memsize.h"
39 #include "yices.h"
40 #include "yices_exit_codes.h"
41 
42 
43 static lexer_t lexer;
44 static parser_t parser;
45 static tstack_t stack;
46 static smt_benchmark_t bench;
47 static context_t context;
48 
49 
50 /*
51  * Parameters:
52  * - filename = name of the input file (in SMT format)
53  *   if filenemae is NULL, we read stdin
54  * - out_file = name of the output file
55  *   if out_file is NULL, we use 'yices2bblast.cnf' as default.
56  */
57 static char *filename;
58 static char *out_file;
59 
60 
61 /*
62  * Command-line options
63  */
64 enum {
65   out_option,
66   help_flag,
67 };
68 
69 #define NUM_OPTIONS 2
70 
71 static option_desc_t options[NUM_OPTIONS] = {
72   { "out",  'o', MANDATORY_STRING, out_option },
73   { "help", 'h', FLAG_OPTION, help_flag },
74 };
75 
print_help(char * progname)76 static void print_help(char *progname) {
77   printf("Usage: %s [options] filename\n\n", progname);
78   printf("Options:\n"
79 	 "  --help, -h                   Display this information\n"
80 	 "  --out=<file> or -o <file>    Set the output file (default = 'yices2bblast.cnf')\n"
81 	 "\n");
82   fflush(stdout);
83 }
84 
print_usage(char * progname)85 static void print_usage(char *progname) {
86   fprintf(stderr, "Try %s --help for more information\n", progname);
87 }
88 
89 
90 /*
91  * Parse the command line:
92  * - set filename, dump, and dump_file
93  */
process_command_line(int argc,char * argv[])94 static void process_command_line(int argc, char *argv[]) {
95   cmdline_parser_t parser;
96   cmdline_elem_t elem;
97 
98   // default options
99   filename = NULL;
100   out_file = NULL;
101 
102   init_cmdline_parser(&parser, options, NUM_OPTIONS, argv, argc);
103   for (;;) {
104     cmdline_parse_element(&parser, &elem);
105     switch (elem.status) {
106     case cmdline_done:
107       goto done;
108 
109     case cmdline_argument:
110       if (filename == NULL) {
111 	filename = elem.arg;
112       } else {
113 	fprintf(stderr, "%s: can't have several input files\n", parser.command_name);
114 	goto bad_usage;
115       }
116       break;
117 
118     case cmdline_option:
119       switch (elem.key) {
120       case out_option:
121 	if (out_file == NULL) {
122 	  out_file = elem.s_value;
123 	} else {
124 	  fprintf(stderr, "%s: can't have several output files\n", parser.command_name);
125 	  goto bad_usage;
126 	}
127 	break;
128 
129       case help_flag:
130 	print_help(parser.command_name);
131 	goto quick_exit;
132 
133       default:
134 	assert(false);
135 	break;
136       }
137       break;
138 
139     case cmdline_error:
140       cmdline_print_error(&parser, &elem);
141       goto bad_usage;
142     }
143   }
144 
145  done:
146   // check that dump_file and filename are different
147   if (filename != NULL && out_file != NULL && strcmp(filename, out_file) == 0) {
148     fprintf(stderr, "%s: can't use '%s' for both input and dump file\n", parser.command_name, out_file);
149     goto bad_usage;
150   }
151 
152   return;
153 
154  quick_exit:
155   exit(YICES_EXIT_SUCCESS);
156 
157  bad_usage:
158   print_usage(parser.command_name);
159   exit(YICES_EXIT_USAGE);
160 }
161 
162 
163 /*
164  * Conversion of internalization code to an error message
165  */
166 static const char * const code2error[NUM_INTERNALIZATION_ERRORS] = {
167   "no error",
168   "internal error",
169   "type error",
170   "formula contains free variables",
171   "logic not supported",
172   "context does not support UF",
173   "context does not support scalar types",
174   "context does not support tuples",
175   "context does not support uninterpreted types",
176   "context does not support arithmetic",
177   "context does not support bitvectors",
178   "context does not support function equalities",
179   "context does not support quantifiers",
180   "context does not support lambdas",
181   "not an IDL formula",
182   "not an RDL formula",
183   "non-linear arithmetic not supported",
184   "too many variables for the arithmetic solver",
185   "too many atoms for the arithmetic solver",
186   "arithmetic solver exception",
187   "bitvector solver exception",
188 };
189 
190 
191 /*
192  * Print the translation code returned by assert_formulas
193  */
print_internalization_code(int32_t code)194 static void print_internalization_code(int32_t code) {
195   assert(-NUM_INTERNALIZATION_ERRORS < code && code <= TRIVIALLY_UNSAT);
196   if (code == TRIVIALLY_UNSAT) {
197     printf("Internalization OK\n");
198     printf("Assertions simplify to false\n\n");
199     printf("unsat\n");
200   } else if (code == CTX_NO_ERROR) {
201     printf("Internalization OK\n\n");
202     printf("unknown\n");
203   } else {
204     assert(code < 0);
205     code = - code;
206     printf("Internalization error: %s\n\n", code2error[code]);
207     printf("unknown\n");
208   }
209 
210   fflush(stdout);
211 }
212 
213 
214 
215 /*
216  * Temporary test. Check whether one of the input assertion is reduced
217  * to false by simplification. This is checked independent of the
218  * logic label.
219  */
benchmark_reduced_to_false(smt_benchmark_t * bench)220 static bool benchmark_reduced_to_false(smt_benchmark_t *bench) {
221   uint32_t i, n;
222   term_t f;
223 
224   n = bench->nformulas;
225   for (i=0; i<n; i++) {
226     f = bench->formulas[i];
227     assert(is_boolean_term(__yices_globals.terms, f));
228     if (f == false_term) {
229       return true;
230     }
231   }
232 
233   return false;
234 }
235 
236 
237 /*
238  * Temporary test. Check whether the assertions are trivially true
239  * after internalization and variable elimination (i.e., vectors
240  * top_eqs, top_formulas, top_atoms, top_interns are all empty).
241  */
context_is_empty(context_t * ctx)242 static bool context_is_empty(context_t *ctx) {
243   return ctx->top_eqs.size == 0 && ctx->top_atoms.size == 0 &&
244     ctx->top_formulas.size == 0 && ctx->top_interns.size == 0;
245 }
246 
247 
248 #if 0
249 
250 // NOT USED ANYMORE
251 /*
252  * Export the clauses of core into the given file:
253  * - use the DIMACs CNF format
254  * - don't export the learned clauses
255  * - return 0 if this works
256  * - return -1 if the file can't be opened
257  */
258 static int32_t smt_core_export(smt_core_t *core, const char *filename) {
259   FILE *f;
260 
261   f = fopen(filename, "w");
262   if (f == NULL) return -1;
263   dimacs_print_core(f, core);
264   fclose(f);
265 
266   return 0;
267 }
268 
269 #endif
270 
271 /*
272  * Export the context
273  */
export_context(context_t * ctx,const char * filename)274 static int32_t export_context(context_t *ctx, const char *filename) {
275   FILE *f;
276 
277   f = fopen(filename, "w");
278   if (f == NULL) return -1;
279   dimacs_print_bvcontext(f, ctx);
280   fclose(f);
281 
282   return 0;
283 }
284 
285 
286 /*
287  * Test bitblasting
288  */
test_blaster(smt_benchmark_t * bench)289 static void test_blaster(smt_benchmark_t *bench) {
290   int32_t code;
291   smt_logic_t logic;
292 
293   /*
294    * Check the benchmark logic
295    */
296   if (bench->logic_name == NULL) {
297     printf("No logic specified\n\nunknown\n");
298     return;
299   }
300 
301   logic = smt_logic_code(bench->logic_name);
302   assert(AUFLIA <= logic && logic <= SMT_UNKNOWN);
303   if (logic != QF_BV && logic != QF_UFBV) {
304     printf("Input is not a bit-vector problem (logic= %s)\n", bench->logic_name);
305     return;
306   }
307 
308   init_context(&context, __yices_globals.terms, logic, CTX_MODE_ONECHECK, CTX_ARCH_BV, false);
309   enable_lax_mode(&context); // FOR TESTING
310   enable_variable_elimination(&context);
311   enable_eq_abstraction(&context);
312   enable_bvarith_elimination(&context);
313 
314   code = assert_formulas(&context, bench->nformulas, bench->formulas);
315   if (code != CTX_NO_ERROR) {
316     print_internalization_code(code);
317   } else if (context_is_empty(&context)) {
318     printf("Reduced to the empty context\n\nsat\n");
319   } else {
320     assert(context_has_bv_solver(&context));
321     // test bit-blasting
322     if (bv_solver_bitblast(context.bv_solver)) {
323       printf("Bitblasting OK\n");
324       fflush(stdout);
325       print_internalization_code(code);
326       fflush(stdout);
327       // one round of smt_process to handle the lemmas
328       smt_process(context.core);
329 
330       if (out_file == NULL) {
331 	out_file = "yices2bblast.cnf";
332       }
333       if (export_context(&context, out_file) == 0) {
334 	printf("Exported to %s\n", out_file);
335       } else {
336 	perror(out_file);
337       }
338 
339     } else {
340       printf("Bitbasting detected an inconsistency\n\nunsat\n");
341     }
342 
343   }
344 
345   delete_context(&context);
346 }
347 
348 
349 /*
350  * Test: parse and internalize an SMT benchmark
351  */
main(int argc,char * argv[])352 int main(int argc, char *argv[]) {
353   int32_t code;
354   double time, mem_used;
355 
356   process_command_line(argc, argv);
357 
358   if (filename != NULL) {
359     // read from file
360     if (init_smt_file_lexer(&lexer, filename) < 0) {
361       perror(filename);
362       exit(YICES_EXIT_FILE_NOT_FOUND);
363     }
364   } else {
365     // read from stdin
366     init_smt_stdin_lexer(&lexer);
367   }
368 
369   yices_init();
370   init_smt_tstack(&stack);
371   init_parser(&parser, &lexer, &stack);
372   init_benchmark(&bench);
373   code = parse_smt_benchmark(&parser, &bench);
374   if (code == 0) {
375     printf("No syntax error found\n");
376     printf("term table: %"PRIu32" elements\n", __yices_globals.terms->nelems);
377     fflush(stdout);
378   } else {
379     exit(YICES_EXIT_SYNTAX_ERROR);
380   }
381 
382   if (benchmark_reduced_to_false(&bench)) {
383     printf("Reduced to false\n\nunsat\n");
384   } else {
385     test_blaster(&bench);
386   }
387   fflush(stdout);
388 
389   time = get_cpu_time();
390   mem_used = mem_size() / (1024 * 1024);
391   printf("\nConstruction time: %.4f s\n", time);
392   printf("Memory used: %.2f MB\n\n", mem_used);
393   fflush(stdout);
394 
395   delete_benchmark(&bench);
396   delete_parser(&parser);
397   close_lexer(&lexer);
398   delete_tstack(&stack);
399   yices_exit();
400 
401   return YICES_EXIT_SUCCESS;
402 }
403 
404