LLZK 2.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 AssertOp : SMTOp<"assert", []> {
152 let summary = "assert that a boolean expression holds";
153 let arguments = (ins BoolType:$input);
154 let assemblyFormat = "$input attr-dict";
155}
156
157def ResetOp : SMTOp<"reset", []> {
158 let summary = "reset the solver";
159 let assemblyFormat = "attr-dict";
160}
161
162def PushOp : SMTOp<"push", []> {
163 let summary = "push a given number of levels onto the assertion stack";
164 let arguments = (ins ConfinedAttr<I32Attr, [IntNonNegative]>:$count);
165 let assemblyFormat = "$count attr-dict";
166}
167
168def PopOp : SMTOp<"pop", []> {
169 let summary = "pop a given number of levels from the assertion stack";
170 let arguments = (ins ConfinedAttr<I32Attr, [IntNonNegative]>:$count);
171 let assemblyFormat = "$count attr-dict";
172}
173
174def CheckOp : SMTOp<"check", [NoRegionArguments,
175 SingleBlockImplicitTerminator<"smt::YieldOp">,
176]> {
177 let summary = "check if the current set of assertions is satisfiable";
178 let description = [{
179 This operation checks if all the assertions in the solver defined by the
180 nearest ancestor operation of type `smt.solver` are consistent. The outcome
181 an be 'satisfiable', 'unknown', or 'unsatisfiable' and the corresponding
182 region will be executed. It is the corresponding construct to the
183 `check-sat` in SMT-LIB.
184
185 Example:
186 ```mlir
187 %0 = smt.check sat {
188 %c1_i32 = arith.constant 1 : i32
189 smt.yield %c1_i32 : i32
190 } unknown {
191 %c0_i32 = arith.constant 0 : i32
192 smt.yield %c0_i32 : i32
193 } unsat {
194 %c-1_i32 = arith.constant -1 : i32
195 smt.yield %c-1_i32 : i32
196 } -> i32
197 ```
198 }];
199
200 let regions = (region SizedRegion<1>:$satRegion,
201 SizedRegion<1>:$unknownRegion, SizedRegion<1>:$unsatRegion);
202 let results = (outs Variadic<AnyType>:$results);
203
204 let assemblyFormat = [{
205 attr-dict `sat` $satRegion `unknown` $unknownRegion `unsat` $unsatRegion
206 (`->` qualified(type($results))^ )?
207 }];
208
209 let hasRegionVerifier = true;
210}
211
212def YieldOp : SMTOp<"yield", [Pure, Terminator, ReturnLike,
213 ParentOneOf<["smt::SolverOp", "smt::CheckOp",
214 "smt::ForallOp", "smt::ExistsOp"]>,
215]> {
216 let summary = "terminator operation for various regions of SMT operations";
217 let arguments = (ins Variadic<AnyType>:$values);
218 let assemblyFormat = "($values^ `:` qualified(type($values)))? attr-dict";
219 let builders = [OpBuilder<(ins), [{
220 build($_builder, $_state, {});
221 }]>];
222}
223
224def ApplyFuncOp
225 : SMTOp<"apply_func", [Pure,
226 TypesMatchWith<
227 "summary", "func", "result",
228 "cast<SMTFuncType>($_self).getRangeType()">,
229 RangedTypesMatchWith<
230 "summary", "func", "args",
231 "cast<SMTFuncType>($_self).getDomainTypes()">]> {
232 let summary = "apply a function";
233 let description = [{
234 This operation performs a function application as described in the
235 [SMT-LIB 2.7 standard](https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-02-05.pdf).
236 It is part of the language itself rather than a theory or logic.
237 }];
238
239 let arguments = (ins SMTFuncType:$func, Variadic<AnyNonFuncSMTType>:$args);
240 let results = (outs AnyNonFuncSMTType:$result);
241
242 let assemblyFormat = [{
243 $func `(` $args `)` attr-dict `:` qualified(type($func))
244 }];
245}
246
247def EqOp : SMTOp<"eq", [Pure, SameTypeOperands]> {
248 let summary = "returns true iff all operands are identical";
249 let description = [{
250 This operation compares the operands and returns true iff all operands are
251 identical. The semantics are equivalent to the `=` operator defined in the
252 SMT-LIB Standard 2.7 in the
253 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2).
254
255 Any SMT sort/type is allowed for the operands and it supports a variadic
256 number of operands, but requires at least two. This is because the `=`
257 operator is annotated with `:chainable` which means that `= a b c d` is
258 equivalent to `and (= a b) (= b c) (= c d)` where `and` is annotated
259 `:left-assoc`, i.e., it can be further rewritten to
260 `and (and (= a b) (= b c)) (= c d)`.
261 }];
262
263 let arguments = (ins Variadic<AnyNonFuncSMTType>:$inputs);
264 let results = (outs BoolType:$result);
265
266 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
267 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
268 }]>];
269
270 let hasCustomAssemblyFormat = true;
271 let hasVerifier = true;
272}
273
274def DistinctOp : SMTOp<"distinct", [Pure, SameTypeOperands]> {
275 let summary = "returns true iff all operands are not identical to any other";
276 let description = [{
277 This operation compares the operands and returns true iff all operands are
278 not identical to any of the other operands. The semantics are equivalent to
279 the `distinct` operator defined in the SMT-LIB Standard 2.7 in the
280 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2).
281
282 Any SMT sort/type is allowed for the operands and it supports a variadic
283 number of operands, but requires at least two. This is because the
284 `distinct` operator is annotated with `:pairwise` which means that
285 `distinct a b c d` is equivalent to
286 ```
287 and (distinct a b) (distinct a c) (distinct a d)
288 (distinct b c) (distinct b d)
289 (distinct c d)
290 ```
291 where `and` is annotated `:left-assoc`, i.e., it can be further rewritten to
292 ```
293 (and (and (and (and (and (distinct a b)
294 (distinct a c))
295 (distinct a d))
296 (distinct b c))
297 (distinct b d))
298 (distinct c d)
299 ```
300 }];
301
302 let arguments = (ins Variadic<AnyNonFuncSMTType>:$inputs);
303 let results = (outs BoolType:$result);
304
305 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
306 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
307 }]>];
308
309 let hasCustomAssemblyFormat = true;
310 let hasVerifier = true;
311}
312
313def IteOp
314 : SMTOp<"ite", [Pure,
315 AllTypesMatch<["thenValue", "elseValue", "result"]>]> {
316 let summary = "an if-then-else function";
317 let description = [{
318 This operation returns its second operand or its third operand depending on
319 whether its first operand is true or not. The semantics are equivalent to
320 the `ite` operator defined in the
321 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2) of the SMT-LIB
322 2.7 standard.
323 }];
324
325 let arguments = (ins BoolType:$cond, AnySMTType:$thenValue,
326 AnySMTType:$elseValue);
327 let results = (outs AnySMTType:$result);
328
329 let assemblyFormat = [{
330 $cond `,` $thenValue `,` $elseValue attr-dict `:` qualified(type($result))
331 }];
332}
333
334def NotOp : SMTOp<"not", [Pure]> {
335 let summary = "a boolean negation";
336 let description = [{
337 This operation performs a boolean negation. The semantics are equivalent to
338 the 'not' operator in the
339 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2) of the SMT-LIB
340 Standard 2.7.
341 }];
342
343 let arguments = (ins BoolType:$input);
344 let results = (outs BoolType:$result);
345 let assemblyFormat = "$input attr-dict";
346}
347
348class VariadicBoolOp<string mnemonic, string desc> : SMTOp<mnemonic, [Pure]> {
349 let summary = desc;
350 let description = "This operation performs "#desc#[{.
351 The semantics are equivalent to the '}]#mnemonic#[{' operator in the
352 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2).
353 of the SMT-LIB Standard 2.7.
354
355 It supports a variadic number of operands, but requires at least two.
356 This is because the operator is annotated with the `:left-assoc` attribute
357 which means that `op a b c` is equivalent to `(op (op a b) c)`.
358 }];
359
360 let arguments = (ins Variadic<BoolType>:$inputs);
361 let results = (outs BoolType:$result);
362 let assemblyFormat = "$inputs attr-dict";
363
364 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
365 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
366 }]>];
367}
368
369def AndOp : VariadicBoolOp<"and", "a boolean conjunction">;
370def OrOp : VariadicBoolOp<"or", "a boolean disjunction">;
371def XOrOp : VariadicBoolOp<"xor", "a boolean exclusive OR">;
372
373def ImpliesOp : SMTOp<"implies", [Pure]> {
374 let summary = "boolean implication";
375 let description = [{
376 This operation performs a boolean implication. The semantics are equivalent
377 to the '=>' operator in the
378 [Core theory](https://smtlib.cs.uiowa.edu/Theories/Core.smt2) of the SMT-LIB
379 Standard 2.7.
380 }];
381
382 let arguments = (ins BoolType:$lhs, BoolType:$rhs);
383 let results = (outs BoolType:$result);
384 let assemblyFormat = "$lhs `,` $rhs attr-dict";
385}
386
387class QuantifierOp<string mnemonic>
388 : SMTOp<mnemonic, [RecursivelySpeculatable, RecursiveMemoryEffects,
389 SingleBlockImplicitTerminator<"smt::YieldOp">,
390]> {
391 let description = [{
392 This operation represents the }]#summary#[{ as described in the
393 [SMT-LIB 2.7 standard](https://smt-lib.org/papers/smt-lib-reference-v2.7-r2025-02-05.pdf).
394 It is part of the language itself rather than a theory or logic.
395
396 The operation specifies the name prefixes (as an optional attribute) and
397 types (as the types of the block arguments of the regions) of bound
398 variables that may be used in the 'body' of the operation. If a 'patterns'
399 region is specified, the block arguments must match the ones of the 'body'
400 region and (other than there) must be used at least once in the 'patterns'
401 region. It may also not contain any operations that bind variables, such as
402 quantifiers. While the 'body' region must always yield exactly one
403 `!smt.bool`-typed value, the 'patterns' region can yield an arbitrary number
404 (but at least one) of SMT values.
405
406 The bound variables can be any SMT type except of functions, since SMT only
407 supports first-order logic.
408
409 The 'no_patterns' attribute is only allowed when no 'patterns' region is
410 specified and forbids the solver to generate and use patterns for this
411 quantifier.
412
413 The 'weight' attribute indicates the importance of this quantifier being
414 instantiated compared to other quantifiers that may be present. The default
415 value is zero.
416
417 Both the 'no_patterns' and 'weight' attributes are annotations to the
418 quantifiers body term. Annotations and attributes are described in the
419 standard in sections 3.4, and 3.6 (specifically 3.6.5). SMT-LIB allows
420 adding custom attributes to provide solvers with additional metadata, e.g.,
421 hints such as above mentioned attributes. They are not part of the standard
422 themselves, but supported by common SMT solvers (e.g., Z3).
423 }];
424
425 let arguments = (ins DefaultValuedAttr<I32Attr, "0">:$weight,
426 UnitAttr:$noPattern, OptionalAttr<StrArrayAttr>:$boundVarNames);
427 let regions = (region SizedRegion<1>:$body,
428 VariadicRegion<SizedRegion<1>>:$patterns);
429 let results = (outs BoolType:$result);
430
431 let builders = [OpBuilder<(ins "mlir::TypeRange":$boundVarTypes,
432 "llvm::function_ref<mlir::Value(mlir::OpBuilder &, mlir::Location, "
433 "mlir::ValueRange)>":$bodyBuilder,
434 CArg<"std::optional<llvm::ArrayRef<mlir::StringRef>>",
435 "std::nullopt">:$boundVarNames,
436 CArg<"llvm::function_ref<mlir::ValueRange(mlir::OpBuilder &, "
437 "mlir::Location, mlir::ValueRange)>",
438 "{}">:$patternBuilder,
439 CArg<"uint32_t", "0">:$weight, CArg<"bool", "false">:$noPattern)>];
440 let skipDefaultBuilders = true;
441
442 let assemblyFormat = [{
443 ($boundVarNames^)? (`no_pattern` $noPattern^)? (`weight` $weight^)?
444 attr-dict-with-keyword $body (`patterns` $patterns^)?
445 }];
446
447 let hasVerifier = true;
448 let hasRegionVerifier = true;
449}
450
451def ForallOp : QuantifierOp<"forall"> { let summary = "forall quantifier"; }
452def ExistsOp : QuantifierOp<"exists"> { let summary = "exists quantifier"; }
453
454#endif // MLIR_DIALECT_SMT_IR_SMTOPS_TD