1//===- SMTOps.td - SMT dialect operations ------------------*- tablegen -*-===//
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
7//===----------------------------------------------------------------------===//
9#ifndef MLIR_DIALECT_SMT_IR_SMTOPS_TD
10#define MLIR_DIALECT_SMT_IR_SMTOPS_TD
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"
21class SMTOp<string mnemonic, list<Trait> traits = []>
22 : Op<SMTDialect, mnemonic, traits>;
25 : SMTOp<"declare_fun", [DeclareOpInterfaceMethods<
26 OpAsmOpInterface, ["getAsmResultNames"]>]> {
27 let summary = "declare a symbolic value of a given sort";
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.
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.
39 Note that there does not exist a separate operation equivalent to
40 SMT-LIBs `define-fun` since
42 (define-fun f (a Int) Int (-a))
44 is only syntactic sugar for
46 %f = smt.declare_fun : !smt.func<(!smt.int) !smt.int>
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
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
62 let arguments = (ins OptionalAttr<StrAttr>:$namePrefix);
63 let results = (outs Res<AnySMTType, "a symbolic value", [MemAlloc]>:$result);
65 let assemblyFormat = [{
66 ($namePrefix^)? attr-dict `:` qualified(type($result))
69 let builders = [OpBuilder<(ins "mlir::Type":$type), [{
70 build($_builder, $_state, type, nullptr);
75 : SMTOp<"constant", [Pure, ConstantLike,
76 DeclareOpInterfaceMethods<
77 OpAsmOpInterface, ["getAsmResultNames"]>,
79 let summary = "Produce a constant boolean";
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
86 let arguments = (ins BoolAttr:$value);
87 let results = (outs BoolType:$result);
88 let assemblyFormat = "$value attr-dict";
93def SolverOp : SMTOp<"solver", [IsolatedFromAbove,
94 SingleBlockImplicitTerminator<"smt::YieldOp">,
96 let summary = "create a solver instance within a lifespan";
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
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`
115 %0:2 = smt.solver (%in) {smt.some_attr} : (i8) -> (i8, i32) {
117 %c = smt.declare_fun "c" : !smt.bool
120 %c1_i32 = arith.constant 1 : i32
121 smt.yield %c1_i32 : i32
123 %c0_i32 = arith.constant 0 : i32
124 smt.yield %c0_i32 : i32
126 %c-1_i32 = arith.constant -1 : i32
127 smt.yield %c-1_i32 : i32
129 smt.yield %arg0, %1 : i8, i32
134 let arguments = (ins Variadic<AnyNonSMTType>:$inputs);
135 let regions = (region SizedRegion<1>:$bodyRegion);
136 let results = (outs Variadic<AnyNonSMTType>:$results);
138 let assemblyFormat = [{
139 `(` $inputs `)` attr-dict `:` functional-type($inputs, $results) $bodyRegion
142 let hasRegionVerifier = true;
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";
151def SetInfoOp : SMTOp<"set_info", []> {
152 let summary = "attach SMT-LIB set-info metadata to the script";
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
161 smt.set_info ":llzk-stage" "pre"
162 smt.set_info ":status" sat
163 smt.set_info ":notes" ("phase1" ok :custom)
167 let arguments = (ins KeywordAttr:$key, AnyAttr:$value);
169 let hasCustomAssemblyFormat = 1;
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";
179def ResetOp : SMTOp<"reset", []> {
180 let summary = "reset the solver";
181 let assemblyFormat = "attr-dict";
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";
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";
196def CheckOp : SMTOp<"check", [NoRegionArguments,
197 SingleBlockImplicitTerminator<"smt::YieldOp">,
199 let summary = "check if the current set of assertions is satisfiable";
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.
210 %c1_i32 = arith.constant 1 : i32
211 smt.yield %c1_i32 : i32
213 %c0_i32 = arith.constant 0 : i32
214 smt.yield %c0_i32 : i32
216 %c-1_i32 = arith.constant -1 : i32
217 smt.yield %c-1_i32 : i32
222 let regions = (region SizedRegion<1>:$satRegion,
223 SizedRegion<1>:$unknownRegion, SizedRegion<1>:$unsatRegion);
224 let results = (outs Variadic<AnyType>:$results);
226 let assemblyFormat = [{
227 attr-dict `sat` $satRegion `unknown` $unknownRegion `unsat` $unsatRegion
228 (`->` qualified(type($results))^ )?
231 let hasRegionVerifier = true;
234def YieldOp : SMTOp<"yield", [Pure, Terminator, ReturnLike,
235 ParentOneOf<["smt::SolverOp", "smt::CheckOp",
236 "smt::ForallOp", "smt::ExistsOp"]>,
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, {});
247 : SMTOp<"apply_func", [Pure,
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";
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.
261 let arguments = (ins SMTFuncType:$func, Variadic<AnyNonFuncSMTType>:$args);
262 let results = (outs AnyNonFuncSMTType:$result);
264 let assemblyFormat = [{
265 $func `(` $args `)` attr-dict `:` qualified(type($func))
269def EqOp : SMTOp<"eq", [Pure, SameTypeOperands]> {
270 let summary = "returns true iff all operands are identical";
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).
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)`.
285 let arguments = (ins Variadic<AnyNonFuncSMTType>:$inputs);
286 let results = (outs BoolType:$result);
288 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
289 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
292 let hasCustomAssemblyFormat = true;
293 let hasVerifier = true;
296def DistinctOp : SMTOp<"distinct", [Pure, SameTypeOperands]> {
297 let summary = "returns true iff all operands are not identical to any other";
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).
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
309 and (distinct a b) (distinct a c) (distinct a d)
310 (distinct b c) (distinct b d)
313 where `and` is annotated `:left-assoc`, i.e., it can be further rewritten to
315 (and (and (and (and (and (distinct a b)
324 let arguments = (ins Variadic<AnyNonFuncSMTType>:$inputs);
325 let results = (outs BoolType:$result);
327 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
328 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
331 let hasCustomAssemblyFormat = true;
332 let hasVerifier = true;
336 : SMTOp<"ite", [Pure,
337 AllTypesMatch<["thenValue", "elseValue", "result"]>]> {
338 let summary = "an if-then-else function";
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
347 let arguments = (ins BoolType:$cond, AnySMTType:$thenValue,
348 AnySMTType:$elseValue);
349 let results = (outs AnySMTType:$result);
351 let assemblyFormat = [{
352 $cond `,` $thenValue `,` $elseValue attr-dict `:` qualified(type($result))
356def NotOp : SMTOp<"not", [Pure]> {
357 let summary = "a boolean negation";
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
365 let arguments = (ins BoolType:$input);
366 let results = (outs BoolType:$result);
367 let assemblyFormat = "$input attr-dict";
370class VariadicBoolOp<string mnemonic, string desc> : SMTOp<mnemonic, [Pure]> {
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.
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)`.
382 let arguments = (ins Variadic<BoolType>:$inputs);
383 let results = (outs BoolType:$result);
384 let assemblyFormat = "$inputs attr-dict";
386 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
387 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
391def AndOp : VariadicBoolOp<"and", "a boolean conjunction">;
392def OrOp : VariadicBoolOp<"or", "a boolean disjunction">;
393def XOrOp : VariadicBoolOp<"xor", "a boolean exclusive OR">;
395def ImpliesOp : SMTOp<"implies", [Pure]> {
396 let summary = "boolean implication";
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
404 let arguments = (ins BoolType:$lhs, BoolType:$rhs);
405 let results = (outs BoolType:$result);
406 let assemblyFormat = "$lhs `,` $rhs attr-dict";
409class QuantifierOp<string mnemonic>
410 : SMTOp<mnemonic, [RecursivelySpeculatable, RecursiveMemoryEffects,
411 SingleBlockImplicitTerminator<"smt::YieldOp">,
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.
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.
428 The bound variables can be any SMT type except of functions, since SMT only
429 supports first-order logic.
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
435 The 'weight' attribute indicates the importance of this quantifier being
436 instantiated compared to other quantifiers that may be present. The default
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).
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);
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;
464 let assemblyFormat = [{
465 ($boundVarNames^)? (`no_pattern` $noPattern^)? (`weight` $weight^)?
466 attr-dict-with-keyword $body (`patterns` $patterns^)?
469 let hasVerifier = true;
470 let hasRegionVerifier = true;
473def ForallOp : QuantifierOp<"forall"> { let summary = "forall quantifier"; }
474def ExistsOp : QuantifierOp<"exists"> { let summary = "exists quantifier"; }
476#endif // MLIR_DIALECT_SMT_IR_SMTOPS_TD