2 * Copyright (C) 2025 Mikulas Patocka
4 * This file is part of Ajla.
6 * Ajla is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
11 * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * Ajla. If not, see <https://www.gnu.org/licenses/>.
19 private unit compiler.optimize.verify;
21 uses compiler.optimize.defs;
23 fn verify_function(ctx : context) : unit_type;
30 uses compiler.common.blob;
32 fn allocate_variable(implicit z3w : z3_world, implicit z3ctx : z3_context, ctx : context, v : int) : (z3_world, context, bytes, bytes);
34 fn get_z3_name(ctx : context, v : int) : (bytes, bytes)
36 var n := "var_" + map(ntos_base(v, 16), ascii_locase);
37 var m := "valid_" + map(ntos_base(v, 16), ascii_locase);
38 if len_greater_than(ctx.variables[v].name, 0) then [
39 n += "_" + ctx.variables[v].name;
40 m += "_" + ctx.variables[v].name;
45 fn get_z3_bb_name(ctx : context, bgi : int) : (context, bytes)
47 var str := "bb_" + map(ntos_base(bgi, 16), ascii_locase);
48 ctx.blocks[bgi].verifier_name := str;
52 fn get_z3_type(ctx : context, v : int) : bytes
54 if ctx.variables[v].type_index = T_AlwaysFlatOption then
56 if ctx.variables[v].type_index <= T_Integer, ctx.variables[v].type_index >= T_Integer128 then
58 if ctx.variables[v].type_index <= T_Real16, ctx.variables[v].type_index >= T_Real128 then
63 fn gen_assert(implicit z3w : z3_world, implicit z3ctx : z3_context, ctx : context, v : int, deps : list(bytes), ops : bytes) : z3_world
65 var str := "(assert (= " + ctx.variables[v].verifier_valid + " (and";
66 for i := 0 to len(deps) do
68 str += " (= " + ctx.variables[v].verifier_name + " " + ops + "))))";
69 z3_eval_smtlib2_string_noret(str);
72 fn assert_instruction(implicit z3w : z3_world, implicit z3ctx : z3_context, ctx : context, v : int) : (z3_world, context)
74 var ins := ctx.instrs[ctx.variables[v].defining_instr];
75 if ins.opcode = P_BinaryOp or ins.opcode = P_BinaryConstOp then [
76 var t := ctx.variables[ins.params[3]].type_index;
77 var is_bool := t = T_AlwaysFlatOption;
78 var is_int := t <= T_Integer and t >= T_Integer128;
79 var is_real := t <= T_Real16 and t >= T_Real128;
81 var op := ins.params[0];
82 if op = Bin_Add then op_z3 := "+";
83 else if op = Bin_Subtract then op_z3 := "-";
84 else if op = Bin_Multiply then op_z3 := "*";
85 else if op = Bin_Divide_Int then op_z3 := "div";
86 else if op = Bin_Divide_Real then op_z3 := "/";
87 else if op = Bin_Modulo then op_z3 := "rem";
88 else if op = Bin_And, is_bool then op_z3 := "and";
89 else if op = Bin_Or, is_bool then op_z3 := "or";
90 else if op = Bin_Xor, is_bool then op_z3 := "xor";
91 else if op = Bin_Equal then op_z3 := "=";
92 else if op = Bin_NotEqual then op_z3 := "distinct";
93 else if op = Bin_Less, is_int or is_real then op_z3 := "<";
94 else if op = Bin_LessEqual, is_int or is_real then op_z3 := "<=";
95 else if op = Bin_Greater, is_int or is_real then op_z3 := ">";
96 else if op = Bin_GreaterEqual, is_int or is_real then op_z3 := ">=";
97 else if op = Bin_LessEqual, is_bool then op_z3 := "=>";
99 var var1 var1v var2 var2v : bytes;
100 //eval debug("P_BinaryOp: " + ntos(ins.params[0]) + ", " + ntos(ins.params[1]) + ", " + ntos(ins.params[2]) + ", " + ntos(ins.params[3]) + ", " + ntos(ins.params[4]) + ", " + ntos(ins.params[5]));
101 ctx, var1, var1v := allocate_variable(ctx, ins.params[3]);
102 if len(var1) = 0 then
104 if ins.opcode = P_BinaryOp then [
105 ctx, var2, var2v := allocate_variable(ctx, ins.params[5]);
106 if len(var2) = 0 then
108 gen_assert(ctx, v, [var1v, var2v], "(" + op_z3 + " " + var1 + " " + var2 + ")");
111 var2 := select(ins.params[4] <> 0, "false", "true");
113 var2 := ntos(ins.params[4]);
114 gen_assert(ctx, v, [var1v], "(" + op_z3 + " " + var1 + " " + var2 + ")");
118 if ins.opcode = P_UnaryOp then [
119 var t := ctx.variables[v].type_index;
120 var is_bool := t = T_AlwaysFlatOption;
121 var is_int := t <= T_Integer and t >= T_Integer128;
122 var is_real := t <= T_Real16 and t >= T_Real128;
124 var op := ins.params[0];
125 if op = Un_Not, is_bool then op_z3 := "not";
126 else if op = Un_Neg, is_int or is_real then op_z3 := "-";
128 var var1 var1v : bytes;
129 ctx, var1, var1v := allocate_variable(ctx, ins.params[3]);
130 if len(var1) = 0 then
132 gen_assert(ctx, v, [var1v], "(" + op_z3 + " " + var1 + ")");
135 if ins.opcode = P_Copy then [
136 var var1 var1v : bytes;
137 ctx, var1, var1v := allocate_variable(ctx, ins.params[2]);
138 if len(var1) = 0 then
140 gen_assert(ctx, v, [var1v], var1);
143 if ins.opcode = P_Load_Const then [
145 var l := blob_to_int(ins.params[1 ..]);
146 var t := ctx.variables[v].type_index;
147 if t = T_AlwaysFlatOption then
148 cnst := select(l <> 0, "false", "true");
149 else if t <= T_Integer and t >= T_Integer128 then
153 gen_assert(ctx, v, empty(bytes), cnst);
156 if ins.opcode = P_Return_Vars then [
158 for i := 0 to len(ins.params) do [
159 if ins.params[i] = v then [
160 new_v := ctx.return_ins.params[1 + 2 * i];
164 abort internal("P_Return_Vars parameter not found");
166 var var1 var1v : bytes;
167 ctx, var1, var1v := allocate_variable(ctx, new_v);
168 if len(var1) = 0 then
170 gen_assert(ctx, v, [var1v], var1);
173 //eval debug("opcode: " + ntos(ins.opcode) + " (" + ctx.variables[v].verifier_name + ")");
177 fn allocate_variable(implicit z3w : z3_world, implicit z3ctx : z3_context, ctx : context, v : int) : (z3_world, context, bytes, bytes)
179 if not len_greater_than(ctx.variables[v].verifier_name, 0) then [
180 var t := get_z3_type(ctx, v);
181 if not len_greater_than(t, 0) then
183 var n, m := get_z3_name(ctx, v);
184 ctx.variables[v].verifier_name := n;
185 ctx.variables[v].verifier_valid := m;
186 z3_eval_smtlib2_string_noret("(declare-const " + n + " " + t + ")");
187 z3_eval_smtlib2_string_noret("(declare-const " + m + " Bool)");
188 ctx := assert_instruction(ctx, v);
190 return ctx, ctx.variables[v].verifier_name, ctx.variables[v].verifier_valid;
193 fn verify_function(ctx : context) : unit_type
196 implicit var z3w := z3_mk_world;
197 implicit var z3ctx := z3_mk_context();
198 //eval debug("verify function " + ctx.name);
200 for bgi := 0 to len(ctx.blocks) do [
201 if not ctx.blocks[bgi].active then
203 for ili := 0 to len(ctx.blocks[bgi].instrs) do [
204 var igi := ctx.blocks[bgi].instrs[ili];
205 var ins := ctx.instrs[igi];
206 if ins.opcode = P_Return then [
207 ctx.return_ins := ins;
211 ctx, str := get_z3_bb_name(ctx, bgi);
212 z3_eval_smtlib2_string_noret("(declare-const " + str + " Bool)");
215 for bgi := 0 to len(ctx.blocks) do [
216 if not ctx.blocks[bgi].active then
221 for i := 0 to len(ctx.blocks[bgi].pred_list) do [
222 var p := ctx.blocks[bgi].pred_list[i];
223 if len_greater_than(ctx.blocks[p].instrs, 0) then [
224 var ins := ctx.instrs[ctx.blocks[p].instrs[len(ctx.blocks[p].instrs) - 1]];
225 if ins.opcode = P_Jmp_False then [
226 var pos := ctx.blocks[bgi].pred_position[i];
227 var var1 var1v : bytes;
228 ctx, var1, var1v := allocate_variable(ctx, ins.params[0]);
230 guards += " (and " + var1v + " " + var1 + ")";
232 guards += " (and " + var1v + " (not " + var1 + "))";
238 if len_greater_than(guards, 0) then
239 assumes += " (or" + guards + ")";
240 for ili := 0 to len(ctx.blocks[bgi].instrs) do [
241 var igi := ctx.blocks[bgi].instrs[ili];
242 var ins := ctx.instrs[igi];
243 if ins.opcode = P_Assume then [
244 var var1 var1v : bytes;
245 ctx, var1, var1v := allocate_variable(ctx, ins.params[0]);
246 if len_greater_than(var1, 0) then
247 assumes += " (=> " + var1v + " " + var1 + ")";
248 ] else if ins.opcode = P_Claim then [
249 var var1 var1v : bytes;
250 ctx, var1, var1v := allocate_variable(ctx, ins.params[0]);
251 if len_greater_than(var1, 0) then [
252 claims += " (=> " + var1v + " " + var1 + ")";
256 for i := 0 to len(ctx.blocks[bgi].post_list) do [
257 var p := ctx.blocks[bgi].post_list[i];
258 claims += " " + ctx.blocks[p].verifier_name;
260 if len(assumes) = 0 then
262 if len(claims) = 0 then
264 z3_eval_smtlib2_string_noret("(assert (= " + ctx.blocks[bgi].verifier_name + " (=> (and" + assumes + ") (and" + claims + "))))");
267 z3_eval_smtlib2_string_noret("(assert (not " + ctx.blocks[0].verifier_name + "))");
268 b := z3_eval_smtlib2_string("(check-sat)");
269 if list_begins_with(b, "unsat") then
271 if list_begins_with(b, "sat") then [
272 b := z3_eval_smtlib2_string("(get-model)");
273 b := "Verification of function " + ctx.name + " failed:" + nl + b;
274 return exception_make_str(unit_type, ec_async, error_compiler_error, 0, false, b);
276 b := "Verification of function " + ctx.name + " inconclusive";
277 return exception_make_str(unit_type, ec_async, error_compiler_error, 0, false, b);