//===- CommonFolders.h - Common Operation Folders----------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This header file declares various common operation folders. These folders // are intended to be used by dialects to support common folding behavior // without requiring each dialect to provide its own implementation. // //===----------------------------------------------------------------------===// #ifndef MLIR_DIALECT_COMMONFOLDERS_H #define MLIR_DIALECT_COMMONFOLDERS_H #include "mlir/IR/Attributes.h" #include "mlir/IR/BuiltinAttributeInterfaces.h" #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/BuiltinTypeInterfaces.h" #include "mlir/IR/Types.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include #include #include namespace mlir { namespace ub { class PoisonAttr; } /// Performs constant folding `calculate` with element-wise behavior on the two /// attributes in `operands` and returns the result if possible. /// Uses `resultType` for the type of the returned attribute. /// Optional PoisonAttr template argument allows to specify 'poison' attribute /// which will be directly propagated to result. template ( LElementValueT, RElementValueT)>> Attribute constFoldBinaryOpConditional(ArrayRef operands, Type resultType, CalculationT &&calculate) { assert(operands.size() == 2 && "binary op takes two operands"); static_assert( std::is_void_v || !llvm::is_incomplete_v, "PoisonAttr is undefined, either add a dependency on UB dialect or pass " "void as template argument to opt-out from poison semantics."); if constexpr (!std::is_void_v) { if (isa_and_nonnull(operands[0])) return operands[0]; if (isa_and_nonnull(operands[1])) return operands[1]; } if (!resultType || !operands[0] || !operands[1]) return {}; if (isa(operands[0]) && isa(operands[1])) { auto lhs = cast(operands[0]); auto rhs = cast(operands[1]); if constexpr (std::is_same_v) if (lhs.getType() != rhs.getType()) return {}; auto calRes = calculate(lhs.getValue(), rhs.getValue()); if (!calRes) return {}; return ResultAttrElementT::get(resultType, *calRes); } if (isa(operands[0]) && isa(operands[1])) { // Both operands are splats so we can avoid expanding the values out and // just fold based on the splat value. auto lhs = cast(operands[0]); auto rhs = cast(operands[1]); if constexpr (std::is_same_v) if (lhs.getType() != rhs.getType()) return {}; auto elementResult = calculate(lhs.getSplatValue(), rhs.getSplatValue()); if (!elementResult) return {}; return DenseElementsAttr::get(cast(resultType), *elementResult); } if (isa(operands[0]) && isa(operands[1])) { // Operands are ElementsAttr-derived; perform an element-wise fold by // expanding the values. auto lhs = cast(operands[0]); auto rhs = cast(operands[1]); if constexpr (std::is_same_v) if (lhs.getType() != rhs.getType()) return {}; auto maybeLhsIt = lhs.try_value_begin(); auto maybeRhsIt = rhs.try_value_begin(); if (!maybeLhsIt || !maybeRhsIt) return {}; auto lhsIt = *maybeLhsIt; auto rhsIt = *maybeRhsIt; SmallVector elementResults; elementResults.reserve(lhs.getNumElements()); for (size_t i = 0, e = lhs.getNumElements(); i < e; ++i, ++lhsIt, ++rhsIt) { auto elementResult = calculate(*lhsIt, *rhsIt); if (!elementResult) return {}; elementResults.push_back(*elementResult); } return DenseElementsAttr::get(cast(resultType), elementResults); } return {}; } /// Performs constant folding `calculate` with element-wise behavior on the two /// attributes in `operands` and returns the result if possible. /// Uses the operand element type for the element type of the returned /// attribute. /// Optional PoisonAttr template argument allows to specify 'poison' attribute /// which will be directly propagated to result. template ( LElementValueT, RElementValueT)>> Attribute constFoldBinaryOpConditional(ArrayRef operands, CalculationT &&calculate) { assert(operands.size() == 2 && "binary op takes two operands"); static_assert( std::is_void_v || !llvm::is_incomplete_v, "PoisonAttr is undefined, either add a dependency on UB dialect or pass " "void as template argument to opt-out from poison semantics."); if constexpr (!std::is_void_v) { if (isa_and_nonnull(operands[0])) return operands[0]; if (isa_and_nonnull(operands[1])) return operands[1]; } auto getAttrType = [](Attribute attr) -> Type { if (auto typed = dyn_cast_or_null(attr)) return typed.getType(); return {}; }; Type lhsType = getAttrType(operands[0]); Type rhsType = getAttrType(operands[1]); if (!lhsType || !rhsType) return {}; if constexpr (std::is_same_v) if (lhsType != rhsType) return {}; return constFoldBinaryOpConditional< LAttrElementT, RAttrElementT, LElementValueT, RElementValueT, PoisonAttr, ResultAttrElementT, ResultElementValueT, CalculationT>( operands, lhsType, std::forward(calculate)); } template > Attribute constFoldBinaryOp(ArrayRef operands, Type resultType, CalculationT &&calculate) { return constFoldBinaryOpConditional( operands, resultType, [&](LElementValueT a, RElementValueT b) -> std::optional { return calculate(a, b); }); } template > Attribute constFoldBinaryOp(ArrayRef operands, CalculationT &&calculate) { return constFoldBinaryOpConditional( operands, [&](LElementValueT a, RElementValueT b) -> std::optional { return calculate(a, b); }); } /// Performs constant folding `calculate` with element-wise behavior on the one /// attributes in `operands` and returns the result if possible. /// Uses `resultType` for the type of the returned attribute. /// Optional PoisonAttr template argument allows to specify 'poison' attribute /// which will be directly propagated to result. template (ElementValueT)>> Attribute constFoldUnaryOpConditional(ArrayRef operands, Type resultType, CalculationT &&calculate) { if (!resultType || !llvm::getSingleElement(operands)) return {}; static_assert( std::is_void_v || !llvm::is_incomplete_v, "PoisonAttr is undefined, either add a dependency on UB dialect or pass " "void as template argument to opt-out from poison semantics."); if constexpr (!std::is_void_v) { if (isa(operands[0])) return operands[0]; } if (isa(operands[0])) { auto op = cast(operands[0]); auto res = calculate(op.getValue()); if (!res) return {}; return ResultAttrElementT::get(resultType, *res); } if (isa(operands[0])) { // Both operands are splats so we can avoid expanding the values out and // just fold based on the splat value. auto op = cast(operands[0]); auto elementResult = calculate(op.getSplatValue()); if (!elementResult) return {}; return DenseElementsAttr::get(cast(resultType), *elementResult); } else if (isa(operands[0])) { // Operands are ElementsAttr-derived; perform an element-wise fold by // expanding the values. auto op = cast(operands[0]); auto maybeOpIt = op.try_value_begin(); if (!maybeOpIt) return {}; auto opIt = *maybeOpIt; SmallVector elementResults; elementResults.reserve(op.getNumElements()); for (size_t i = 0, e = op.getNumElements(); i < e; ++i, ++opIt) { auto elementResult = calculate(*opIt); if (!elementResult) return {}; elementResults.push_back(*elementResult); } return DenseElementsAttr::get(cast(resultType), elementResults); } return {}; } /// Performs constant folding `calculate` with element-wise behavior on the one /// attributes in `operands` and returns the result if possible. /// Uses the operand element type for the element type of the returned /// attribute. /// Optional PoisonAttr template argument allows to specify 'poison' attribute /// which will be directly propagated to result. template (ElementValueT)>> Attribute constFoldUnaryOpConditional(ArrayRef operands, CalculationT &&calculate) { if (!llvm::getSingleElement(operands)) return {}; static_assert( std::is_void_v || !llvm::is_incomplete_v, "PoisonAttr is undefined, either add a dependency on UB dialect or pass " "void as template argument to opt-out from poison semantics."); if constexpr (!std::is_void_v) { if (isa(operands[0])) return operands[0]; } auto getAttrType = [](Attribute attr) -> Type { if (auto typed = dyn_cast_or_null(attr)) return typed.getType(); return {}; }; Type operandType = getAttrType(operands[0]); if (!operandType) return {}; return constFoldUnaryOpConditional( operands, operandType, std::forward(calculate)); } template > Attribute constFoldUnaryOp(ArrayRef operands, Type resultType, CalculationT &&calculate) { return constFoldUnaryOpConditional( operands, resultType, [&](ElementValueT a) -> std::optional { return calculate(a); }); } template > Attribute constFoldUnaryOp(ArrayRef operands, CalculationT &&calculate) { return constFoldUnaryOpConditional( operands, [&](ElementValueT a) -> std::optional { return calculate(a); }); } template < class AttrElementT, class TargetAttrElementT, class ElementValueT = typename AttrElementT::ValueType, class TargetElementValueT = typename TargetAttrElementT::ValueType, class PoisonAttr = ub::PoisonAttr, class CalculationT = function_ref> Attribute constFoldCastOp(ArrayRef operands, Type resType, CalculationT &&calculate) { if (!llvm::getSingleElement(operands)) return {}; static_assert( std::is_void_v || !llvm::is_incomplete_v, "PoisonAttr is undefined, either add a dependency on UB dialect or pass " "void as template argument to opt-out from poison semantics."); if constexpr (!std::is_void_v) { if (isa(operands[0])) return operands[0]; } if (isa(operands[0])) { auto op = cast(operands[0]); bool castStatus = true; auto res = calculate(op.getValue(), castStatus); if (!castStatus) return {}; return TargetAttrElementT::get(resType, res); } if (isa(operands[0])) { // The operand is a splat so we can avoid expanding the values out and // just fold based on the splat value. auto op = cast(operands[0]); bool castStatus = true; auto elementResult = calculate(op.getSplatValue(), castStatus); if (!castStatus) return {}; auto shapedResType = cast(resType); if (!shapedResType.hasStaticShape()) return {}; return DenseElementsAttr::get(shapedResType, elementResult); } if (auto op = dyn_cast(operands[0])) { // Operand is ElementsAttr-derived; perform an element-wise fold by // expanding the value. bool castStatus = true; auto maybeOpIt = op.try_value_begin(); if (!maybeOpIt) return {}; auto opIt = *maybeOpIt; SmallVector elementResults; elementResults.reserve(op.getNumElements()); for (size_t i = 0, e = op.getNumElements(); i < e; ++i, ++opIt) { auto elt = calculate(*opIt, castStatus); if (!castStatus) return {}; elementResults.push_back(elt); } return DenseElementsAttr::get(cast(resType), elementResults); } return {}; } } // namespace mlir #endif // MLIR_DIALECT_COMMONFOLDERS_H