1 //===- CommonFolders.h - Common Operation Folders----------------*- C++ -*-===//
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 header file declares various common operation folders. These folders
10 // are intended to be used by dialects to support common folding behavior
11 // without requiring each dialect to provide its own implementation.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #ifndef MLIR_DIALECT_COMMONFOLDERS_H
16 #define MLIR_DIALECT_COMMONFOLDERS_H
17 
18 #include "mlir/IR/BuiltinAttributes.h"
19 #include "mlir/IR/BuiltinTypes.h"
20 #include "llvm/ADT/ArrayRef.h"
21 #include "llvm/ADT/STLExtras.h"
22 
23 namespace mlir {
24 /// Performs constant folding `calculate` with element-wise behavior on the two
25 /// attributes in `operands` and returns the result if possible.
26 template <
27     class AttrElementT, class ElementValueT = typename AttrElementT::ValueType,
28     class CalculationT =
29         function_ref<Optional<ElementValueT>(ElementValueT, ElementValueT)>>
constFoldBinaryOpConditional(ArrayRef<Attribute> operands,const CalculationT & calculate)30 Attribute constFoldBinaryOpConditional(ArrayRef<Attribute> operands,
31                                        const CalculationT &calculate) {
32   assert(operands.size() == 2 && "binary op takes two operands");
33   if (!operands[0] || !operands[1])
34     return {};
35   if (operands[0].getType() != operands[1].getType())
36     return {};
37 
38   if (operands[0].isa<AttrElementT>() && operands[1].isa<AttrElementT>()) {
39     auto lhs = operands[0].cast<AttrElementT>();
40     auto rhs = operands[1].cast<AttrElementT>();
41 
42     auto calRes = calculate(lhs.getValue(), rhs.getValue());
43 
44     if (!calRes)
45       return {};
46 
47     return AttrElementT::get(lhs.getType(), *calRes);
48   }
49 
50   if (operands[0].isa<SplatElementsAttr>() &&
51       operands[1].isa<SplatElementsAttr>()) {
52     // Both operands are splats so we can avoid expanding the values out and
53     // just fold based on the splat value.
54     auto lhs = operands[0].cast<SplatElementsAttr>();
55     auto rhs = operands[1].cast<SplatElementsAttr>();
56 
57     auto elementResult = calculate(lhs.getSplatValue<ElementValueT>(),
58                                    rhs.getSplatValue<ElementValueT>());
59     if (!elementResult)
60       return {};
61 
62     return DenseElementsAttr::get(lhs.getType(), *elementResult);
63   } else if (operands[0].isa<ElementsAttr>() &&
64              operands[1].isa<ElementsAttr>()) {
65     // Operands are ElementsAttr-derived; perform an element-wise fold by
66     // expanding the values.
67     auto lhs = operands[0].cast<ElementsAttr>();
68     auto rhs = operands[1].cast<ElementsAttr>();
69 
70     auto lhsIt = lhs.value_begin<ElementValueT>();
71     auto rhsIt = rhs.value_begin<ElementValueT>();
72     SmallVector<ElementValueT, 4> elementResults;
73     elementResults.reserve(lhs.getNumElements());
74     for (size_t i = 0, e = lhs.getNumElements(); i < e; ++i, ++lhsIt, ++rhsIt) {
75       auto elementResult = calculate(*lhsIt, *rhsIt);
76       if (!elementResult)
77         return {};
78       elementResults.push_back(*elementResult);
79     }
80 
81     return DenseElementsAttr::get(lhs.getType(), elementResults);
82   }
83   return {};
84 }
85 
86 template <class AttrElementT,
87           class ElementValueT = typename AttrElementT::ValueType,
88           class CalculationT =
89               function_ref<ElementValueT(ElementValueT, ElementValueT)>>
constFoldBinaryOp(ArrayRef<Attribute> operands,const CalculationT & calculate)90 Attribute constFoldBinaryOp(ArrayRef<Attribute> operands,
91                             const CalculationT &calculate) {
92   return constFoldBinaryOpConditional<AttrElementT>(
93       operands,
94       [&](ElementValueT a, ElementValueT b) -> Optional<ElementValueT> {
95         return calculate(a, b);
96       });
97 }
98 
99 /// Performs constant folding `calculate` with element-wise behavior on the one
100 /// attributes in `operands` and returns the result if possible.
101 template <
102     class AttrElementT, class ElementValueT = typename AttrElementT::ValueType,
103     class CalculationT = function_ref<Optional<ElementValueT>(ElementValueT)>>
constFoldUnaryOpConditional(ArrayRef<Attribute> operands,const CalculationT && calculate)104 Attribute constFoldUnaryOpConditional(ArrayRef<Attribute> operands,
105                                       const CalculationT &&calculate) {
106   assert(operands.size() == 1 && "unary op takes one operands");
107   if (!operands[0])
108     return {};
109 
110   if (operands[0].isa<AttrElementT>()) {
111     auto op = operands[0].cast<AttrElementT>();
112 
113     auto res = calculate(op.getValue());
114     if (!res)
115       return {};
116     return AttrElementT::get(op.getType(), *res);
117   }
118   if (operands[0].isa<SplatElementsAttr>()) {
119     // Both operands are splats so we can avoid expanding the values out and
120     // just fold based on the splat value.
121     auto op = operands[0].cast<SplatElementsAttr>();
122 
123     auto elementResult = calculate(op.getSplatValue<ElementValueT>());
124     if (!elementResult)
125       return {};
126     return DenseElementsAttr::get(op.getType(), *elementResult);
127   } else if (operands[0].isa<ElementsAttr>()) {
128     // Operands are ElementsAttr-derived; perform an element-wise fold by
129     // expanding the values.
130     auto op = operands[0].cast<ElementsAttr>();
131 
132     auto opIt = op.value_begin<ElementValueT>();
133     SmallVector<ElementValueT> elementResults;
134     elementResults.reserve(op.getNumElements());
135     for (size_t i = 0, e = op.getNumElements(); i < e; ++i, ++opIt) {
136       auto elementResult = calculate(*opIt);
137       if (!elementResult)
138         return {};
139       elementResults.push_back(*elementResult);
140     }
141     return DenseElementsAttr::get(op.getType(), elementResults);
142   }
143   return {};
144 }
145 
146 template <class AttrElementT,
147           class ElementValueT = typename AttrElementT::ValueType,
148           class CalculationT = function_ref<ElementValueT(ElementValueT)>>
constFoldUnaryOp(ArrayRef<Attribute> operands,const CalculationT && calculate)149 Attribute constFoldUnaryOp(ArrayRef<Attribute> operands,
150                            const CalculationT &&calculate) {
151   return constFoldUnaryOpConditional<AttrElementT>(
152       operands,
153       [&](ElementValueT a) -> Optional<ElementValueT> { return calculate(a); });
154 }
155 
156 template <
157     class AttrElementT, class TargetAttrElementT,
158     class ElementValueT = typename AttrElementT::ValueType,
159     class TargetElementValueT = typename TargetAttrElementT::ValueType,
160     class CalculationT = function_ref<TargetElementValueT(ElementValueT, bool)>>
constFoldCastOp(ArrayRef<Attribute> operands,Type resType,const CalculationT & calculate)161 Attribute constFoldCastOp(ArrayRef<Attribute> operands, Type resType,
162                           const CalculationT &calculate) {
163   assert(operands.size() == 1 && "Cast op takes one operand");
164   if (!operands[0])
165     return {};
166 
167   if (operands[0].isa<AttrElementT>()) {
168     auto op = operands[0].cast<AttrElementT>();
169     bool castStatus = true;
170     auto res = calculate(op.getValue(), castStatus);
171     if (!castStatus)
172       return {};
173     return TargetAttrElementT::get(resType, res);
174   }
175   if (operands[0].isa<SplatElementsAttr>()) {
176     // The operand is a splat so we can avoid expanding the values out and
177     // just fold based on the splat value.
178     auto op = operands[0].cast<SplatElementsAttr>();
179     bool castStatus = true;
180     auto elementResult =
181         calculate(op.getSplatValue<ElementValueT>(), castStatus);
182     if (!castStatus)
183       return {};
184     return DenseElementsAttr::get(resType, elementResult);
185   }
186   if (operands[0].isa<ElementsAttr>()) {
187     // Operand is ElementsAttr-derived; perform an element-wise fold by
188     // expanding the value.
189     auto op = operands[0].cast<ElementsAttr>();
190     bool castStatus = true;
191     auto opIt = op.value_begin<ElementValueT>();
192     SmallVector<TargetElementValueT> elementResults;
193     elementResults.reserve(op.getNumElements());
194     for (size_t i = 0, e = op.getNumElements(); i < e; ++i, ++opIt) {
195       auto elt = calculate(*opIt, castStatus);
196       if (!castStatus)
197         return {};
198       elementResults.push_back(elt);
199     }
200 
201     return DenseElementsAttr::get(resType, elementResults);
202   }
203   return {};
204 }
205 
206 } // namespace mlir
207 
208 #endif // MLIR_DIALECT_COMMONFOLDERS_H
209