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