xref: /llvm-project/mlir/lib/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.cpp (revision 039b969b32b64b64123dce30dd28ec4e343d893f)
1 //===- ControlFlowToLLVM.cpp - ControlFlow to LLVM dialect conversion -----===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file implements a pass to convert MLIR standard and builtin dialects
10 // into the LLVM IR dialect.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h"
15 #include "../PassDetail.h"
16 #include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
17 #include "mlir/Conversion/LLVMCommon/Pattern.h"
18 #include "mlir/Conversion/LLVMCommon/VectorPattern.h"
19 #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
20 #include "mlir/Dialect/LLVMIR/FunctionCallUtils.h"
21 #include "mlir/Dialect/LLVMIR/LLVMDialect.h"
22 #include "mlir/IR/BuiltinOps.h"
23 #include "mlir/IR/PatternMatch.h"
24 #include "mlir/Transforms/DialectConversion.h"
25 #include "llvm/ADT/StringRef.h"
26 #include <functional>
27 
28 using namespace mlir;
29 
30 #define PASS_NAME "convert-cf-to-llvm"
31 
32 namespace {
33 /// Lower `cf.assert`. The default lowering calls the `abort` function if the
34 /// assertion is violated and has no effect otherwise. The failure message is
35 /// ignored by the default lowering but should be propagated by any custom
36 /// lowering.
37 struct AssertOpLowering : public ConvertOpToLLVMPattern<cf::AssertOp> {
38   using ConvertOpToLLVMPattern<cf::AssertOp>::ConvertOpToLLVMPattern;
39 
40   LogicalResult
41   matchAndRewrite(cf::AssertOp op, OpAdaptor adaptor,
42                   ConversionPatternRewriter &rewriter) const override {
43     auto loc = op.getLoc();
44 
45     // Insert the `abort` declaration if necessary.
46     auto module = op->getParentOfType<ModuleOp>();
47     auto abortFunc = module.lookupSymbol<LLVM::LLVMFuncOp>("abort");
48     if (!abortFunc) {
49       OpBuilder::InsertionGuard guard(rewriter);
50       rewriter.setInsertionPointToStart(module.getBody());
51       auto abortFuncTy = LLVM::LLVMFunctionType::get(getVoidType(), {});
52       abortFunc = rewriter.create<LLVM::LLVMFuncOp>(rewriter.getUnknownLoc(),
53                                                     "abort", abortFuncTy);
54     }
55 
56     // Split block at `assert` operation.
57     Block *opBlock = rewriter.getInsertionBlock();
58     auto opPosition = rewriter.getInsertionPoint();
59     Block *continuationBlock = rewriter.splitBlock(opBlock, opPosition);
60 
61     // Generate IR to call `abort`.
62     Block *failureBlock = rewriter.createBlock(opBlock->getParent());
63     rewriter.create<LLVM::CallOp>(loc, abortFunc, llvm::None);
64     rewriter.create<LLVM::UnreachableOp>(loc);
65 
66     // Generate assertion test.
67     rewriter.setInsertionPointToEnd(opBlock);
68     rewriter.replaceOpWithNewOp<LLVM::CondBrOp>(
69         op, adaptor.getArg(), continuationBlock, failureBlock);
70 
71     return success();
72   }
73 };
74 
75 /// The cf->LLVM lowerings for branching ops require that the blocks they jump
76 /// to first have updated types which should be handled by a pattern operating
77 /// on the parent op.
78 static LogicalResult verifyMatchingValues(ConversionPatternRewriter &rewriter,
79                                           ValueRange operands,
80                                           ValueRange blockArgs, Location loc,
81                                           llvm::StringRef messagePrefix) {
82   for (const auto &idxAndTypes :
83        llvm::enumerate(llvm::zip(blockArgs, operands))) {
84     int64_t i = idxAndTypes.index();
85     Value argValue =
86         rewriter.getRemappedValue(std::get<0>(idxAndTypes.value()));
87     Type operandType = std::get<1>(idxAndTypes.value()).getType();
88     // In the case of an invalid jump, the block argument will have been
89     // remapped to an UnrealizedConversionCast. In the case of a valid jump,
90     // there might still be a no-op conversion cast with both types being equal.
91     // Consider both of these details to see if the jump would be invalid.
92     if (auto op = dyn_cast_or_null<UnrealizedConversionCastOp>(
93             argValue.getDefiningOp())) {
94       if (op.getOperandTypes().front() != operandType) {
95         return rewriter.notifyMatchFailure(loc, [&](Diagnostic &diag) {
96           diag << messagePrefix;
97           diag << "mismatched types from operand # " << i << " ";
98           diag << operandType;
99           diag << " not compatible with destination block argument type ";
100           diag << argValue.getType();
101           diag << " which should be converted with the parent op.";
102         });
103       }
104     }
105   }
106   return success();
107 }
108 
109 /// Ensure that all block types were updated and then create an LLVM::BrOp
110 struct BranchOpLowering : public ConvertOpToLLVMPattern<cf::BranchOp> {
111   using ConvertOpToLLVMPattern<cf::BranchOp>::ConvertOpToLLVMPattern;
112 
113   LogicalResult
114   matchAndRewrite(cf::BranchOp op, typename cf::BranchOp::Adaptor adaptor,
115                   ConversionPatternRewriter &rewriter) const override {
116     if (failed(verifyMatchingValues(rewriter, adaptor.getDestOperands(),
117                                     op.getSuccessor()->getArguments(),
118                                     op.getLoc(),
119                                     /*messagePrefix=*/"")))
120       return failure();
121 
122     rewriter.replaceOpWithNewOp<LLVM::BrOp>(
123         op, adaptor.getOperands(), op->getSuccessors(), op->getAttrs());
124     return success();
125   }
126 };
127 
128 /// Ensure that all block types were updated and then create an LLVM::CondBrOp
129 struct CondBranchOpLowering : public ConvertOpToLLVMPattern<cf::CondBranchOp> {
130   using ConvertOpToLLVMPattern<cf::CondBranchOp>::ConvertOpToLLVMPattern;
131 
132   LogicalResult
133   matchAndRewrite(cf::CondBranchOp op,
134                   typename cf::CondBranchOp::Adaptor adaptor,
135                   ConversionPatternRewriter &rewriter) const override {
136     if (failed(verifyMatchingValues(rewriter, adaptor.getFalseDestOperands(),
137                                     op.getFalseDest()->getArguments(),
138                                     op.getLoc(), "in false case branch ")))
139       return failure();
140     if (failed(verifyMatchingValues(rewriter, adaptor.getTrueDestOperands(),
141                                     op.getTrueDest()->getArguments(),
142                                     op.getLoc(), "in true case branch ")))
143       return failure();
144 
145     rewriter.replaceOpWithNewOp<LLVM::CondBrOp>(
146         op, adaptor.getOperands(), op->getSuccessors(), op->getAttrs());
147     return success();
148   }
149 };
150 
151 /// Ensure that all block types were updated and then create an LLVM::SwitchOp
152 struct SwitchOpLowering : public ConvertOpToLLVMPattern<cf::SwitchOp> {
153   using ConvertOpToLLVMPattern<cf::SwitchOp>::ConvertOpToLLVMPattern;
154 
155   LogicalResult
156   matchAndRewrite(cf::SwitchOp op, typename cf::SwitchOp::Adaptor adaptor,
157                   ConversionPatternRewriter &rewriter) const override {
158     if (failed(verifyMatchingValues(rewriter, adaptor.getDefaultOperands(),
159                                     op.getDefaultDestination()->getArguments(),
160                                     op.getLoc(), "in switch default case ")))
161       return failure();
162 
163     for (const auto &i : llvm::enumerate(
164              llvm::zip(adaptor.getCaseOperands(), op.getCaseDestinations()))) {
165       if (failed(verifyMatchingValues(
166               rewriter, std::get<0>(i.value()),
167               std::get<1>(i.value())->getArguments(), op.getLoc(),
168               "in switch case " + std::to_string(i.index()) + " "))) {
169         return failure();
170       }
171     }
172 
173     rewriter.replaceOpWithNewOp<LLVM::SwitchOp>(
174         op, adaptor.getOperands(), op->getSuccessors(), op->getAttrs());
175     return success();
176   }
177 };
178 
179 } // namespace
180 
181 void mlir::cf::populateControlFlowToLLVMConversionPatterns(
182     LLVMTypeConverter &converter, RewritePatternSet &patterns) {
183   // clang-format off
184   patterns.add<
185       AssertOpLowering,
186       BranchOpLowering,
187       CondBranchOpLowering,
188       SwitchOpLowering>(converter);
189   // clang-format on
190 }
191 
192 //===----------------------------------------------------------------------===//
193 // Pass Definition
194 //===----------------------------------------------------------------------===//
195 
196 namespace {
197 /// A pass converting MLIR operations into the LLVM IR dialect.
198 struct ConvertControlFlowToLLVM
199     : public ConvertControlFlowToLLVMBase<ConvertControlFlowToLLVM> {
200   ConvertControlFlowToLLVM() = default;
201 
202   /// Run the dialect converter on the module.
203   void runOnOperation() override {
204     LLVMConversionTarget target(getContext());
205     RewritePatternSet patterns(&getContext());
206 
207     LowerToLLVMOptions options(&getContext());
208     if (indexBitwidth != kDeriveIndexBitwidthFromDataLayout)
209       options.overrideIndexBitwidth(indexBitwidth);
210 
211     LLVMTypeConverter converter(&getContext(), options);
212     mlir::cf::populateControlFlowToLLVMConversionPatterns(converter, patterns);
213 
214     if (failed(applyPartialConversion(getOperation(), target,
215                                       std::move(patterns))))
216       signalPassFailure();
217   }
218 };
219 } // namespace
220 
221 std::unique_ptr<Pass> mlir::cf::createConvertControlFlowToLLVMPass() {
222   return std::make_unique<ConvertControlFlowToLLVM>();
223 }
224