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 AssertOp : SMTOp<"assert", []> {
152 let summary = "assert that a boolean expression holds";
153 let arguments = (ins BoolType:$input);
154 let assemblyFormat = "$input attr-dict";
157def ResetOp : SMTOp<"reset", []> {
158 let summary = "reset the solver";
159 let assemblyFormat = "attr-dict";
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";
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";
174def CheckOp : SMTOp<"check", [NoRegionArguments,
175 SingleBlockImplicitTerminator<"smt::YieldOp">,
177 let summary = "check if the current set of assertions is satisfiable";
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.
188 %c1_i32 = arith.constant 1 : i32
189 smt.yield %c1_i32 : i32
191 %c0_i32 = arith.constant 0 : i32
192 smt.yield %c0_i32 : i32
194 %c-1_i32 = arith.constant -1 : i32
195 smt.yield %c-1_i32 : i32
200 let regions = (region SizedRegion<1>:$satRegion,
201 SizedRegion<1>:$unknownRegion, SizedRegion<1>:$unsatRegion);
202 let results = (outs Variadic<AnyType>:$results);
204 let assemblyFormat = [{
205 attr-dict `sat` $satRegion `unknown` $unknownRegion `unsat` $unsatRegion
206 (`->` qualified(type($results))^ )?
209 let hasRegionVerifier = true;
212def YieldOp : SMTOp<"yield", [Pure, Terminator, ReturnLike,
213 ParentOneOf<["smt::SolverOp", "smt::CheckOp",
214 "smt::ForallOp", "smt::ExistsOp"]>,
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, {});
225 : SMTOp<"apply_func", [Pure,
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";
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.
239 let arguments = (ins SMTFuncType:$func, Variadic<AnyNonFuncSMTType>:$args);
240 let results = (outs AnyNonFuncSMTType:$result);
242 let assemblyFormat = [{
243 $func `(` $args `)` attr-dict `:` qualified(type($func))
247def EqOp : SMTOp<"eq", [Pure, SameTypeOperands]> {
248 let summary = "returns true iff all operands are identical";
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).
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)`.
263 let arguments = (ins Variadic<AnyNonFuncSMTType>:$inputs);
264 let results = (outs BoolType:$result);
266 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
267 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
270 let hasCustomAssemblyFormat = true;
271 let hasVerifier = true;
274def DistinctOp : SMTOp<"distinct", [Pure, SameTypeOperands]> {
275 let summary = "returns true iff all operands are not identical to any other";
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).
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
287 and (distinct a b) (distinct a c) (distinct a d)
288 (distinct b c) (distinct b d)
291 where `and` is annotated `:left-assoc`, i.e., it can be further rewritten to
293 (and (and (and (and (and (distinct a b)
302 let arguments = (ins Variadic<AnyNonFuncSMTType>:$inputs);
303 let results = (outs BoolType:$result);
305 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
306 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
309 let hasCustomAssemblyFormat = true;
310 let hasVerifier = true;
314 : SMTOp<"ite", [Pure,
315 AllTypesMatch<["thenValue", "elseValue", "result"]>]> {
316 let summary = "an if-then-else function";
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
325 let arguments = (ins BoolType:$cond, AnySMTType:$thenValue,
326 AnySMTType:$elseValue);
327 let results = (outs AnySMTType:$result);
329 let assemblyFormat = [{
330 $cond `,` $thenValue `,` $elseValue attr-dict `:` qualified(type($result))
334def NotOp : SMTOp<"not", [Pure]> {
335 let summary = "a boolean negation";
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
343 let arguments = (ins BoolType:$input);
344 let results = (outs BoolType:$result);
345 let assemblyFormat = "$input attr-dict";
348class VariadicBoolOp<string mnemonic, string desc> : SMTOp<mnemonic, [Pure]> {
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.
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)`.
360 let arguments = (ins Variadic<BoolType>:$inputs);
361 let results = (outs BoolType:$result);
362 let assemblyFormat = "$inputs attr-dict";
364 let builders = [OpBuilder<(ins "mlir::Value":$lhs, "mlir::Value":$rhs), [{
365 build($_builder, $_state, mlir::ValueRange{lhs, rhs});
369def AndOp : VariadicBoolOp<"and", "a boolean conjunction">;
370def OrOp : VariadicBoolOp<"or", "a boolean disjunction">;
371def XOrOp : VariadicBoolOp<"xor", "a boolean exclusive OR">;
373def ImpliesOp : SMTOp<"implies", [Pure]> {
374 let summary = "boolean implication";
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
382 let arguments = (ins BoolType:$lhs, BoolType:$rhs);
383 let results = (outs BoolType:$result);
384 let assemblyFormat = "$lhs `,` $rhs attr-dict";
387class QuantifierOp<string mnemonic>
388 : SMTOp<mnemonic, [RecursivelySpeculatable, RecursiveMemoryEffects,
389 SingleBlockImplicitTerminator<"smt::YieldOp">,
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.
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.
406 The bound variables can be any SMT type except of functions, since SMT only
407 supports first-order logic.
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
413 The 'weight' attribute indicates the importance of this quantifier being
414 instantiated compared to other quantifiers that may be present. The default
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).
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);
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;
442 let assemblyFormat = [{
443 ($boundVarNames^)? (`no_pattern` $noPattern^)? (`weight` $weight^)?
444 attr-dict-with-keyword $body (`patterns` $patterns^)?
447 let hasVerifier = true;
448 let hasRegionVerifier = true;
451def ForallOp : QuantifierOp<"forall"> { let summary = "forall quantifier"; }
452def ExistsOp : QuantifierOp<"exists"> { let summary = "exists quantifier"; }
454#endif // MLIR_DIALECT_SMT_IR_SMTOPS_TD