LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
SMTOps.td
Go to the documentation of this file.
1//===- SMTOps.td - SMT dialect operations ------------------*- tablegen -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#ifndef MLIR_DIALECT_SMT_IR_SMTOPS_TD
10#define MLIR_DIALECT_SMT_IR_SMTOPS_TD
11
12include "llzk/Dialect/SMT/IR/SMTDialect.td"
13include "llzk/Dialect/SMT/IR/SMTAttributes.td"
14include "llzk/Dialect/SMT/IR/SMTTypes.td"
15include "mlir/IR/EnumAttr.td"
16include "mlir/IR/OpAsmInterface.td"
17include "mlir/Interfaces/InferTypeOpInterface.td"
18include "mlir/Interfaces/SideEffectInterfaces.td"
19include "mlir/Interfaces/ControlFlowInterfaces.td"
20
21class SMTOp<string mnemonic, list<Trait> traits = []>
22 : Op<SMTDialect, mnemonic, traits>;
23
24def DeclareFunOp
25 : SMTOp<"declare_fun", [DeclareOpInterfaceMethods<
26 OpAsmOpInterface, ["getAsmResultNames"]>]> {
27 let summary = "declare a symbolic value of a given sort";
28 let description = [{
29 This operation declares a symbolic value just as the `declare-const` and
30 `declare-fun` statements in SMT-LIB 2.7. The result type determines the SMT
31 sort of the symbolic value. The returned value can then be used to refer to
32 the symbolic value instead of using the identifier like in SMT-LIB.
33
34 The optionally provided string will be used as a prefix for the newly
35 generated identifier (useful for easier readability when exporting to
36 SMT-LIB). Each `declare` will always provide a unique new symbolic value
37 even if the identifier strings are the same.
38
39 Note that there does not exist a separate operation equivalent to
40 SMT-LIBs `define-fun` since
41 ```
42 (define-fun f (a Int) Int (-a))
43 ```
44 is only syntactic sugar for
45 ```
46 %f = smt.declare_fun : !smt.func<(!smt.int) !smt.int>
47 %0 = smt.forall {
48 ^bb0(%arg0: !smt.int):
49 %1 = smt.apply_func %f(%arg0) : !smt.func<(!smt.int) !smt.int>
50 %2 = smt.int.neg %arg0
51 %3 = smt.eq %1, %2 : !smt.int
52 smt.yield %3 : !smt.bool
53 }
54 smt.assert %0
55 ```
56
57 Note that this operation cannot be marked as Pure since two operations (even
58 with the same identifier string) could then be CSEd, leading to incorrect
59 behavior.
60 }];
61
62 let arguments = (ins OptionalAttr<StrAttr>:$namePrefix);
63 let results = (outs Res<AnySMTType, "a symbolic value", [MemAlloc]>:$result);
64
65 let assemblyFormat = [{
66 ($namePrefix^)? attr-dict `:` qualified(type($result))
67 }];
68
69 let builders = [OpBuilder<(ins "mlir::Type":$type), [{
70 build($_builder, $_state, type, nullptr);
71 }]>];
72}
73
74def BoolConstantOp
75 : SMTOp<"constant", [Pure, ConstantLike,
76 DeclareOpInterfaceMethods<
77 OpAsmOpInterface, ["getAsmResultNames"]>,
78]> {
79 let summary = "Produce a constant boolean";
80 let description = [{
81 Produces the constant expressions 'true' and 'false' as described in the
82 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2) of the SMT-LIB
83 Standard 2.7.
84 }];
85
86 let arguments = (ins BoolAttr:$value);
87 let results = (outs BoolType:$result);
88 let assemblyFormat = "$value attr-dict";
89
90 let hasFolder = true;
91}
92
93def SolverOp : SMTOp<"solver", [IsolatedFromAbove,
94 SingleBlockImplicitTerminator<"smt::YieldOp">,
95]> {
96 let summary = "create a solver instance within a lifespan";
97 let description = [{
98 This operation defines an SMT context with a solver instance. SMT operations
99 are only valid when being executed between the start and end of the region
100 of this operation. Any invocation outside is undefined. However, they do not
101 have to be direct children of this operation. For example, it is allowed to
102 have SMT operations in a `func.func` which is only called from within this
103 region. No SMT value may enter or exit the lifespan of this region (such
104 that no value created from another SMT context can be used in this scope and
105 the solver can deallocate all state required to keep track of SMT values at
106 the end).
107
108 As a result, the region is comparable to an entire SMT-LIB script, but
109 allows for concrete operations and control-flow. Concrete values may be
110 passed in and returned to influence the computations after the `smt.solver`
111 operation.
112
113 Example:
114 ```mlir
115 %0:2 = smt.solver (%in) {smt.some_attr} : (i8) -> (i8, i32) {
116 ^bb0(%arg0: i8):
117 %c = smt.declare_fun "c" : !smt.bool
118 smt.assert %c
119 %1 = smt.check sat {
120 %c1_i32 = arith.constant 1 : i32
121 smt.yield %c1_i32 : i32
122 } unknown {
123 %c0_i32 = arith.constant 0 : i32
124 smt.yield %c0_i32 : i32
125 } unsat {
126 %c-1_i32 = arith.constant -1 : i32
127 smt.yield %c-1_i32 : i32
128 } -> i32
129 smt.yield %arg0, %1 : i8, i32
130 }
131 ```
132 }];
133
134 let arguments = (ins Variadic<AnyNonSMTType>:$inputs);
135 let regions = (region SizedRegion<1>:$bodyRegion);
136 let results = (outs Variadic<AnyNonSMTType>:$results);
137
138 let assemblyFormat = [{
139 `(` $inputs `)` attr-dict `:` functional-type($inputs, $results) $bodyRegion
140 }];
141
142 let hasRegionVerifier = true;
143}
144
145def SetLogicOp : SMTOp<"set_logic", [HasParent<"smt::SolverOp">, ]> {
146 let summary = "set the logic for the SMT solver";
147 let arguments = (ins StrAttr:$logic);
148 let assemblyFormat = "$logic attr-dict";
149}
150
151def SetInfoOp : SMTOp<"set_info", []> {
152 let summary = "attach SMT-LIB set-info metadata to the script";
153 let description = [{
154 This operation models SMT-LIB's `(set-info ...)` command directly. It is a
155 generic script-level metadata mechanism and can be used for standardized
156 keys such as `:status` as well as tool-specific keys such as
157 `:llzk-stage`.
158
159 Example:
160 ```mlir
161 smt.set_info ":llzk-stage" "pre"
162 smt.set_info ":status" sat
163 smt.set_info ":notes" ("phase1" ok :custom)
164 ```
165 }];
166
167 let arguments = (ins KeywordAttr:$key, AnyAttr:$value);
168
169 let hasCustomAssemblyFormat = 1;
170 let hasVerifier = 1;
171}
172
173def AssertOp : SMTOp<"assert", []> {
174 let summary = "assert that a boolean expression holds";
175 let arguments = (ins BoolType:$input);
176 let assemblyFormat = "$input attr-dict";
177}
178
179def ResetOp : SMTOp<"reset", []> {
180 let summary = "reset the solver";
181 let assemblyFormat = "attr-dict";
182}
183
184def PushOp : SMTOp<"push", []> {
185 let summary = "push a given number of levels onto the assertion stack";
186 let arguments = (ins ConfinedAttr<I32Attr, [IntNonNegative]>:$count);
187 let assemblyFormat = "$count attr-dict";
188}
189
190def PopOp : SMTOp<"pop", []> {
191 let summary = "pop a given number of levels from the assertion stack";
192 let arguments = (ins ConfinedAttr<I32Attr, [IntNonNegative]>:$count);
193 let assemblyFormat = "$count attr-dict";
194}
195
196def CheckOp : SMTOp<"check", [NoRegionArguments,
197 SingleBlockImplicitTerminator<"smt::YieldOp">,
198]> {
199 let summary = "check if the current set of assertions is satisfiable";
200 let description = [{
201 This operation checks if all the assertions in the solver defined by the
202 nearest ancestor operation of type `smt.solver` are consistent. The outcome
203 an be 'satisfiable', 'unknown', or 'unsatisfiable' and the corresponding
204 region will be executed. It is the corresponding construct to the
205 `check-sat` in SMT-LIB.
206
207 Example:
208 ```mlir
209 %0 = smt.check sat {
210 %c1_i32 = arith.constant 1 : i32
211 smt.yield %c1_i32 : i32
212 } unknown {
213 %c0_i32 = arith.constant 0 : i32
214 smt.yield %c0_i32 : i32
215 } unsat {
216 %c-1_i32 = arith.constant -1 : i32
217 smt.yield %c-1_i32 : i32
218 } -> i32
219 ```
220 }];
221
222 let regions = (region SizedRegion<1>:$satRegion,
223 SizedRegion<1>:$unknownRegion, SizedRegion<1>:$unsatRegion);
224 let results = (outs Variadic<AnyType>:$results);
225
226 let assemblyFormat = [{
227 attr-dict `sat` $satRegion `unknown` $unknownRegion `unsat` $unsatRegion
228 (`->` qualified(type($results))^ )?
229 }];
230
231 let hasRegionVerifier = true;
232}
233
234def YieldOp : SMTOp<"yield", [Pure, Terminator, ReturnLike,
235 ParentOneOf<["smt::SolverOp", "smt::CheckOp",
236 "smt::ForallOp", "smt::ExistsOp"]>,
237]> {
238 let summary = "terminator operation for various regions of SMT operations";
239 let arguments = (ins Variadic<AnyType>:$values);
240 let assemblyFormat = "($values^ `:` qualified(type($values)))? attr-dict";
241 let builders = [OpBuilder<(ins), [{
242 build($_builder, $_state, {});
243 }]>];
244}
245
246def ApplyFuncOp
247 : SMTOp<"apply_func", [Pure,
248 TypesMatchWith<
249 "summary", "func", "result",
250 "cast<SMTFuncType>($_self).getRangeType()">,
251 RangedTypesMatchWith<
252 "summary", "func", "args",
253 "cast<SMTFuncType>($_self).getDomainTypes()">]> {
254 let summary = "apply a function";
255 let description = [{
256 This operation performs a function application as described in the
257 [SMT-LIB 2.7 standard](https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-02-05.pdf).
258 It is part of the language itself rather than a theory or logic.
259 }];
260
261 let arguments = (ins SMTFuncType:$func, Variadic<AnyNonFuncSMTType>:$args);
262 let results = (outs AnyNonFuncSMTType:$result);
263
264 let assemblyFormat = [{
265 $func `(` $args `)` attr-dict `:` qualified(type($func))
266 }];
267}
268
269def EqOp : SMTOp<"eq", [Pure, SameTypeOperands]> {
270 let summary = "returns true iff all operands are identical";
271 let description = [{
272 This operation compares the operands and returns true iff all operands are
273 identical. The semantics are equivalent to the `=` operator defined in the
274 SMT-LIB Standard 2.7 in the
275 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2).
276
277 Any SMT sort/type is allowed for the operands and it supports a variadic
278 number of operands, but requires at least two. This is because the `=`
279 operator is annotated with `:chainable` which means that `= a b c d` is
280 equivalent to `and (= a b) (= b c) (= c d)` where `and` is annotated
281 `:left-assoc`, i.e., it can be further rewritten to
282 `and (and (= a b) (= b c)) (= c d)`.
283 }];
284
285 let arguments = (ins Variadic<AnyNonFuncSMTType>:$inputs);
286 let results = (outs BoolType:$result);
287
288 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
289 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
290 }]>];
291
292 let hasCustomAssemblyFormat = true;
293 let hasVerifier = true;
294}
295
296def DistinctOp : SMTOp<"distinct", [Pure, SameTypeOperands]> {
297 let summary = "returns true iff all operands are not identical to any other";
298 let description = [{
299 This operation compares the operands and returns true iff all operands are
300 not identical to any of the other operands. The semantics are equivalent to
301 the `distinct` operator defined in the SMT-LIB Standard 2.7 in the
302 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2).
303
304 Any SMT sort/type is allowed for the operands and it supports a variadic
305 number of operands, but requires at least two. This is because the
306 `distinct` operator is annotated with `:pairwise` which means that
307 `distinct a b c d` is equivalent to
308 ```
309 and (distinct a b) (distinct a c) (distinct a d)
310 (distinct b c) (distinct b d)
311 (distinct c d)
312 ```
313 where `and` is annotated `:left-assoc`, i.e., it can be further rewritten to
314 ```
315 (and (and (and (and (and (distinct a b)
316 (distinct a c))
317 (distinct a d))
318 (distinct b c))
319 (distinct b d))
320 (distinct c d)
321 ```
322 }];
323
324 let arguments = (ins Variadic<AnyNonFuncSMTType>:$inputs);
325 let results = (outs BoolType:$result);
326
327 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
328 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
329 }]>];
330
331 let hasCustomAssemblyFormat = true;
332 let hasVerifier = true;
333}
334
335def IteOp
336 : SMTOp<"ite", [Pure,
337 AllTypesMatch<["thenValue", "elseValue", "result"]>]> {
338 let summary = "an if-then-else function";
339 let description = [{
340 This operation returns its second operand or its third operand depending on
341 whether its first operand is true or not. The semantics are equivalent to
342 the `ite` operator defined in the
343 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2) of the SMT-LIB
344 2.7 standard.
345 }];
346
347 let arguments = (ins BoolType:$cond, AnySMTType:$thenValue,
348 AnySMTType:$elseValue);
349 let results = (outs AnySMTType:$result);
350
351 let assemblyFormat = [{
352 $cond `,` $thenValue `,` $elseValue attr-dict `:` qualified(type($result))
353 }];
354}
355
356def NotOp : SMTOp<"not", [Pure]> {
357 let summary = "a boolean negation";
358 let description = [{
359 This operation performs a boolean negation. The semantics are equivalent to
360 the 'not' operator in the
361 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2) of the SMT-LIB
362 Standard 2.7.
363 }];
364
365 let arguments = (ins BoolType:$input);
366 let results = (outs BoolType:$result);
367 let assemblyFormat = "$input attr-dict";
368}
369
370class VariadicBoolOp<string mnemonic, string desc> : SMTOp<mnemonic, [Pure]> {
371 let summary = desc;
372 let description = "This operation performs "#desc#[{.
373 The semantics are equivalent to the '}]#mnemonic#[{' operator in the
374 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2).
375 of the SMT-LIB Standard 2.7.
376
377 It supports a variadic number of operands, but requires at least two.
378 This is because the operator is annotated with the `:left-assoc` attribute
379 which means that `op a b c` is equivalent to `(op (op a b) c)`.
380 }];
381
382 let arguments = (ins Variadic<BoolType>:$inputs);
383 let results = (outs BoolType:$result);
384 let assemblyFormat = "$inputs attr-dict";
385
386 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
387 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
388 }]>];
389}
390
391def AndOp : VariadicBoolOp<"and", "a boolean conjunction">;
392def OrOp : VariadicBoolOp<"or", "a boolean disjunction">;
393def XOrOp : VariadicBoolOp<"xor", "a boolean exclusive OR">;
394
395def ImpliesOp : SMTOp<"implies", [Pure]> {
396 let summary = "boolean implication";
397 let description = [{
398 This operation performs a boolean implication. The semantics are equivalent
399 to the '=>' operator in the
400 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2) of the SMT-LIB
401 Standard 2.7.
402 }];
403
404 let arguments = (ins BoolType:$lhs, BoolType:$rhs);
405 let results = (outs BoolType:$result);
406 let assemblyFormat = "$lhs `,` $rhs attr-dict";
407}
408
409class QuantifierOp<string mnemonic>
410 : SMTOp<mnemonic, [RecursivelySpeculatable, RecursiveMemoryEffects,
411 SingleBlockImplicitTerminator<"smt::YieldOp">,
412]> {
413 let description = [{
414 This operation represents the }]#summary#[{ as described in the
415 [SMT-LIB 2.7 standard](https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-02-05.pdf).
416 It is part of the language itself rather than a theory or logic.
417
418 The operation specifies the name prefixes (as an optional attribute) and
419 types (as the types of the block arguments of the regions) of bound
420 variables that may be used in the 'body' of the operation. If a 'patterns'
421 region is specified, the block arguments must match the ones of the 'body'
422 region and (other than there) must be used at least once in the 'patterns'
423 region. It may also not contain any operations that bind variables, such as
424 quantifiers. While the 'body' region must always yield exactly one
425 `!smt.bool`-typed value, the 'patterns' region can yield an arbitrary number
426 (but at least one) of SMT values.
427
428 The bound variables can be any SMT type except of functions, since SMT only
429 supports first-order logic.
430
431 The 'no_patterns' attribute is only allowed when no 'patterns' region is
432 specified and forbids the solver to generate and use patterns for this
433 quantifier.
434
435 The 'weight' attribute indicates the importance of this quantifier being
436 instantiated compared to other quantifiers that may be present. The default
437 value is zero.
438
439 Both the 'no_patterns' and 'weight' attributes are annotations to the
440 quantifiers body term. Annotations and attributes are described in the
441 standard in sections 3.4, and 3.6 (specifically 3.6.5). SMT-LIB allows
442 adding custom attributes to provide solvers with additional metadata, e.g.,
443 hints such as above mentioned attributes. They are not part of the standard
444 themselves, but supported by common SMT solvers (e.g., Z3).
445 }];
446
447 let arguments = (ins DefaultValuedAttr<I32Attr, "0">:$weight,
448 UnitAttr:$noPattern, OptionalAttr<StrArrayAttr>:$boundVarNames);
449 let regions = (region SizedRegion<1>:$body,
450 VariadicRegion<SizedRegion<1>>:$patterns);
451 let results = (outs BoolType:$result);
452
453 let builders = [OpBuilder<(ins "mlir::TypeRange":$boundVarTypes,
454 "llvm::function_ref<mlir::Value(mlir::OpBuilder &, mlir::Location, "
455 "mlir::ValueRange)>":$bodyBuilder,
456 CArg<"std::optional<llvm::ArrayRef<mlir::StringRef>>",
457 "std::nullopt">:$boundVarNames,
458 CArg<"llvm::function_ref<mlir::ValueRange(mlir::OpBuilder &, "
459 "mlir::Location, mlir::ValueRange)>",
460 "{}">:$patternBuilder,
461 CArg<"uint32_t", "0">:$weight, CArg<"bool", "false">:$noPattern)>];
462 let skipDefaultBuilders = true;
463
464 let assemblyFormat = [{
465 ($boundVarNames^)? (`no_pattern` $noPattern^)? (`weight` $weight^)?
466 attr-dict-with-keyword $body (`patterns` $patterns^)?
467 }];
468
469 let hasVerifier = true;
470 let hasRegionVerifier = true;
471}
472
473def ForallOp : QuantifierOp<"forall"> { let summary = "forall quantifier"; }
474def ExistsOp : QuantifierOp<"exists"> { let summary = "exists quantifier"; }
475
476#endif // MLIR_DIALECT_SMT_IR_SMTOPS_TD