CIRCT 23.0.0git
Loading...
Searching...
No Matches
LowerSMTToZ3LLVM.cpp
Go to the documentation of this file.
1//===- LowerSMTToZ3LLVM.cpp -----------------------------------------------===//
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
14#include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h"
15#include "mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h"
16#include "mlir/Conversion/FuncToLLVM/ConvertFuncToLLVM.h"
17#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
18#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
19#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
20#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h"
21#include "mlir/Dialect/Func/IR/FuncOps.h"
22#include "mlir/Dialect/LLVMIR/FunctionCallUtils.h"
23#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
24#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
25#include "mlir/Dialect/SCF/IR/SCF.h"
26#include "mlir/Dialect/SMT/IR/SMTOps.h"
27#include "mlir/IR/BuiltinDialect.h"
28#include "mlir/Interfaces/FunctionInterfaces.h"
29#include "mlir/Pass/Pass.h"
30#include "mlir/Transforms/DialectConversion.h"
31#include "llvm/ADT/STLExtras.h"
32#include "llvm/ADT/SmallPtrSet.h"
33#include "llvm/ADT/StringMap.h"
34#include "llvm/ADT/TypeSwitch.h"
35#include "llvm/Support/Debug.h"
36
37#define DEBUG_TYPE "lower-smt-to-z3-llvm"
38
39namespace circt {
40#define GEN_PASS_DEF_LOWERSMTTOZ3LLVM
41#include "circt/Conversion/Passes.h.inc"
42} // namespace circt
43
44using namespace mlir;
45using namespace circt;
46using namespace smt;
47
48//===----------------------------------------------------------------------===//
49// SMTGlobalHandler implementation
50//===----------------------------------------------------------------------===//
51
53 ModuleOp module) {
54 OpBuilder::InsertionGuard guard(builder);
55 builder.setInsertionPointToStart(module.getBody());
56
57 SymbolCache symCache;
58 symCache.addDefinitions(module);
60 names.add(symCache);
61
62 Location loc = module.getLoc();
63 auto ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
64
65 auto createGlobal = [&](StringRef namePrefix) {
66 auto global = LLVM::GlobalOp::create(
67 builder, loc, ptrTy, false, LLVM::Linkage::Internal,
68 names.newName(namePrefix), Attribute{}, /*alignment=*/8);
69 OpBuilder::InsertionGuard g(builder);
70 builder.createBlock(&global.getInitializer());
71 Value res = LLVM::ZeroOp::create(builder, loc, ptrTy);
72 LLVM::ReturnOp::create(builder, loc, res);
73 return global;
74 };
75
76 auto ctxGlobal = createGlobal("ctx");
77 auto solverGlobal = createGlobal("solver");
78
79 return SMTGlobalsHandler(std::move(names), solverGlobal, ctxGlobal);
80}
81
83 mlir::LLVM::GlobalOp solver,
84 mlir::LLVM::GlobalOp ctx)
85 : solver(solver), ctx(ctx), names(names) {}
86
88 mlir::LLVM::GlobalOp solver,
89 mlir::LLVM::GlobalOp ctx)
90 : solver(solver), ctx(ctx) {
91 SymbolCache symCache;
92 symCache.addDefinitions(module);
93 names.add(symCache);
94}
95
96//===----------------------------------------------------------------------===//
97// Lowering Pattern Base
98//===----------------------------------------------------------------------===//
99
100namespace {
101
102template <typename OpTy>
103class SMTLoweringPattern : public OpConversionPattern<OpTy> {
104public:
105 SMTLoweringPattern(const TypeConverter &typeConverter, MLIRContext *context,
106 SMTGlobalsHandler &globals,
107 const LowerSMTToZ3LLVMOptions &options)
108 : OpConversionPattern<OpTy>(typeConverter, context), globals(globals),
109 options(options) {}
110
111private:
112 Value buildGlobalPtrToGlobal(OpBuilder &builder, Location loc,
113 LLVM::GlobalOp global,
114 DenseMap<Block *, Value> &cache) const {
115 Block *block = builder.getBlock();
116 if (auto iter = cache.find(block); iter != cache.end())
117 return iter->getSecond();
118
119 OpBuilder::InsertionGuard g(builder);
120 builder.setInsertionPointToStart(block);
121 Value globalAddr = LLVM::AddressOfOp::create(builder, loc, global);
122 return cache[block] = LLVM::LoadOp::create(
123 builder, loc, LLVM::LLVMPointerType::get(builder.getContext()),
124 globalAddr);
125 }
126
127protected:
128 /// A convenience function to get the pointer to the context from the 'global'
129 /// operation. The result is cached for each basic block, i.e., it is assumed
130 /// that this function is never called in the same basic block again at a
131 /// location (insertion point of the 'builder') not dominating all previous
132 /// locations this function was called at.
133 Value buildContextPtr(OpBuilder &builder, Location loc) const {
134 return buildGlobalPtrToGlobal(builder, loc, globals.ctx, globals.ctxCache);
135 }
136
137 /// A convenience function to get the pointer to the solver from the 'global'
138 /// operation. The result is cached for each basic block, i.e., it is assumed
139 /// that this function is never called in the same basic block again at a
140 /// location (insertion point of the 'builder') not dominating all previous
141 /// locations this function was called at.
142 Value buildSolverPtr(OpBuilder &builder, Location loc) const {
143 return buildGlobalPtrToGlobal(builder, loc, globals.solver,
144 globals.solverCache);
145 }
146
147 /// Create a `llvm.call` operation to a function with the given 'name' and
148 /// 'type'. If there does not already exist a (external) function with that
149 /// name create a matching external function declaration.
150 LLVM::CallOp buildCall(OpBuilder &builder, Location loc, StringRef name,
151 LLVM::LLVMFunctionType funcType,
152 ValueRange args) const {
153 auto &funcOp = globals.funcMap[builder.getStringAttr(name)];
154 if (!funcOp) {
155 OpBuilder::InsertionGuard guard(builder);
156 auto module =
157 builder.getBlock()->getParent()->getParentOfType<ModuleOp>();
158 builder.setInsertionPointToEnd(module.getBody());
159 auto funcOpResult = LLVM::lookupOrCreateFn(
160 builder, module, name, funcType.getParams(), funcType.getReturnType(),
161 funcType.getVarArg());
162 assert(succeeded(funcOpResult) && "expected to lookup or create printf");
163 funcOp = funcOpResult.value();
164 }
165 return LLVM::CallOp::create(builder, loc, funcOp, args);
166 }
167
168 /// Build a global constant for the given string and construct an 'addressof'
169 /// operation at the current 'builder' insertion point to get a pointer to it.
170 /// Multiple calls with the same string will reuse the same global. It is
171 /// guaranteed that the symbol of the global will be unique.
172 Value buildString(OpBuilder &builder, Location loc, StringRef str) const {
173 auto &global = globals.stringCache[builder.getStringAttr(str)];
174 if (!global) {
175 OpBuilder::InsertionGuard guard(builder);
176 auto module =
177 builder.getBlock()->getParent()->getParentOfType<ModuleOp>();
178 builder.setInsertionPointToEnd(module.getBody());
179 auto arrayTy =
180 LLVM::LLVMArrayType::get(builder.getI8Type(), str.size() + 1);
181 auto strAttr = builder.getStringAttr(str.str() + '\00');
182 global = LLVM::GlobalOp::create(
183 builder, loc, arrayTy, /*isConstant=*/true, LLVM::Linkage::Internal,
184 globals.names.newName("str"), strAttr);
185 }
186 return LLVM::AddressOfOp::create(builder, loc, global);
187 }
188 /// Most API functions require a pointer to the the Z3 context object as the
189 /// first argument. This helper function prepends this pointer value to the
190 /// call for convenience.
191 LLVM::CallOp buildAPICallWithContext(OpBuilder &builder, Location loc,
192 StringRef name, Type returnType,
193 ValueRange args = {}) const {
194 auto ctx = buildContextPtr(builder, loc);
195 SmallVector<Value> arguments;
196 arguments.emplace_back(ctx);
197 arguments.append(SmallVector<Value>(args));
198 return buildCall(
199 builder, loc, name,
200 LLVM::LLVMFunctionType::get(
201 returnType, SmallVector<Type>(ValueRange(arguments).getTypes())),
202 arguments);
203 }
204
205 /// Most API functions we need to call return a 'Z3_AST' object which is a
206 /// pointer in LLVM. This helper function simplifies calling those API
207 /// functions.
208 Value buildPtrAPICall(OpBuilder &builder, Location loc, StringRef name,
209 ValueRange args = {}) const {
210 return buildAPICallWithContext(
211 builder, loc, name,
212 LLVM::LLVMPointerType::get(builder.getContext()), args)
213 ->getResult(0);
214 }
215
216 /// Build a value representing the SMT sort given with 'type'.
217 Value buildSort(OpBuilder &builder, Location loc, Type type) const {
218 // NOTE: if a type not handled by this switch is passed, an assertion will
219 // be triggered.
220 return TypeSwitch<Type, Value>(type)
221 .Case([&](smt::IntType ty) {
222 return buildPtrAPICall(builder, loc, "Z3_mk_int_sort");
223 })
224 .Case([&](smt::BitVectorType ty) {
225 Value bitwidth = LLVM::ConstantOp::create(
226 builder, loc, builder.getI32Type(), ty.getWidth());
227 return buildPtrAPICall(builder, loc, "Z3_mk_bv_sort", {bitwidth});
228 })
229 .Case([&](smt::BoolType ty) {
230 return buildPtrAPICall(builder, loc, "Z3_mk_bool_sort");
231 })
232 .Case([&](smt::SortType ty) {
233 Value str = buildString(builder, loc, ty.getIdentifier());
234 Value sym =
235 buildPtrAPICall(builder, loc, "Z3_mk_string_symbol", {str});
236 return buildPtrAPICall(builder, loc, "Z3_mk_uninterpreted_sort",
237 {sym});
238 })
239 .Case([&](smt::ArrayType ty) {
240 return buildPtrAPICall(builder, loc, "Z3_mk_array_sort",
241 {buildSort(builder, loc, ty.getDomainType()),
242 buildSort(builder, loc, ty.getRangeType())});
243 });
244 }
245
246 SMTGlobalsHandler &globals;
247 const LowerSMTToZ3LLVMOptions &options;
248};
249
250//===----------------------------------------------------------------------===//
251// Lowering Patterns
252//===----------------------------------------------------------------------===//
253
254/// The 'smt.declare_fun' operation is used to declare both constants and
255/// functions. The Z3 API, however, uses two different functions. Therefore,
256/// depending on the result type of this operation, one of the following two
257/// API functions is used to create the symbolic value:
258/// ```
259/// Z3_ast Z3_API Z3_mk_fresh_const(Z3_context c, Z3_string prefix, Z3_sort ty);
260/// Z3_func_decl Z3_API Z3_mk_fresh_func_decl(
261/// Z3_context c, Z3_string prefix, unsigned domain_size,
262/// Z3_sort const domain[], Z3_sort range);
263/// ```
264struct DeclareFunOpLowering : public SMTLoweringPattern<DeclareFunOp> {
265 using SMTLoweringPattern::SMTLoweringPattern;
266
267 LogicalResult
268 matchAndRewrite(DeclareFunOp op, OpAdaptor adaptor,
269 ConversionPatternRewriter &rewriter) const final {
270 Location loc = op.getLoc();
271
272 // Create the name prefix.
273 Value prefix;
274 if (adaptor.getNamePrefix())
275 prefix = buildString(rewriter, loc, *adaptor.getNamePrefix());
276 else
277 prefix = LLVM::ZeroOp::create(rewriter, loc,
278 LLVM::LLVMPointerType::get(getContext()));
279
280 // Handle the constant value case.
281 if (!isa<SMTFuncType>(op.getType())) {
282 Value sort = buildSort(rewriter, loc, op.getType());
283 Value constDecl =
284 buildPtrAPICall(rewriter, loc, "Z3_mk_fresh_const", {prefix, sort});
285 rewriter.replaceOp(op, constDecl);
286 return success();
287 }
288
289 // Otherwise, we declare a function.
290 Type llvmPtrTy = LLVM::LLVMPointerType::get(getContext());
291 auto funcType = cast<SMTFuncType>(op.getResult().getType());
292 Value rangeSort = buildSort(rewriter, loc, funcType.getRangeType());
293
294 Type arrTy =
295 LLVM::LLVMArrayType::get(llvmPtrTy, funcType.getDomainTypes().size());
296
297 Value domain = LLVM::UndefOp::create(rewriter, loc, arrTy);
298 for (auto [i, ty] : llvm::enumerate(funcType.getDomainTypes())) {
299 Value sort = buildSort(rewriter, loc, ty);
300 domain = LLVM::InsertValueOp::create(rewriter, loc, domain, sort, i);
301 }
302
303 Value one =
304 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), 1);
305 Value domainStorage =
306 LLVM::AllocaOp::create(rewriter, loc, llvmPtrTy, arrTy, one);
307 LLVM::StoreOp::create(rewriter, loc, domain, domainStorage);
308
309 Value domainSize = LLVM::ConstantOp::create(
310 rewriter, loc, rewriter.getI32Type(), funcType.getDomainTypes().size());
311 Value decl =
312 buildPtrAPICall(rewriter, loc, "Z3_mk_fresh_func_decl",
313 {prefix, domainSize, domainStorage, rangeSort});
314
315 rewriter.replaceOp(op, decl);
316 return success();
317 }
318};
319
320/// Lower the 'smt.apply_func' operation to Z3 API calls of the form:
321/// ```
322/// Z3_ast Z3_API Z3_mk_app(Z3_context c, Z3_func_decl d,
323/// unsigned num_args, Z3_ast const args[]);
324/// ```
325struct ApplyFuncOpLowering : public SMTLoweringPattern<ApplyFuncOp> {
326 using SMTLoweringPattern::SMTLoweringPattern;
327
328 LogicalResult
329 matchAndRewrite(ApplyFuncOp op, OpAdaptor adaptor,
330 ConversionPatternRewriter &rewriter) const final {
331 Location loc = op.getLoc();
332 Type llvmPtrTy = LLVM::LLVMPointerType::get(getContext());
333 Type arrTy = LLVM::LLVMArrayType::get(llvmPtrTy, adaptor.getArgs().size());
334
335 // Create an array of the function arguments.
336 Value domain = LLVM::UndefOp::create(rewriter, loc, arrTy);
337 for (auto [i, arg] : llvm::enumerate(adaptor.getArgs()))
338 domain = LLVM::InsertValueOp::create(rewriter, loc, domain, arg, i);
339
340 // Store the array on the stack.
341 Value one =
342 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), 1);
343 Value domainStorage =
344 LLVM::AllocaOp::create(rewriter, loc, llvmPtrTy, arrTy, one);
345 LLVM::StoreOp::create(rewriter, loc, domain, domainStorage);
346
347 // Call the API function with a pointer to the function, the number of
348 // arguments, and the pointer to the arguments stored on the stack.
349 Value domainSize = LLVM::ConstantOp::create(
350 rewriter, loc, rewriter.getI32Type(), adaptor.getArgs().size());
351 Value returnVal =
352 buildPtrAPICall(rewriter, loc, "Z3_mk_app",
353 {adaptor.getFunc(), domainSize, domainStorage});
354 rewriter.replaceOp(op, returnVal);
355
356 return success();
357 }
358};
359
360/// Lower the `smt.bv.constant` operation to either
361/// ```
362/// Z3_ast Z3_API Z3_mk_unsigned_int64(Z3_context c, uint64_t v, Z3_sort ty);
363/// ```
364/// if the bit-vector fits into a 64-bit integer or convert it to a string and
365/// use the sligtly slower but arbitrary precision API function:
366/// ```
367/// Z3_ast Z3_API Z3_mk_numeral(Z3_context c, Z3_string numeral, Z3_sort ty);
368/// ```
369/// Note that there is also an API function taking an array of booleans, and
370/// while those are typically compiled to 'i8' in LLVM they don't necessarily
371/// have to (I think).
372struct BVConstantOpLowering : public SMTLoweringPattern<smt::BVConstantOp> {
373 using SMTLoweringPattern::SMTLoweringPattern;
374
375 LogicalResult
376 matchAndRewrite(smt::BVConstantOp op, OpAdaptor adaptor,
377 ConversionPatternRewriter &rewriter) const final {
378 Location loc = op.getLoc();
379 unsigned width = op.getType().getWidth();
380 auto bvSort = buildSort(rewriter, loc, op.getResult().getType());
381 APInt val = adaptor.getValue().getValue();
382
383 if (width <= 64) {
384 Value bvConst = LLVM::ConstantOp::create(
385 rewriter, loc, rewriter.getI64Type(), val.getZExtValue());
386 Value res = buildPtrAPICall(rewriter, loc, "Z3_mk_unsigned_int64",
387 {bvConst, bvSort});
388 rewriter.replaceOp(op, res);
389 return success();
390 }
391
392 std::string str;
393 llvm::raw_string_ostream stream(str);
394 stream << val;
395 Value bvString = buildString(rewriter, loc, str);
396 Value bvNumeral =
397 buildPtrAPICall(rewriter, loc, "Z3_mk_numeral", {bvString, bvSort});
398
399 rewriter.replaceOp(op, bvNumeral);
400 return success();
401 }
402};
403
404/// Some of the Z3 API supports a variadic number of operands for some
405/// operations (in particular if the expansion would lead to a super-linear
406/// increase in operations such as with the ':pairwise' attribute). Those API
407/// calls take an 'unsigned' argument indicating the size of an array of
408/// pointers to the operands.
409template <typename SourceTy>
410struct VariadicSMTPattern : public SMTLoweringPattern<SourceTy> {
411 using OpAdaptor = typename SMTLoweringPattern<SourceTy>::OpAdaptor;
412
413 VariadicSMTPattern(const TypeConverter &typeConverter, MLIRContext *context,
414 SMTGlobalsHandler &globals,
415 const LowerSMTToZ3LLVMOptions &options,
416 StringRef apiFuncName, unsigned minNumArgs)
417 : SMTLoweringPattern<SourceTy>(typeConverter, context, globals, options),
418 apiFuncName(apiFuncName), minNumArgs(minNumArgs) {}
419
420 LogicalResult
421 matchAndRewrite(SourceTy op, OpAdaptor adaptor,
422 ConversionPatternRewriter &rewriter) const final {
423 if (adaptor.getOperands().size() < minNumArgs)
424 return failure();
425
426 Location loc = op.getLoc();
427 Value numOperands = LLVM::ConstantOp::create(
428 rewriter, loc, rewriter.getI32Type(), op->getNumOperands());
429 Value constOne =
430 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), 1);
431 Type ptrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
432 Type arrTy = LLVM::LLVMArrayType::get(ptrTy, op->getNumOperands());
433 Value storage =
434 LLVM::AllocaOp::create(rewriter, loc, ptrTy, arrTy, constOne);
435 Value array = LLVM::UndefOp::create(rewriter, loc, arrTy);
436
437 for (auto [i, operand] : llvm::enumerate(adaptor.getOperands()))
438 array = LLVM::InsertValueOp::create(rewriter, loc, array, operand,
439 ArrayRef<int64_t>{(int64_t)i});
440
441 LLVM::StoreOp::create(rewriter, loc, array, storage);
442
443 rewriter.replaceOp(op,
444 SMTLoweringPattern<SourceTy>::buildPtrAPICall(
445 rewriter, loc, apiFuncName, {numOperands, storage}));
446 return success();
447 }
448
449private:
450 StringRef apiFuncName;
451 unsigned minNumArgs;
452};
453
454/// Lower an SMT operation to a function call with the name 'apiFuncName' with
455/// arguments matching the operands one-to-one.
456template <typename SourceTy>
457struct OneToOneSMTPattern : public SMTLoweringPattern<SourceTy> {
458 using OpAdaptor = typename SMTLoweringPattern<SourceTy>::OpAdaptor;
459
460 OneToOneSMTPattern(const TypeConverter &typeConverter, MLIRContext *context,
461 SMTGlobalsHandler &globals,
462 const LowerSMTToZ3LLVMOptions &options,
463 StringRef apiFuncName, unsigned numOperands)
464 : SMTLoweringPattern<SourceTy>(typeConverter, context, globals, options),
465 apiFuncName(apiFuncName), numOperands(numOperands) {}
466
467 LogicalResult
468 matchAndRewrite(SourceTy op, OpAdaptor adaptor,
469 ConversionPatternRewriter &rewriter) const final {
470 if (adaptor.getOperands().size() != numOperands)
471 return failure();
472
473 rewriter.replaceOp(
474 op, SMTLoweringPattern<SourceTy>::buildPtrAPICall(
475 rewriter, op.getLoc(), apiFuncName, adaptor.getOperands()));
476 return success();
477 }
478
479private:
480 StringRef apiFuncName;
481 unsigned numOperands;
482};
483
484/// A pattern to lower SMT operations with a variadic number of operands
485/// modelling the ':chainable' attribute in SMT to binary operations.
486template <typename SourceTy>
487class LowerChainableSMTPattern : public SMTLoweringPattern<SourceTy> {
488 using SMTLoweringPattern<SourceTy>::SMTLoweringPattern;
489 using OpAdaptor = typename SMTLoweringPattern<SourceTy>::OpAdaptor;
490
491 LogicalResult
492 matchAndRewrite(SourceTy op, OpAdaptor adaptor,
493 ConversionPatternRewriter &rewriter) const final {
494 if (adaptor.getOperands().size() <= 2)
495 return failure();
496
497 Location loc = op.getLoc();
498 SmallVector<Value> elements;
499 for (int i = 1, e = adaptor.getOperands().size(); i < e; ++i) {
500 Value val = SourceTy::create(
501 rewriter, loc, op->getResultTypes(),
502 ValueRange{adaptor.getOperands()[i - 1], adaptor.getOperands()[i]});
503 elements.push_back(val);
504 }
505 rewriter.replaceOpWithNewOp<smt::AndOp>(op, elements);
506 return success();
507 }
508};
509
510/// A pattern to lower SMT operations with a variadic number of operands
511/// modelling the `:left-assoc` attribute to a sequence of binary operators.
512template <typename SourceTy>
513class LowerLeftAssocSMTPattern : public SMTLoweringPattern<SourceTy> {
514 using SMTLoweringPattern<SourceTy>::SMTLoweringPattern;
515 using OpAdaptor = typename SMTLoweringPattern<SourceTy>::OpAdaptor;
516
517 LogicalResult
518 matchAndRewrite(SourceTy op, OpAdaptor adaptor,
519 ConversionPatternRewriter &rewriter) const final {
520 if (adaptor.getOperands().size() <= 2)
521 return rewriter.notifyMatchFailure(op, "must have at least two operands");
522
523 Value runner = adaptor.getOperands()[0];
524 for (Value val : adaptor.getOperands().drop_front())
525 runner = SourceTy::create(rewriter, op.getLoc(), op->getResultTypes(),
526 ValueRange{runner, val});
527
528 rewriter.replaceOp(op, runner);
529 return success();
530 }
531};
532
533/// The 'smt.solver' operation has a region that corresponds to the lifetime of
534/// the Z3 context and one solver instance created within this context.
535/// To create a context, a Z3 configuration has to be built first and various
536/// configuration parameters can be set before creating a context from it. Once
537/// we have a context, we can create a solver and store a pointer to the context
538/// and the solver in an LLVM global such that operations in the child region
539/// have access to them. While the context created with `Z3_mk_context` takes
540/// care of the reference counting of `Z3_AST` objects, it still requires manual
541/// reference counting of `Z3_solver` objects, therefore, we need to increase
542/// the ref. counter of the solver we get from `Z3_mk_solver` and must decrease
543/// it again once we don't need it anymore. Finally, the configuration object
544/// can be deleted.
545/// ```
546/// Z3_config Z3_API Z3_mk_config(void);
547/// void Z3_API Z3_set_param_value(Z3_config c, Z3_string param_id,
548/// Z3_string param_value);
549/// Z3_context Z3_API Z3_mk_context(Z3_config c);
550/// Z3_solver Z3_API Z3_mk_solver(Z3_context c);
551/// void Z3_API Z3_solver_inc_ref(Z3_context c, Z3_solver s);
552/// void Z3_API Z3_del_config(Z3_config c);
553/// ```
554/// At the end of the solver lifetime, we have to tell the context that we
555/// don't need the solver anymore and delete the context itself.
556/// ```
557/// void Z3_API Z3_solver_dec_ref(Z3_context c, Z3_solver s);
558/// void Z3_API Z3_del_context(Z3_context c);
559/// ```
560/// Note that the solver created here is a combined solver. There might be some
561/// potential for optimization by creating more specialized solvers supported by
562/// the Z3 API according the the kind of operations present in the body region.
563struct SolverOpLowering : public SMTLoweringPattern<SolverOp> {
564 using SMTLoweringPattern::SMTLoweringPattern;
565
566 LogicalResult
567 matchAndRewrite(SolverOp op, OpAdaptor adaptor,
568 ConversionPatternRewriter &rewriter) const final {
569 Location loc = op.getLoc();
570 auto ptrTy = LLVM::LLVMPointerType::get(getContext());
571 auto voidTy = LLVM::LLVMVoidType::get(getContext());
572 auto ptrToPtrFunc = LLVM::LLVMFunctionType::get(ptrTy, ptrTy);
573 auto ptrPtrToPtrFunc = LLVM::LLVMFunctionType::get(ptrTy, {ptrTy, ptrTy});
574 auto ptrToVoidFunc = LLVM::LLVMFunctionType::get(voidTy, ptrTy);
575 auto ptrPtrToVoidFunc = LLVM::LLVMFunctionType::get(voidTy, {ptrTy, ptrTy});
576
577 // Create the configuration.
578 Value config = buildCall(rewriter, loc, "Z3_mk_config",
579 LLVM::LLVMFunctionType::get(ptrTy, {}), {})
580 .getResult();
581
582 // In debug-mode, we enable proofs such that we can fetch one in the 'unsat'
583 // region of each 'smt.check' operation.
584 if (options.debug) {
585 Value paramKey = buildString(rewriter, loc, "proof");
586 Value paramValue = buildString(rewriter, loc, "true");
587 buildCall(rewriter, loc, "Z3_set_param_value",
588 LLVM::LLVMFunctionType::get(voidTy, {ptrTy, ptrTy, ptrTy}),
589 {config, paramKey, paramValue});
590 }
591
592 // Check if the logic is set anywhere within the solver
593 std::optional<StringRef> logic = std::nullopt;
594 auto setLogicOps = op.getBodyRegion().getOps<smt::SetLogicOp>();
595 if (!setLogicOps.empty()) {
596 // We know from before patterns were applied that there is only one
597 // set_logic op
598 auto setLogicOp = *setLogicOps.begin();
599 logic = setLogicOp.getLogic();
600 rewriter.eraseOp(setLogicOp);
601 }
602
603 // Create the context and store a pointer to it in the global variable.
604 Value ctx = buildCall(rewriter, loc, "Z3_mk_context", ptrToPtrFunc, config)
605 .getResult();
606 Value ctxAddr =
607 LLVM::AddressOfOp::create(rewriter, loc, globals.ctx).getResult();
608 LLVM::StoreOp::create(rewriter, loc, ctx, ctxAddr);
609
610 // Delete the configuration again.
611 buildCall(rewriter, loc, "Z3_del_config", ptrToVoidFunc, {config});
612
613 // Create a solver instance, increase its reference counter, and store a
614 // pointer to it in the global variable.
615 Value solver;
616 if (logic) {
617 auto logicStr = buildString(rewriter, loc, logic.value());
618 solver = buildCall(rewriter, loc, "Z3_mk_solver_for_logic",
619 ptrPtrToPtrFunc, {ctx, logicStr})
620 ->getResult(0);
621 } else {
622 solver = buildCall(rewriter, loc, "Z3_mk_solver", ptrToPtrFunc, ctx)
623 ->getResult(0);
624 }
625 buildCall(rewriter, loc, "Z3_solver_inc_ref", ptrPtrToVoidFunc,
626 {ctx, solver});
627 Value solverAddr =
628 LLVM::AddressOfOp::create(rewriter, loc, globals.solver).getResult();
629 LLVM::StoreOp::create(rewriter, loc, solver, solverAddr);
630
631 // This assumes that no constant hoisting of the like happens inbetween
632 // the patterns defined in this pass because once the solver initialization
633 // and deallocation calls are inserted and the body region is inlined,
634 // canonicalizations and folders applied inbetween lowering patterns might
635 // hoist the SMT constants which means they would access uninitialized
636 // global variables once they are lowered.
637 SmallVector<Type> convertedTypes;
638 if (failed(
639 typeConverter->convertTypes(op->getResultTypes(), convertedTypes)))
640 return failure();
641
642 // Solver regions are outlined below. If one contains a trace marker,
643 // forward the enclosing function's trace context through the outlined
644 // function as its final argument.
645 bool containsBMCTrace = false;
646 op.getBodyRegion().walk(
647 [&](verif::BMCTraceOp) { containsBMCTrace = true; });
648 Value traceContext;
649 SmallVector<Type> inputTypes(adaptor.getInputs().getTypes());
650 SmallVector<Value> callOperands(adaptor.getInputs());
651 if (containsBMCTrace) {
652 auto parentFunction = op->getParentOfType<FunctionOpInterface>();
653 if (!parentFunction || parentFunction.getNumArguments() == 0)
654 return rewriter.notifyMatchFailure(op, "missing BMC trace context");
655 traceContext =
656 parentFunction.getArgument(parentFunction.getNumArguments() - 1);
657 if (!isa<LLVM::LLVMPointerType>(traceContext.getType()))
658 return rewriter.notifyMatchFailure(op,
659 "invalid BMC trace context type");
660 inputTypes.push_back(traceContext.getType());
661 callOperands.push_back(traceContext);
662 op.getBodyRegion().addArgument(traceContext.getType(), loc);
663 }
664
665 func::FuncOp funcOp;
666 {
667 OpBuilder::InsertionGuard guard(rewriter);
668 auto module = op->getParentOfType<ModuleOp>();
669 rewriter.setInsertionPointToEnd(module.getBody());
670
671 funcOp = func::FuncOp::create(
672 rewriter, loc, globals.names.newName("solver"),
673 rewriter.getFunctionType(inputTypes, convertedTypes));
674 rewriter.inlineRegionBefore(op.getBodyRegion(), funcOp.getBody(),
675 funcOp.end());
676 }
677
678 ValueRange results =
679 func::CallOp::create(rewriter, loc, funcOp, callOperands)->getResults();
680
681 // At the end of the region, decrease the solver's reference counter and
682 // delete the context.
683 // NOTE: we cannot use the convenience helper here because we don't want to
684 // load the context from the global but use the result from the 'mk_context'
685 // call directly for two reasons:
686 // * avoid an unnecessary load
687 // * the caching mechanism of the context does not work here because it
688 // would reuse the loaded context from a earlier solver
689 buildCall(rewriter, loc, "Z3_solver_dec_ref", ptrPtrToVoidFunc,
690 {ctx, solver});
691 buildCall(rewriter, loc, "Z3_del_context", ptrToVoidFunc, ctx);
692
693 rewriter.replaceOp(op, results);
694 return success();
695 }
696};
697
698/// Lower `smt.assert` operations to Z3 API calls of the form:
699/// ```
700/// void Z3_API Z3_solver_assert(Z3_context c, Z3_solver s, Z3_ast a);
701/// ```
702struct AssertOpLowering : public SMTLoweringPattern<AssertOp> {
703 using SMTLoweringPattern::SMTLoweringPattern;
704
705 LogicalResult
706 matchAndRewrite(AssertOp op, OpAdaptor adaptor,
707 ConversionPatternRewriter &rewriter) const final {
708 Location loc = op.getLoc();
709 buildAPICallWithContext(
710 rewriter, loc, "Z3_solver_assert",
711 LLVM::LLVMVoidType::get(getContext()),
712 {buildSolverPtr(rewriter, loc), adaptor.getInput()});
713
714 rewriter.eraseOp(op);
715 return success();
716 }
717};
718
719/// Lower `smt.reset` operations to Z3 API calls of the form:
720/// ```
721/// void Z3_API Z3_solver_reset(Z3_context c, Z3_solver s);
722/// ```
723struct ResetOpLowering : public SMTLoweringPattern<ResetOp> {
724 using SMTLoweringPattern::SMTLoweringPattern;
725
726 LogicalResult
727 matchAndRewrite(ResetOp op, OpAdaptor adaptor,
728 ConversionPatternRewriter &rewriter) const final {
729 Location loc = op.getLoc();
730 buildAPICallWithContext(rewriter, loc, "Z3_solver_reset",
731 LLVM::LLVMVoidType::get(getContext()),
732 {buildSolverPtr(rewriter, loc)});
733
734 rewriter.eraseOp(op);
735 return success();
736 }
737};
738
739/// Lower `smt.push` operations to (repeated) Z3 API calls of the form:
740/// ```
741/// void Z3_API Z3_solver_push(Z3_context c, Z3_solver s);
742/// ```
743struct PushOpLowering : public SMTLoweringPattern<PushOp> {
744 using SMTLoweringPattern::SMTLoweringPattern;
745 LogicalResult
746 matchAndRewrite(PushOp op, OpAdaptor adaptor,
747 ConversionPatternRewriter &rewriter) const final {
748 Location loc = op.getLoc();
749 // SMTLIB allows multiple levels to be pushed with one push command, but the
750 // Z3 C API doesn't let you provide a number of levels for push calls so
751 // multiple calls have to be created.
752 for (uint32_t i = 0; i < op.getCount(); i++)
753 buildAPICallWithContext(rewriter, loc, "Z3_solver_push",
754 LLVM::LLVMVoidType::get(getContext()),
755 {buildSolverPtr(rewriter, loc)});
756 rewriter.eraseOp(op);
757 return success();
758 }
759};
760
761/// Lower `smt.pop` operations to Z3 API calls of the form:
762/// ```
763/// void Z3_API Z3_solver_pop(Z3_context c, Z3_solver s, unsigned n);
764/// ```
765struct PopOpLowering : public SMTLoweringPattern<PopOp> {
766 using SMTLoweringPattern::SMTLoweringPattern;
767 LogicalResult
768 matchAndRewrite(PopOp op, OpAdaptor adaptor,
769 ConversionPatternRewriter &rewriter) const final {
770 Location loc = op.getLoc();
771 Value constVal = LLVM::ConstantOp::create(
772 rewriter, loc, rewriter.getI32Type(), op.getCount());
773 buildAPICallWithContext(rewriter, loc, "Z3_solver_pop",
774 LLVM::LLVMVoidType::get(getContext()),
775 {buildSolverPtr(rewriter, loc), constVal});
776 rewriter.eraseOp(op);
777 return success();
778 }
779};
780
781/// Lower `smt.yield` operations to `scf.yield` operations. This not necessary
782/// for the yield in `smt.solver` or in quantifiers since they are deleted
783/// directly by the parent operation, but makes the lowering of the `smt.check`
784/// operation simpler and more convenient since the regions get translated
785/// directly to regions of `scf.if` operations.
786struct YieldOpLowering : public SMTLoweringPattern<YieldOp> {
787 using SMTLoweringPattern::SMTLoweringPattern;
788
789 LogicalResult
790 matchAndRewrite(YieldOp op, OpAdaptor adaptor,
791 ConversionPatternRewriter &rewriter) const final {
792 if (op->getParentOfType<func::FuncOp>()) {
793 rewriter.replaceOpWithNewOp<func::ReturnOp>(op, adaptor.getValues());
794 return success();
795 }
796 if (op->getParentOfType<LLVM::LLVMFuncOp>()) {
797 rewriter.replaceOpWithNewOp<LLVM::ReturnOp>(op, adaptor.getValues());
798 return success();
799 }
800 if (isa_and_nonnull<scf::SCFDialect>(op->getParentOp()->getDialect())) {
801 rewriter.replaceOpWithNewOp<scf::YieldOp>(op, adaptor.getValues());
802 return success();
803 }
804 return failure();
805 }
806};
807
808/// Lower `smt.check` operations to Z3 API calls and control-flow operations.
809/// ```
810/// Z3_lbool Z3_API Z3_solver_check(Z3_context c, Z3_solver s);
811///
812/// typedef enum
813/// {
814/// Z3_L_FALSE = -1, // means unsatisfiable here
815/// Z3_L_UNDEF, // means unknown here
816/// Z3_L_TRUE // means satisfiable here
817/// } Z3_lbool;
818/// ```
819struct CheckOpLowering : public SMTLoweringPattern<CheckOp> {
820 using SMTLoweringPattern::SMTLoweringPattern;
821
822 LogicalResult
823 matchAndRewrite(CheckOp op, OpAdaptor adaptor,
824 ConversionPatternRewriter &rewriter) const final {
825 Location loc = op.getLoc();
826 auto ptrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
827 auto printfType = LLVM::LLVMFunctionType::get(
828 LLVM::LLVMVoidType::get(rewriter.getContext()), {ptrTy}, true);
829
830 auto getHeaderString = [](const std::string &title) {
831 unsigned titleSize = title.size() + 2; // Add a space left and right
832 return std::string((80 - titleSize) / 2, '-') + " " + title + " " +
833 std::string((80 - titleSize + 1) / 2, '-') + "\n%s\n" +
834 std::string(80, '-') + "\n";
835 };
836
837 // Get the pointer to the solver instance.
838 Value solver = buildSolverPtr(rewriter, loc);
839
840 // In debug-mode, print the state of the solver before calling 'check-sat'
841 // on it. This prints the asserted SMT expressions.
842 if (options.debug) {
843 auto solverStringPtr =
844 buildPtrAPICall(rewriter, loc, "Z3_solver_to_string", {solver});
845 auto solverFormatString =
846 buildString(rewriter, loc, getHeaderString("Solver"));
847 buildCall(rewriter, op.getLoc(), "printf", printfType,
848 {solverFormatString, solverStringPtr});
849 }
850
851 // Convert the result types of the `smt.check` operation.
852 SmallVector<Type> resultTypes;
853 if (failed(typeConverter->convertTypes(op->getResultTypes(), resultTypes)))
854 return failure();
855
856 // Call 'check-sat' and check if the assertions are satisfiable.
857 Value checkResult =
858 buildAPICallWithContext(rewriter, loc, "Z3_solver_check",
859 rewriter.getI32Type(), {solver})
860 ->getResult(0);
861 Value constOne =
862 LLVM::ConstantOp::create(rewriter, loc, checkResult.getType(), 1);
863 Value isSat = LLVM::ICmpOp::create(rewriter, loc, LLVM::ICmpPredicate::eq,
864 checkResult, constOne);
865
866 // Simply inline the 'sat' region into the 'then' region of the 'scf.if'
867 auto satIfOp = scf::IfOp::create(rewriter, loc, resultTypes, isSat);
868 rewriter.inlineRegionBefore(op.getSatRegion(), satIfOp.getThenRegion(),
869 satIfOp.getThenRegion().end());
870
871 // Otherwise, the 'else' block checks if the assertions are unsatisfiable or
872 // unknown. The corresponding regions can also be simply inlined into the
873 // two branches of this nested if-statement as well.
874 rewriter.createBlock(&satIfOp.getElseRegion());
875 Value constNegOne =
876 LLVM::ConstantOp::create(rewriter, loc, checkResult.getType(), -1);
877 Value isUnsat = LLVM::ICmpOp::create(rewriter, loc, LLVM::ICmpPredicate::eq,
878 checkResult, constNegOne);
879 auto unsatIfOp = scf::IfOp::create(rewriter, loc, resultTypes, isUnsat);
880 scf::YieldOp::create(rewriter, loc, unsatIfOp->getResults());
881
882 rewriter.inlineRegionBefore(op.getUnsatRegion(), unsatIfOp.getThenRegion(),
883 unsatIfOp.getThenRegion().end());
884 rewriter.inlineRegionBefore(op.getUnknownRegion(),
885 unsatIfOp.getElseRegion(),
886 unsatIfOp.getElseRegion().end());
887
888 rewriter.replaceOp(op, satIfOp->getResults());
889
890 if (options.debug) {
891 // In debug-mode, if the assertions are unsatisfiable we can print the
892 // proof.
893 rewriter.setInsertionPointToStart(unsatIfOp.thenBlock());
894 auto proof = buildPtrAPICall(rewriter, op.getLoc(), "Z3_solver_get_proof",
895 {solver});
896 auto stringPtr =
897 buildPtrAPICall(rewriter, op.getLoc(), "Z3_ast_to_string", {proof});
898 auto formatString =
899 buildString(rewriter, op.getLoc(), getHeaderString("Proof"));
900 buildCall(rewriter, op.getLoc(), "printf", printfType,
901 {formatString, stringPtr});
902
903 // In debug mode, if the assertions are satisfiable we can print the model
904 // (effectively a counter-example).
905 rewriter.setInsertionPointToStart(satIfOp.thenBlock());
906 auto model = buildPtrAPICall(rewriter, op.getLoc(), "Z3_solver_get_model",
907 {solver});
908 auto modelStringPtr =
909 buildPtrAPICall(rewriter, op.getLoc(), "Z3_model_to_string", {model});
910 auto modelFormatString =
911 buildString(rewriter, op.getLoc(), getHeaderString("Model"));
912 buildCall(rewriter, op.getLoc(), "printf", printfType,
913 {modelFormatString, modelStringPtr});
914 }
915
916 return success();
917 }
918};
919
920/// Lower `smt.forall` and `smt.exists` operations to the following Z3 API call.
921/// ```
922/// Z3_ast Z3_API Z3_mk_{forall|exists}_const(
923/// Z3_context c,
924/// unsigned weight,
925/// unsigned num_bound,
926/// Z3_app const bound[],
927/// unsigned num_patterns,
928/// Z3_pattern const patterns[],
929/// Z3_ast body
930/// );
931/// ```
932/// All nested regions are inlined into the parent region and the block
933/// arguments are replaced with new `smt.declare_fun` constants that are also
934/// passed to the `bound` argument of above API function. Patterns are created
935/// with the following API function.
936/// ```
937/// Z3_pattern Z3_API Z3_mk_pattern(Z3_context c, unsigned num_patterns,
938/// Z3_ast const terms[]);
939/// ```
940/// Where each operand of the `smt.yield` in a pattern region is a 'term'.
941template <typename QuantifierOp>
942struct QuantifierLowering : public SMTLoweringPattern<QuantifierOp> {
943 using SMTLoweringPattern<QuantifierOp>::SMTLoweringPattern;
944 using SMTLoweringPattern<QuantifierOp>::typeConverter;
945 using SMTLoweringPattern<QuantifierOp>::buildPtrAPICall;
946 using OpAdaptor = typename QuantifierOp::Adaptor;
947
948 Value createStorageForValueList(ValueRange values, Location loc,
949 ConversionPatternRewriter &rewriter) const {
950 Type ptrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
951 Type arrTy = LLVM::LLVMArrayType::get(ptrTy, values.size());
952 Value constOne =
953 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), 1);
954 Value storage =
955 LLVM::AllocaOp::create(rewriter, loc, ptrTy, arrTy, constOne);
956 Value array = LLVM::UndefOp::create(rewriter, loc, arrTy);
957
958 for (auto [i, val] : llvm::enumerate(values))
959 array = LLVM::InsertValueOp::create(rewriter, loc, array, val,
960 ArrayRef<int64_t>(i));
961
962 LLVM::StoreOp::create(rewriter, loc, array, storage);
963
964 return storage;
965 }
966
967 LogicalResult
968 matchAndRewrite(QuantifierOp op, OpAdaptor adaptor,
969 ConversionPatternRewriter &rewriter) const final {
970 Location loc = op.getLoc();
971 Type ptrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
972
973 // no-pattern attribute not supported yet because the Z3 CAPI allows more
974 // fine-grained control where a list of patterns to be banned can be given.
975 // This means, the no-pattern attribute is equivalent to providing a list of
976 // all possible sub-expressions in the quantifier body to the CAPI.
977 if (adaptor.getNoPattern())
978 return rewriter.notifyMatchFailure(
979 op, "no-pattern attribute not yet supported!");
980
981 rewriter.setInsertionPoint(op);
982
983 // Weight attribute
984 Value weight = LLVM::ConstantOp::create(
985 rewriter, loc, rewriter.getI32Type(), adaptor.getWeight());
986
987 // Bound variables
988 unsigned numDecls = op.getBody().getNumArguments();
989 Value numDeclsVal = LLVM::ConstantOp::create(
990 rewriter, loc, rewriter.getI32Type(), numDecls);
991
992 // We replace the block arguments with constant symbolic values and inform
993 // the quantifier API call which constants it should treat as bound
994 // variables. We also need to make sure that we use the exact same SSA
995 // values in the pattern regions since we lower constant declaration
996 // operation to always produce fresh constants.
997 SmallVector<Value> repl;
998 for (auto [i, arg] : llvm::enumerate(op.getBody().getArguments())) {
999 Value newArg;
1000 if (adaptor.getBoundVarNames().has_value())
1001 newArg = smt::DeclareFunOp::create(
1002 rewriter, loc, arg.getType(),
1003 cast<StringAttr>((*adaptor.getBoundVarNames())[i]));
1004 else
1005 newArg = smt::DeclareFunOp::create(rewriter, loc, arg.getType());
1006 repl.push_back(typeConverter->materializeTargetConversion(
1007 rewriter, loc, typeConverter->convertType(arg.getType()), newArg));
1008 }
1009
1010 Value boundStorage = createStorageForValueList(repl, loc, rewriter);
1011
1012 // Body Expression
1013 auto yieldOp = cast<smt::YieldOp>(op.getBody().front().getTerminator());
1014 Value bodyExp = yieldOp.getValues()[0];
1015 rewriter.setInsertionPointAfterValue(bodyExp);
1016 bodyExp = typeConverter->materializeTargetConversion(
1017 rewriter, loc, typeConverter->convertType(bodyExp.getType()), bodyExp);
1018 rewriter.eraseOp(yieldOp);
1019
1020 rewriter.inlineBlockBefore(&op.getBody().front(), op, repl);
1021 rewriter.setInsertionPoint(op);
1022
1023 // Patterns
1024 unsigned numPatterns = adaptor.getPatterns().size();
1025 Value numPatternsVal = LLVM::ConstantOp::create(
1026 rewriter, loc, rewriter.getI32Type(), numPatterns);
1027
1028 Value patternStorage;
1029 if (numPatterns > 0) {
1030 SmallVector<Value> patterns;
1031 for (Region *patternRegion : adaptor.getPatterns()) {
1032 auto yieldOp =
1033 cast<smt::YieldOp>(patternRegion->front().getTerminator());
1034 auto patternTerms = yieldOp.getOperands();
1035
1036 rewriter.setInsertionPoint(yieldOp);
1037 SmallVector<Value> patternList;
1038 for (auto val : patternTerms)
1039 patternList.push_back(typeConverter->materializeTargetConversion(
1040 rewriter, loc, typeConverter->convertType(val.getType()), val));
1041
1042 rewriter.eraseOp(yieldOp);
1043 rewriter.inlineBlockBefore(&patternRegion->front(), op, repl);
1044
1045 rewriter.setInsertionPoint(op);
1046 Value numTerms = LLVM::ConstantOp::create(
1047 rewriter, loc, rewriter.getI32Type(), patternTerms.size());
1048 Value patternTermStorage =
1049 createStorageForValueList(patternList, loc, rewriter);
1050 Value pattern = buildPtrAPICall(rewriter, loc, "Z3_mk_pattern",
1051 {numTerms, patternTermStorage});
1052
1053 patterns.emplace_back(pattern);
1054 }
1055 patternStorage = createStorageForValueList(patterns, loc, rewriter);
1056 } else {
1057 // If we set the num_patterns parameter to 0, we can just pass a nullptr
1058 // as storage.
1059 patternStorage = LLVM::ZeroOp::create(rewriter, loc, ptrTy);
1060 }
1061
1062 StringRef apiCallName = "Z3_mk_forall_const";
1063 if (std::is_same_v<QuantifierOp, ExistsOp>)
1064 apiCallName = "Z3_mk_exists_const";
1065 Value quantifierExp =
1066 buildPtrAPICall(rewriter, loc, apiCallName,
1067 {weight, numDeclsVal, boundStorage, numPatternsVal,
1068 patternStorage, bodyExp});
1069
1070 rewriter.replaceOp(op, quantifierExp);
1071 return success();
1072 }
1073};
1074
1075/// Lower `smt.bv.repeat` operations to Z3 API function calls of the form
1076/// ```
1077/// Z3_ast Z3_API Z3_mk_repeat(Z3_context c, unsigned i, Z3_ast t1);
1078/// ```
1079struct RepeatOpLowering : public SMTLoweringPattern<RepeatOp> {
1080 using SMTLoweringPattern::SMTLoweringPattern;
1081
1082 LogicalResult
1083 matchAndRewrite(RepeatOp op, OpAdaptor adaptor,
1084 ConversionPatternRewriter &rewriter) const final {
1085 Value count = LLVM::ConstantOp::create(
1086 rewriter, op.getLoc(), rewriter.getI32Type(), op.getCount());
1087 rewriter.replaceOp(op,
1088 buildPtrAPICall(rewriter, op.getLoc(), "Z3_mk_repeat",
1089 {count, adaptor.getInput()}));
1090 return success();
1091 }
1092};
1093
1094/// Lower `smt.bv.extract` operations to Z3 API function calls of the following
1095/// form, where the output bit-vector has size `n = high - low + 1`. This means,
1096/// both the 'high' and 'low' indices are inclusive.
1097/// ```
1098/// Z3_ast Z3_API Z3_mk_extract(Z3_context c, unsigned high, unsigned low,
1099/// Z3_ast t1);
1100/// ```
1101struct ExtractOpLowering : public SMTLoweringPattern<ExtractOp> {
1102 using SMTLoweringPattern::SMTLoweringPattern;
1103
1104 LogicalResult
1105 matchAndRewrite(ExtractOp op, OpAdaptor adaptor,
1106 ConversionPatternRewriter &rewriter) const final {
1107 Location loc = op.getLoc();
1108 Value low = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1109 adaptor.getLowBit());
1110 Value high = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1111 adaptor.getLowBit() +
1112 op.getType().getWidth() - 1);
1113 rewriter.replaceOp(op, buildPtrAPICall(rewriter, loc, "Z3_mk_extract",
1114 {high, low, adaptor.getInput()}));
1115 return success();
1116 }
1117};
1118
1119/// Lower `smt.array.broadcast` operations to Z3 API function calls of the form
1120/// ```
1121/// Z3_ast Z3_API Z3_mk_const_array(Z3_context c, Z3_sort domain, Z3_ast v);
1122/// ```
1123struct ArrayBroadcastOpLowering
1124 : public SMTLoweringPattern<smt::ArrayBroadcastOp> {
1125 using SMTLoweringPattern::SMTLoweringPattern;
1126
1127 LogicalResult
1128 matchAndRewrite(smt::ArrayBroadcastOp op, OpAdaptor adaptor,
1129 ConversionPatternRewriter &rewriter) const final {
1130 auto domainSort = buildSort(
1131 rewriter, op.getLoc(),
1132 cast<smt::ArrayType>(op.getResult().getType()).getDomainType());
1133
1134 rewriter.replaceOp(op, buildPtrAPICall(rewriter, op.getLoc(),
1135 "Z3_mk_const_array",
1136 {domainSort, adaptor.getValue()}));
1137 return success();
1138 }
1139};
1140
1141/// Lower the `smt.constant` operation to one of the following Z3 API function
1142/// calls depending on the value of the boolean attribute.
1143/// ```
1144/// Z3_ast Z3_API Z3_mk_true(Z3_context c);
1145/// Z3_ast Z3_API Z3_mk_false(Z3_context c);
1146/// ```
1147struct BoolConstantOpLowering : public SMTLoweringPattern<smt::BoolConstantOp> {
1148 using SMTLoweringPattern::SMTLoweringPattern;
1149
1150 LogicalResult
1151 matchAndRewrite(smt::BoolConstantOp op, OpAdaptor adaptor,
1152 ConversionPatternRewriter &rewriter) const final {
1153 rewriter.replaceOp(
1154 op, buildPtrAPICall(rewriter, op.getLoc(),
1155 adaptor.getValue() ? "Z3_mk_true" : "Z3_mk_false"));
1156 return success();
1157 }
1158};
1159
1160/// Lower `smt.int.constant` operations to one of the following two Z3 API
1161/// function calls depending on whether the storage APInt has a bit-width that
1162/// fits in a `uint64_t`.
1163/// ```
1164/// Z3_sort Z3_API Z3_mk_int_sort(Z3_context c);
1165///
1166/// Z3_ast Z3_API Z3_mk_int64(Z3_context c, int64_t v, Z3_sort ty);
1167///
1168/// Z3_ast Z3_API Z3_mk_numeral(Z3_context c, Z3_string numeral, Z3_sort ty);
1169/// Z3_ast Z3_API Z3_mk_unary_minus(Z3_context c, Z3_ast arg);
1170/// ```
1171struct IntConstantOpLowering : public SMTLoweringPattern<smt::IntConstantOp> {
1172 using SMTLoweringPattern::SMTLoweringPattern;
1173
1174 LogicalResult
1175 matchAndRewrite(smt::IntConstantOp op, OpAdaptor adaptor,
1176 ConversionPatternRewriter &rewriter) const final {
1177 Location loc = op.getLoc();
1178 Value type = buildPtrAPICall(rewriter, loc, "Z3_mk_int_sort");
1179 if (adaptor.getValue().getBitWidth() <= 64) {
1180 Value val = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI64Type(),
1181 adaptor.getValue().getSExtValue());
1182 rewriter.replaceOp(
1183 op, buildPtrAPICall(rewriter, loc, "Z3_mk_int64", {val, type}));
1184 return success();
1185 }
1186
1187 std::string numeralStr;
1188 llvm::raw_string_ostream stream(numeralStr);
1189 stream << adaptor.getValue().abs();
1190
1191 Value numeral = buildString(rewriter, loc, numeralStr);
1192 Value intNumeral =
1193 buildPtrAPICall(rewriter, loc, "Z3_mk_numeral", {numeral, type});
1194
1195 if (adaptor.getValue().isNegative())
1196 intNumeral =
1197 buildPtrAPICall(rewriter, loc, "Z3_mk_unary_minus", intNumeral);
1198
1199 rewriter.replaceOp(op, intNumeral);
1200 return success();
1201 }
1202};
1203
1204/// Lower `smt.int.cmp` operations to one of the following Z3 API function calls
1205/// depending on the predicate.
1206/// ```
1207/// Z3_ast Z3_API Z3_mk_{{pred}}(Z3_context c, Z3_ast t1, Z3_ast t2);
1208/// ```
1209struct IntCmpOpLowering : public SMTLoweringPattern<IntCmpOp> {
1210 using SMTLoweringPattern::SMTLoweringPattern;
1211
1212 LogicalResult
1213 matchAndRewrite(IntCmpOp op, OpAdaptor adaptor,
1214 ConversionPatternRewriter &rewriter) const final {
1215 rewriter.replaceOp(
1216 op,
1217 buildPtrAPICall(rewriter, op.getLoc(),
1218 "Z3_mk_" + stringifyIntPredicate(op.getPred()).str(),
1219 {adaptor.getLhs(), adaptor.getRhs()}));
1220 return success();
1221 }
1222};
1223
1224/// Lower `smt.int2bv` operations to the following Z3 API function calls.
1225/// ```
1226/// Z3_ast Z3_API Z3_mk_int2bv(Z3_context c, unsigned n, Z3_ast t1);
1227/// ```
1228struct Int2BVOpLowering : public SMTLoweringPattern<Int2BVOp> {
1229 using SMTLoweringPattern::SMTLoweringPattern;
1230
1231 LogicalResult
1232 matchAndRewrite(Int2BVOp op, OpAdaptor adaptor,
1233 ConversionPatternRewriter &rewriter) const final {
1234 Value widthConst =
1235 LLVM::ConstantOp::create(rewriter, op->getLoc(), rewriter.getI32Type(),
1236 op.getResult().getType().getWidth());
1237 rewriter.replaceOp(op,
1238 buildPtrAPICall(rewriter, op.getLoc(), "Z3_mk_int2bv",
1239 {widthConst, adaptor.getInput()}));
1240 return success();
1241 }
1242};
1243
1244/// Lower `smt.bv2int` operations to the following Z3 API function call.
1245/// ```
1246/// Z3_ast Z3_API Z3_mk_bv2int(Z3_context c, Z3_ast t1, bool is_signed)
1247/// ```
1248struct BV2IntOpLowering : public SMTLoweringPattern<BV2IntOp> {
1249 using SMTLoweringPattern::SMTLoweringPattern;
1250
1251 LogicalResult
1252 matchAndRewrite(BV2IntOp op, OpAdaptor adaptor,
1253 ConversionPatternRewriter &rewriter) const final {
1254 // FIXME: ideally we don't want to use i1 here, since bools can sometimes be
1255 // compiled to wider widths in LLVM
1256 Value isSignedConst = LLVM::ConstantOp::create(
1257 rewriter, op->getLoc(), rewriter.getI1Type(), op.getIsSigned());
1258 rewriter.replaceOp(op,
1259 buildPtrAPICall(rewriter, op.getLoc(), "Z3_mk_bv2int",
1260 {adaptor.getInput(), isSignedConst}));
1261 return success();
1262 }
1263};
1264
1265/// Lower `smt.bv.cmp` operations to one of the following Z3 API function calls,
1266/// performing two's complement comparison, depending on the predicate
1267/// attribute.
1268/// ```
1269/// Z3_ast Z3_API Z3_mk_bv{{pred}}(Z3_context c, Z3_ast t1, Z3_ast t2);
1270/// ```
1271struct BVCmpOpLowering : public SMTLoweringPattern<BVCmpOp> {
1272 using SMTLoweringPattern::SMTLoweringPattern;
1273
1274 LogicalResult
1275 matchAndRewrite(BVCmpOp op, OpAdaptor adaptor,
1276 ConversionPatternRewriter &rewriter) const final {
1277 rewriter.replaceOp(
1278 op, buildPtrAPICall(rewriter, op.getLoc(),
1279 "Z3_mk_bv" +
1280 stringifyBVCmpPredicate(op.getPred()).str(),
1281 {adaptor.getLhs(), adaptor.getRhs()}));
1282 return success();
1283 }
1284};
1285
1286/// Expand the `smt.int.abs` operation to a `smt.ite` operation.
1287struct IntAbsOpLowering : public SMTLoweringPattern<IntAbsOp> {
1288 using SMTLoweringPattern::SMTLoweringPattern;
1289
1290 LogicalResult
1291 matchAndRewrite(IntAbsOp op, OpAdaptor adaptor,
1292 ConversionPatternRewriter &rewriter) const final {
1293 Location loc = op.getLoc();
1294 Value zero = IntConstantOp::create(
1295 rewriter, loc, rewriter.getIntegerAttr(rewriter.getI1Type(), 0));
1296 Value cmp = IntCmpOp::create(rewriter, loc, IntPredicate::lt,
1297 adaptor.getInput(), zero);
1298 Value neg = IntSubOp::create(rewriter, loc, zero, adaptor.getInput());
1299 rewriter.replaceOpWithNewOp<IteOp>(op, cmp, neg, adaptor.getInput());
1300 return success();
1301 }
1302};
1303
1304//===----------------------------------------------------------------------===//
1305// Placeholder Debug Patterns
1306//===----------------------------------------------------------------------===//
1307// For now we want to ignore Debug variable and scope ops - eventually we'll
1308// give this debug info to Z3
1309
1310/// Lower bit-vector verif.bmc.trace ops to the circt-bmc runtime callback.
1311/// Other SMT values are discarded until their trace materialization is
1312/// supported.
1313struct BMCTraceLowering : public SMTLoweringPattern<verif::BMCTraceOp> {
1314 using SMTLoweringPattern::SMTLoweringPattern;
1315
1316 LogicalResult
1317 matchAndRewrite(verif::BMCTraceOp op, OpAdaptor adaptor,
1318 ConversionPatternRewriter &rewriter) const final {
1319 auto bitVectorType = dyn_cast<smt::BitVectorType>(op.getValue().getType());
1320 if (!bitVectorType) {
1321 rewriter.eraseOp(op);
1322 return success();
1323 }
1324
1325 Location loc = op.getLoc();
1326 Value name = buildString(rewriter, loc, op.getName());
1327 Value width = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1328 bitVectorType.getWidth());
1329 auto function = op->getParentOfType<FunctionOpInterface>();
1330 if (!function || function.getNumArguments() == 0)
1331 return rewriter.notifyMatchFailure(op, "missing BMC trace context");
1332 Value traceContext = function.getArgument(function.getNumArguments() - 1);
1333 if (!isa<LLVM::LLVMPointerType>(traceContext.getType()))
1334 return rewriter.notifyMatchFailure(op, "invalid BMC trace context type");
1335 auto voidType = LLVM::LLVMVoidType::get(rewriter.getContext());
1336 buildCall(
1337 rewriter, loc, "circt_bmc_record_trace",
1338 LLVM::LLVMFunctionType::get(voidType, {traceContext.getType(),
1339 adaptor.getStep().getType(),
1340 name.getType(), width.getType(),
1341 adaptor.getValue().getType()}),
1342 {traceContext, adaptor.getStep(), name, width, adaptor.getValue()});
1343 rewriter.eraseOp(op);
1344 return success();
1345 }
1346};
1347
1348/// Strip dbg.variable ops.
1349struct DbgVariableLowering : public OpConversionPattern<debug::VariableOp> {
1350 using OpConversionPattern::OpConversionPattern;
1351
1352 LogicalResult
1353 matchAndRewrite(debug::VariableOp op, OpAdaptor adaptor,
1354 ConversionPatternRewriter &rewriter) const final {
1355 rewriter.eraseOp(op);
1356 return success();
1357 }
1358};
1359
1360/// Strip dbg.scope ops.
1361struct DbgScopeLowering : public OpConversionPattern<debug::ScopeOp> {
1362 using OpConversionPattern::OpConversionPattern;
1363
1364 LogicalResult
1365 matchAndRewrite(debug::ScopeOp op, OpAdaptor adaptor,
1366 ConversionPatternRewriter &rewriter) const final {
1367 // Make sure scope's only users are variables and therefore being deleted
1368 if (llvm::any_of(op->getUsers(), [](Operation *user) {
1369 return !isa<debug::VariableOp>(user);
1370 }))
1371 return failure();
1372 rewriter.eraseOp(op);
1373 return success();
1374 }
1375};
1376
1377} // namespace
1378
1379//===----------------------------------------------------------------------===//
1380// Pass Implementation
1381//===----------------------------------------------------------------------===//
1382
1383namespace {
1384struct LowerSMTToZ3LLVMPass
1385 : public circt::impl::LowerSMTToZ3LLVMBase<LowerSMTToZ3LLVMPass> {
1386 using Base::Base;
1387 void runOnOperation() override;
1388};
1389} // namespace
1390
1391void circt::populateSMTToZ3LLVMTypeConverter(TypeConverter &converter) {
1392 converter.addConversion([](smt::BoolType type) {
1393 return LLVM::LLVMPointerType::get(type.getContext());
1394 });
1395 converter.addConversion([](smt::BitVectorType type) {
1396 return LLVM::LLVMPointerType::get(type.getContext());
1397 });
1398 converter.addConversion([](smt::ArrayType type) {
1399 return LLVM::LLVMPointerType::get(type.getContext());
1400 });
1401 converter.addConversion([](smt::IntType type) {
1402 return LLVM::LLVMPointerType::get(type.getContext());
1403 });
1404 converter.addConversion([](smt::SMTFuncType type) {
1405 return LLVM::LLVMPointerType::get(type.getContext());
1406 });
1407 converter.addConversion([](smt::SortType type) {
1408 return LLVM::LLVMPointerType::get(type.getContext());
1409 });
1410}
1411
1413 RewritePatternSet &patterns, TypeConverter &converter,
1414 SMTGlobalsHandler &globals, const LowerSMTToZ3LLVMOptions &options) {
1415#define ADD_VARIADIC_PATTERN(OP, APINAME, MIN_NUM_ARGS) \
1416 patterns.add<VariadicSMTPattern<OP>>(/*NOLINT(bugprone-macro-parentheses)*/ \
1417 converter, patterns.getContext(), \
1418 globals, options, APINAME, \
1419 MIN_NUM_ARGS);
1420
1421#define ADD_ONE_TO_ONE_PATTERN(OP, APINAME, NUM_ARGS) \
1422 patterns.add<OneToOneSMTPattern<OP>>(/*NOLINT(bugprone-macro-parentheses)*/ \
1423 converter, patterns.getContext(), \
1424 globals, options, APINAME, NUM_ARGS);
1425
1426 // Lower `smt.distinct` operations which allows a variadic number of operands
1427 // according to the `:pairwise` attribute. The Z3 API function supports a
1428 // variadic number of operands as well, i.e., a direct lowering is possible:
1429 // ```
1430 // Z3_ast Z3_API Z3_mk_distinct(Z3_context c, unsigned num_args, Z3_ast const
1431 // args[])
1432 // ```
1433 // The API function requires num_args > 1 which is guaranteed to be satisfied
1434 // because `smt.distinct` is verified to have > 1 operands.
1435 ADD_VARIADIC_PATTERN(DistinctOp, "Z3_mk_distinct", 2);
1436
1437 // Lower `smt.and` operations which allows a variadic number of operands
1438 // according to the `:left-assoc` attribute. The Z3 API function supports a
1439 // variadic number of operands as well, i.e., a direct lowering is possible:
1440 // ```
1441 // Z3_ast Z3_API Z3_mk_and(Z3_context c, unsigned num_args, Z3_ast const
1442 // args[])
1443 // ```
1444 // The API function requires num_args > 1. This is not guaranteed by the
1445 // `smt.and` operation and thus the pattern will not apply when no operand is
1446 // present. The constant folder of the operation is assumed to fold this to
1447 // a constant 'true' (neutral element of AND).
1448 ADD_VARIADIC_PATTERN(AndOp, "Z3_mk_and", 2);
1449
1450 // Lower `smt.or` operations which allows a variadic number of operands
1451 // according to the `:left-assoc` attribute. The Z3 API function supports a
1452 // variadic number of operands as well, i.e., a direct lowering is possible:
1453 // ```
1454 // Z3_ast Z3_API Z3_mk_or(Z3_context c, unsigned num_args, Z3_ast const
1455 // args[])
1456 // ```
1457 // The API function requires num_args > 1. This is not guaranteed by the
1458 // `smt.or` operation and thus the pattern will not apply when no operand is
1459 // present. The constant folder of the operation is assumed to fold this to
1460 // a constant 'false' (neutral element of OR).
1461 ADD_VARIADIC_PATTERN(OrOp, "Z3_mk_or", 2);
1462
1463 // Lower `smt.not` operations to the following Z3 API function:
1464 // ```
1465 // Z3_ast Z3_API Z3_mk_not(Z3_context c, Z3_ast a);
1466 // ```
1467 ADD_ONE_TO_ONE_PATTERN(NotOp, "Z3_mk_not", 1);
1468
1469 // Lower `smt.xor` operations which allows a variadic number of operands
1470 // according to the `:left-assoc` attribute. The Z3 API function, however,
1471 // only takes two operands.
1472 // ```
1473 // Z3_ast Z3_API Z3_mk_xor(Z3_context c, Z3_ast t1, Z3_ast t2);
1474 // ```
1475 // Therefore, we need to decompose the operation first to a sequence of XOR
1476 // operations matching the left associative behavior.
1477 patterns.add<LowerLeftAssocSMTPattern<XOrOp>>(
1478 converter, patterns.getContext(), globals, options);
1479 ADD_ONE_TO_ONE_PATTERN(XOrOp, "Z3_mk_xor", 2);
1480
1481 // Lower `smt.implies` operations to the following Z3 API function:
1482 // ```
1483 // Z3_ast Z3_API Z3_mk_implies(Z3_context c, Z3_ast t1, Z3_ast t2);
1484 // ```
1485 ADD_ONE_TO_ONE_PATTERN(ImpliesOp, "Z3_mk_implies", 2);
1486
1487 // All the bit-vector arithmetic and bitwise operations conveniently lower to
1488 // Z3 API function calls with essentially matching names and a one-to-one
1489 // correspondence of operands to call arguments.
1490 ADD_ONE_TO_ONE_PATTERN(BVNegOp, "Z3_mk_bvneg", 1);
1491 ADD_ONE_TO_ONE_PATTERN(BVAddOp, "Z3_mk_bvadd", 2);
1492 ADD_ONE_TO_ONE_PATTERN(BVMulOp, "Z3_mk_bvmul", 2);
1493 ADD_ONE_TO_ONE_PATTERN(BVURemOp, "Z3_mk_bvurem", 2);
1494 ADD_ONE_TO_ONE_PATTERN(BVSRemOp, "Z3_mk_bvsrem", 2);
1495 ADD_ONE_TO_ONE_PATTERN(BVSModOp, "Z3_mk_bvsmod", 2);
1496 ADD_ONE_TO_ONE_PATTERN(BVUDivOp, "Z3_mk_bvudiv", 2);
1497 ADD_ONE_TO_ONE_PATTERN(BVSDivOp, "Z3_mk_bvsdiv", 2);
1498 ADD_ONE_TO_ONE_PATTERN(BVShlOp, "Z3_mk_bvshl", 2);
1499 ADD_ONE_TO_ONE_PATTERN(BVLShrOp, "Z3_mk_bvlshr", 2);
1500 ADD_ONE_TO_ONE_PATTERN(BVAShrOp, "Z3_mk_bvashr", 2);
1501 ADD_ONE_TO_ONE_PATTERN(BVNotOp, "Z3_mk_bvnot", 1);
1502 ADD_ONE_TO_ONE_PATTERN(BVAndOp, "Z3_mk_bvand", 2);
1503 ADD_ONE_TO_ONE_PATTERN(BVOrOp, "Z3_mk_bvor", 2);
1504 ADD_ONE_TO_ONE_PATTERN(BVXOrOp, "Z3_mk_bvxor", 2);
1505
1506 // The `smt.bv.concat` operation only supports two operands, just like the
1507 // Z3 API function.
1508 // ```
1509 // Z3_ast Z3_API Z3_mk_concat(Z3_context c, Z3_ast t1, Z3_ast t2);
1510 // ```
1511 ADD_ONE_TO_ONE_PATTERN(ConcatOp, "Z3_mk_concat", 2);
1512
1513 // Lower the `smt.ite` operation to the following Z3 API function call, where
1514 // `t1` must have boolean sort.
1515 // ```
1516 // Z3_ast Z3_API Z3_mk_ite(Z3_context c, Z3_ast t1, Z3_ast t2, Z3_ast t3);
1517 // ```
1518 ADD_ONE_TO_ONE_PATTERN(IteOp, "Z3_mk_ite", 3);
1519
1520 // Lower the `smt.array.select` operation to the following Z3 function call.
1521 // The operand declaration of the operation matches the order of arguments of
1522 // the API function.
1523 // ```
1524 // Z3_ast Z3_API Z3_mk_select(Z3_context c, Z3_ast a, Z3_ast i);
1525 // ```
1526 // Where `a` is the array expression and `i` is the index expression.
1527 ADD_ONE_TO_ONE_PATTERN(ArraySelectOp, "Z3_mk_select", 2);
1528
1529 // Lower the `smt.array.store` operation to the following Z3 function call.
1530 // The operand declaration of the operation matches the order of arguments of
1531 // the API function.
1532 // ```
1533 // Z3_ast Z3_API Z3_mk_store(Z3_context c, Z3_ast a, Z3_ast i, Z3_ast v);
1534 // ```
1535 // Where `a` is the array expression, `i` is the index expression, and `v` is
1536 // the value expression to be stored.
1537 ADD_ONE_TO_ONE_PATTERN(ArrayStoreOp, "Z3_mk_store", 3);
1538
1539 // Lower the `smt.int.add` operation to the following Z3 API function call.
1540 // ```
1541 // Z3_ast Z3_API Z3_mk_add(Z3_context c, unsigned num_args, Z3_ast const
1542 // args[]);
1543 // ```
1544 // The number of arguments must be greater than zero. Therefore, the pattern
1545 // will fail if applied to an operation with less than two operands.
1546 ADD_VARIADIC_PATTERN(IntAddOp, "Z3_mk_add", 2);
1547
1548 // Lower the `smt.int.mul` operation to the following Z3 API function call.
1549 // ```
1550 // Z3_ast Z3_API Z3_mk_mul(Z3_context c, unsigned num_args, Z3_ast const
1551 // args[]);
1552 // ```
1553 // The number of arguments must be greater than zero. Therefore, the pattern
1554 // will fail if applied to an operation with less than two operands.
1555 ADD_VARIADIC_PATTERN(IntMulOp, "Z3_mk_mul", 2);
1556
1557 // Lower the `smt.int.sub` operation to the following Z3 API function call.
1558 // ```
1559 // Z3_ast Z3_API Z3_mk_sub(Z3_context c, unsigned num_args, Z3_ast const
1560 // args[]);
1561 // ```
1562 // The number of arguments must be greater than zero. Since the `smt.int.sub`
1563 // operation always has exactly two operands, this trivially holds.
1564 ADD_VARIADIC_PATTERN(IntSubOp, "Z3_mk_sub", 2);
1565
1566 // Lower the `smt.int.div` operation to the following Z3 API function call.
1567 // ```
1568 // Z3_ast Z3_API Z3_mk_div(Z3_context c, Z3_ast arg1, Z3_ast arg2);
1569 // ```
1570 ADD_ONE_TO_ONE_PATTERN(IntDivOp, "Z3_mk_div", 2);
1571
1572 // Lower the `smt.int.mod` operation to the following Z3 API function call.
1573 // ```
1574 // Z3_ast Z3_API Z3_mk_mod(Z3_context c, Z3_ast arg1, Z3_ast arg2);
1575 // ```
1576 ADD_ONE_TO_ONE_PATTERN(IntModOp, "Z3_mk_mod", 2);
1577
1578#undef ADD_VARIADIC_PATTERN
1579#undef ADD_ONE_TO_ONE_PATTERN
1580
1581 // Lower `smt.eq` operations which allows a variadic number of operands
1582 // according to the `:chainable` attribute. The Z3 API function does not
1583 // support a variadic number of operands, but exactly two:
1584 // ```
1585 // Z3_ast Z3_API Z3_mk_eq(Z3_context c, Z3_ast l, Z3_ast r)
1586 // ```
1587 // As a result, we first apply a rewrite pattern that unfolds chainable
1588 // operators and then lower it one-to-one to the API function. In this case,
1589 // this means:
1590 // ```
1591 // eq(a,b,c,d) ->
1592 // and(eq(a,b), eq(b,c), eq(c,d)) ->
1593 // and(Z3_mk_eq(ctx, a, b), Z3_mk_eq(ctx, b, c), Z3_mk_eq(ctx, c, d))
1594 // ```
1595 // The patterns for `smt.and` will then do the remaining work.
1596 patterns.add<LowerChainableSMTPattern<EqOp>>(converter, patterns.getContext(),
1597 globals, options);
1598 patterns.add<OneToOneSMTPattern<EqOp>>(converter, patterns.getContext(),
1599 globals, options, "Z3_mk_eq", 2);
1600
1601 // Other lowering patterns. Refer to their implementation directly for more
1602 // information.
1603 patterns.add<BVConstantOpLowering, DeclareFunOpLowering, AssertOpLowering,
1604 ResetOpLowering, PushOpLowering, PopOpLowering, CheckOpLowering,
1605 SolverOpLowering, ApplyFuncOpLowering, YieldOpLowering,
1606 RepeatOpLowering, ExtractOpLowering, BoolConstantOpLowering,
1607 IntConstantOpLowering, ArrayBroadcastOpLowering, BVCmpOpLowering,
1608 IntCmpOpLowering, IntAbsOpLowering, Int2BVOpLowering,
1609 BV2IntOpLowering, QuantifierLowering<ForallOp>,
1610 QuantifierLowering<ExistsOp>>(converter, patterns.getContext(),
1611 globals, options);
1612 patterns.add<BMCTraceLowering>(converter, patterns.getContext(), globals,
1613 options);
1614 patterns.add<DbgVariableLowering, DbgScopeLowering>(patterns.getContext());
1615}
1616
1617void LowerSMTToZ3LLVMPass::runOnOperation() {
1618 LowerSMTToZ3LLVMOptions options;
1619 options.debug = debug;
1620
1621 // Check that the lowering is possible
1622 // Specifically, check that the use of set-logic ops is valid for z3
1623 auto setLogicCheck = getOperation().walk([&](SolverOp solverOp)
1624 -> WalkResult {
1625 // Check that solver ops only contain one set-logic op and that they're at
1626 // the start of the body
1627 auto setLogicOps = solverOp.getBodyRegion().getOps<smt::SetLogicOp>();
1628 auto numSetLogicOps = std::distance(setLogicOps.begin(), setLogicOps.end());
1629 if (numSetLogicOps > 1) {
1630 return solverOp.emitError(
1631 "multiple set-logic operations found in one solver operation - Z3 "
1632 "only supports setting the logic once");
1633 }
1634 if (numSetLogicOps == 1)
1635 // Check the only ops before the set-logic op are ConstantLike
1636 for (auto &blockOp : solverOp.getBodyRegion().getOps()) {
1637 if (isa<smt::SetLogicOp>(blockOp))
1638 break;
1639 if (!blockOp.hasTrait<OpTrait::ConstantLike>()) {
1640 return solverOp.emitError("set-logic operation must be the first "
1641 "non-constant operation in a solver "
1642 "operation");
1643 }
1644 }
1645 return WalkResult::advance();
1646 });
1647 if (setLogicCheck.wasInterrupted())
1648 return signalPassFailure();
1649
1650 llvm::StringMap<Operation *> traceNames;
1651 auto traceNameCheck =
1652 getOperation().walk([&](verif::BMCTraceOp traceOp) -> WalkResult {
1653 auto [it, inserted] =
1654 traceNames.try_emplace(traceOp.getName(), traceOp.getOperation());
1655 if (inserted)
1656 return WalkResult::advance();
1657 auto error = traceOp.emitError() << "duplicate BMC trace name '"
1658 << traceOp.getName() << "'";
1659 error.attachNote(it->second->getLoc())
1660 << "first BMC trace with this name is here";
1661 return WalkResult::interrupt();
1662 });
1663 if (traceNameCheck.wasInterrupted())
1664 return signalPassFailure();
1665
1666 // Thread an opaque runtime context into every function containing a BMC
1667 // trace marker. The lowering below forwards this argument to the runtime
1668 // callback, making trace state explicit in the generated code.
1669 llvm::SmallPtrSet<Operation *, 4> traceFunctions;
1670 getOperation().walk([&](verif::BMCTraceOp traceOp) {
1671 auto function = traceOp->getParentOfType<FunctionOpInterface>();
1672 if (function)
1673 traceFunctions.insert(function.getOperation());
1674 });
1675 auto traceContextType = LLVM::LLVMPointerType::get(&getContext());
1676 for (Operation *operation : traceFunctions) {
1677 auto function = cast<FunctionOpInterface>(operation);
1678 if (failed(function.insertArgument(function.getNumArguments(),
1679 traceContextType, {},
1680 function.getLoc()))) {
1681 function.emitError("failed to add BMC trace context argument");
1682 return signalPassFailure();
1683 }
1684 }
1685
1686 // Set up the type converter
1687 LLVMTypeConverter converter(&getContext());
1689
1690 RewritePatternSet patterns(&getContext());
1691
1692 // Populate the func to LLVM conversion patterns for two reasons:
1693 // * Typically functions are represented using `func.func` and including the
1694 // patterns to lower them here is more convenient for most lowering
1695 // pipelines (avoids running another pass).
1696 // * Already having `llvm.func` in the input or lowering `func.func` before
1697 // the SMT in the body leads to issues because the SCF conversion patterns
1698 // don't take the type converter into consideration and thus create blocks
1699 // with the old types for block arguments. However, the conversion happens
1700 // top-down and thus are assumed to be converted by the parent function op
1701 // which at that point would have already been lowered (and the blocks are
1702 // also not there when doing everything in one pass, i.e.,
1703 // `populateAnyFunctionOpInterfaceTypeConversionPattern` does not have any
1704 // effect as well). Are the SCF lowering patterns actually broken and should
1705 // take a type-converter?
1706 populateFuncToLLVMConversionPatterns(converter, patterns);
1707 arith::populateArithToLLVMConversionPatterns(converter, patterns);
1708
1709 // Populate SCF to CF and CF to LLVM lowering patterns because we create
1710 // `scf.if` operations in the lowering patterns for convenience (given the
1711 // above issue we might want to lower to LLVM directly; or fix upstream?)
1712 populateSCFToControlFlowConversionPatterns(patterns);
1713 mlir::cf::populateControlFlowToLLVMConversionPatterns(converter, patterns);
1714
1715 // Create the globals to store the context and solver and populate the SMT
1716 // lowering patterns.
1717 OpBuilder builder(&getContext());
1718 auto globals = SMTGlobalsHandler::create(builder, getOperation());
1719 populateSMTToZ3LLVMConversionPatterns(patterns, converter, globals, options);
1720
1721 // Do a full conversion. This assumes that all other dialects have been
1722 // lowered before this pass already.
1723 LLVMConversionTarget target(getContext());
1724 target.addLegalOp<mlir::ModuleOp>();
1725 target.addLegalOp<scf::YieldOp>();
1726 target.addIllegalDialect<debug::DebugDialect>();
1727 target.addIllegalOp<verif::BMCTraceOp>();
1728
1729 if (failed(applyFullConversion(getOperation(), target, std::move(patterns))))
1730 return signalPassFailure();
1731}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static FIRRTLBaseType convertType(FIRRTLBaseType type)
Returns null type if no conversion is needed.
Definition DropConst.cpp:32
#define ADD_VARIADIC_PATTERN(OP, APINAME, MIN_NUM_ARGS)
#define ADD_ONE_TO_ONE_PATTERN(OP, APINAME, NUM_ARGS)
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:222
RewritePatternSet pattern
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
void add(mlir::ModuleOp module)
Definition Namespace.h:48
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:87
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Definition SymCache.cpp:23
Default symbol cache implementation; stores associations between names (StringAttr's) to mlir::Operat...
Definition SymCache.h:85
void error(Twine message)
Definition LSPUtils.cpp:16
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void populateSMTToZ3LLVMTypeConverter(TypeConverter &converter)
Populate the given type converter with the SMT to LLVM type conversions.
void populateSMTToZ3LLVMConversionPatterns(RewritePatternSet &patterns, TypeConverter &converter, SMTGlobalsHandler &globals, const LowerSMTToZ3LLVMOptions &options)
Add the SMT to LLVM IR conversion patterns to 'patterns'.
Definition debug.py:1
A symbol cache for LLVM globals and functions relevant to SMT lowering patterns.
Definition SMTToZ3LLVM.h:25
static SMTGlobalsHandler create(OpBuilder &builder, ModuleOp module)
Creates the LLVM global operations to store the pointers to the solver and the context and returns a ...
SMTGlobalsHandler(ModuleOp module, mlir::LLVM::GlobalOp solver, mlir::LLVM::GlobalOp ctx)
Initializes the caches and keeps track of the given globals to store the pointers to the SMT solver a...