|
11 | 11 | // |
12 | 12 | //===----------------------------------------------------------------------===// |
13 | 13 | #include "mlir/Dialect/LLVMIR/LLVMDialect.h" |
| 14 | +#include "LLVMInlining.h" |
14 | 15 | #include "TypeDetail.h" |
15 | 16 | #include "mlir/Dialect/LLVMIR/LLVMAttrs.h" |
16 | 17 | #include "mlir/Dialect/LLVMIR/LLVMInterfaces.h" |
|
22 | 23 | #include "mlir/IR/FunctionImplementation.h" |
23 | 24 | #include "mlir/IR/MLIRContext.h" |
24 | 25 | #include "mlir/IR/Matchers.h" |
25 | | -#include "mlir/Transforms/InliningUtils.h" |
26 | 26 |
|
27 | 27 | #include "llvm/ADT/SCCIterator.h" |
28 | 28 | #include "llvm/ADT/TypeSwitch.h" |
@@ -2777,237 +2777,6 @@ struct LLVMOpAsmDialectInterface : public OpAsmDialectInterface { |
2777 | 2777 | }; |
2778 | 2778 | } // namespace |
2779 | 2779 |
|
2780 | | -//===----------------------------------------------------------------------===// |
2781 | | -// DialectInlinerInterface |
2782 | | -//===----------------------------------------------------------------------===// |
2783 | | - |
2784 | | -/// Check whether the given alloca is an input to a lifetime intrinsic, |
2785 | | -/// optionally passing through one or more casts on the way. This is not |
2786 | | -/// transitive through block arguments. |
2787 | | -static bool hasLifetimeMarkers(LLVM::AllocaOp allocaOp) { |
2788 | | - SmallVector<Operation *> stack(allocaOp->getUsers().begin(), |
2789 | | - allocaOp->getUsers().end()); |
2790 | | - while (!stack.empty()) { |
2791 | | - Operation *op = stack.pop_back_val(); |
2792 | | - if (isa<LLVM::LifetimeStartOp, LLVM::LifetimeEndOp>(op)) |
2793 | | - return true; |
2794 | | - if (isa<LLVM::BitcastOp>(op)) |
2795 | | - stack.append(op->getUsers().begin(), op->getUsers().end()); |
2796 | | - } |
2797 | | - return false; |
2798 | | -} |
2799 | | - |
2800 | | -/// Move all alloca operations with a constant size in the former entry block of |
2801 | | -/// the newly inlined callee into the entry block of the caller, and insert |
2802 | | -/// lifetime intrinsics that limit their scope to the inlined blocks. |
2803 | | -static void moveConstantAllocasToEntryBlock( |
2804 | | - iterator_range<Region::iterator> inlinedBlocks) { |
2805 | | - Block *calleeEntryBlock = &(*inlinedBlocks.begin()); |
2806 | | - Block *callerEntryBlock = &(*calleeEntryBlock->getParent()->begin()); |
2807 | | - if (calleeEntryBlock == callerEntryBlock) |
2808 | | - // Nothing to do. |
2809 | | - return; |
2810 | | - SmallVector<std::tuple<LLVM::AllocaOp, IntegerAttr, bool>> allocasToMove; |
2811 | | - bool shouldInsertLifetimes = false; |
2812 | | - // Conservatively only move alloca operations that are part of the entry block |
2813 | | - // and do not inspect nested regions, since they may execute conditionally or |
2814 | | - // have other unknown semantics. |
2815 | | - for (auto allocaOp : calleeEntryBlock->getOps<LLVM::AllocaOp>()) { |
2816 | | - IntegerAttr arraySize; |
2817 | | - if (!matchPattern(allocaOp.getArraySize(), m_Constant(&arraySize))) |
2818 | | - continue; |
2819 | | - bool shouldInsertLifetime = |
2820 | | - arraySize.getValue() != 0 && !hasLifetimeMarkers(allocaOp); |
2821 | | - shouldInsertLifetimes |= shouldInsertLifetime; |
2822 | | - allocasToMove.emplace_back(allocaOp, arraySize, shouldInsertLifetime); |
2823 | | - } |
2824 | | - if (allocasToMove.empty()) |
2825 | | - return; |
2826 | | - OpBuilder builder(callerEntryBlock, callerEntryBlock->begin()); |
2827 | | - for (auto &[allocaOp, arraySize, shouldInsertLifetime] : allocasToMove) { |
2828 | | - auto newConstant = builder.create<LLVM::ConstantOp>( |
2829 | | - allocaOp->getLoc(), allocaOp.getArraySize().getType(), arraySize); |
2830 | | - // Insert a lifetime start intrinsic where the alloca was before moving it. |
2831 | | - if (shouldInsertLifetime) { |
2832 | | - OpBuilder::InsertionGuard insertionGuard(builder); |
2833 | | - builder.setInsertionPoint(allocaOp); |
2834 | | - builder.create<LLVM::LifetimeStartOp>( |
2835 | | - allocaOp.getLoc(), arraySize.getValue().getLimitedValue(), |
2836 | | - allocaOp.getResult()); |
2837 | | - } |
2838 | | - allocaOp->moveAfter(newConstant); |
2839 | | - allocaOp.getArraySizeMutable().assign(newConstant.getResult()); |
2840 | | - } |
2841 | | - if (!shouldInsertLifetimes) |
2842 | | - return; |
2843 | | - // Insert a lifetime end intrinsic before each return in the callee function. |
2844 | | - for (Block &block : inlinedBlocks) { |
2845 | | - if (!block.getTerminator()->hasTrait<OpTrait::ReturnLike>()) |
2846 | | - continue; |
2847 | | - builder.setInsertionPoint(block.getTerminator()); |
2848 | | - for (auto &[allocaOp, arraySize, shouldInsertLifetime] : allocasToMove) { |
2849 | | - if (!shouldInsertLifetime) |
2850 | | - continue; |
2851 | | - builder.create<LLVM::LifetimeEndOp>( |
2852 | | - allocaOp.getLoc(), arraySize.getValue().getLimitedValue(), |
2853 | | - allocaOp.getResult()); |
2854 | | - } |
2855 | | - } |
2856 | | -} |
2857 | | - |
2858 | | -static Value handleByValArgument(OpBuilder &builder, Operation *callable, |
2859 | | - Value argument, |
2860 | | - NamedAttribute byValAttribute) { |
2861 | | - auto func = cast<LLVM::LLVMFuncOp>(callable); |
2862 | | - LLVM::MemoryEffectsAttr memoryEffects = func.getMemoryAttr(); |
2863 | | - // If there is no memory effects attribute, assume that the function is |
2864 | | - // not read-only. |
2865 | | - bool isReadOnly = memoryEffects && |
2866 | | - memoryEffects.getArgMem() != ModRefInfo::ModRef && |
2867 | | - memoryEffects.getArgMem() != ModRefInfo::Mod; |
2868 | | - if (isReadOnly) |
2869 | | - return argument; |
2870 | | - // Resolve the pointee type and its size. |
2871 | | - auto ptrType = cast<LLVM::LLVMPointerType>(argument.getType()); |
2872 | | - Type elementType = cast<TypeAttr>(byValAttribute.getValue()).getValue(); |
2873 | | - unsigned int typeSize = |
2874 | | - DataLayout(callable->getParentOfType<DataLayoutOpInterface>()) |
2875 | | - .getTypeSize(elementType); |
2876 | | - // Allocate the new value on the stack. |
2877 | | - Value one = builder.create<LLVM::ConstantOp>( |
2878 | | - func.getLoc(), builder.getI64Type(), builder.getI64IntegerAttr(1)); |
2879 | | - Value allocaOp = |
2880 | | - builder.create<LLVM::AllocaOp>(func.getLoc(), ptrType, elementType, one); |
2881 | | - // Copy the pointee to the newly allocated value. |
2882 | | - Value copySize = builder.create<LLVM::ConstantOp>( |
2883 | | - func.getLoc(), builder.getI64Type(), builder.getI64IntegerAttr(typeSize)); |
2884 | | - Value isVolatile = builder.create<LLVM::ConstantOp>( |
2885 | | - func.getLoc(), builder.getI1Type(), builder.getBoolAttr(false)); |
2886 | | - builder.create<LLVM::MemcpyOp>(func.getLoc(), allocaOp, argument, copySize, |
2887 | | - isVolatile); |
2888 | | - return allocaOp; |
2889 | | -} |
2890 | | - |
2891 | | -namespace { |
2892 | | -struct LLVMInlinerInterface : public DialectInlinerInterface { |
2893 | | - using DialectInlinerInterface::DialectInlinerInterface; |
2894 | | - |
2895 | | - bool isLegalToInline(Operation *call, Operation *callable, |
2896 | | - bool wouldBeCloned) const final { |
2897 | | - if (!wouldBeCloned) |
2898 | | - return false; |
2899 | | - auto callOp = dyn_cast<LLVM::CallOp>(call); |
2900 | | - auto funcOp = dyn_cast<LLVM::LLVMFuncOp>(callable); |
2901 | | - if (!callOp || !funcOp) |
2902 | | - return false; |
2903 | | - if (auto attrs = funcOp.getArgAttrs()) { |
2904 | | - for (Attribute attr : *attrs) { |
2905 | | - auto attrDict = cast<DictionaryAttr>(attr); |
2906 | | - for (NamedAttribute attr : attrDict) { |
2907 | | - if (attr.getName() == LLVMDialect::getByValAttrName()) |
2908 | | - continue; |
2909 | | - // TODO: Handle all argument attributes; |
2910 | | - return false; |
2911 | | - } |
2912 | | - } |
2913 | | - } |
2914 | | - // TODO: Handle result attributes; |
2915 | | - if (funcOp.getResAttrs()) |
2916 | | - return false; |
2917 | | - // TODO: Handle exceptions. |
2918 | | - if (funcOp.getPersonality()) |
2919 | | - return false; |
2920 | | - if (funcOp.getPassthrough()) { |
2921 | | - // TODO: Used attributes should not be passthrough. |
2922 | | - DenseSet<StringAttr> disallowed( |
2923 | | - {StringAttr::get(funcOp->getContext(), "noduplicate"), |
2924 | | - StringAttr::get(funcOp->getContext(), "noinline"), |
2925 | | - StringAttr::get(funcOp->getContext(), "optnone"), |
2926 | | - StringAttr::get(funcOp->getContext(), "presplitcoroutine"), |
2927 | | - StringAttr::get(funcOp->getContext(), "returns_twice"), |
2928 | | - StringAttr::get(funcOp->getContext(), "strictfp")}); |
2929 | | - if (llvm::any_of(*funcOp.getPassthrough(), [&](Attribute attr) { |
2930 | | - auto stringAttr = dyn_cast<StringAttr>(attr); |
2931 | | - if (!stringAttr) |
2932 | | - return false; |
2933 | | - return disallowed.contains(stringAttr); |
2934 | | - })) |
2935 | | - return false; |
2936 | | - } |
2937 | | - return true; |
2938 | | - } |
2939 | | - |
2940 | | - bool isLegalToInline(Region *, Region *, bool, IRMapping &) const final { |
2941 | | - return true; |
2942 | | - } |
2943 | | - |
2944 | | - /// Conservative allowlist of operations supported so far. |
2945 | | - bool isLegalToInline(Operation *op, Region *, bool, IRMapping &) const final { |
2946 | | - if (isPure(op)) |
2947 | | - return true; |
2948 | | - // Some attributes on memory operations require handling during |
2949 | | - // inlining. Since this is not yet implemented, refuse to inline memory |
2950 | | - // operations that have any of these attributes. |
2951 | | - if (auto iface = dyn_cast<AliasAnalysisOpInterface>(op)) |
2952 | | - if (iface.getAliasScopesOrNull() || iface.getNoAliasScopesOrNull()) |
2953 | | - return false; |
2954 | | - if (auto iface = dyn_cast<AccessGroupOpInterface>(op)) |
2955 | | - if (iface.getAccessGroupsOrNull()) |
2956 | | - return false; |
2957 | | - return isa<LLVM::CallOp, LLVM::AllocaOp, LLVM::LifetimeStartOp, |
2958 | | - LLVM::LifetimeEndOp, LLVM::LoadOp, LLVM::StoreOp>(op); |
2959 | | - } |
2960 | | - |
2961 | | - /// Handle the given inlined return by replacing it with a branch. This |
2962 | | - /// overload is called when the inlined region has more than one block. |
2963 | | - void handleTerminator(Operation *op, Block *newDest) const final { |
2964 | | - // Only return needs to be handled here. |
2965 | | - auto returnOp = dyn_cast<LLVM::ReturnOp>(op); |
2966 | | - if (!returnOp) |
2967 | | - return; |
2968 | | - |
2969 | | - // Replace the return with a branch to the dest. |
2970 | | - OpBuilder builder(op); |
2971 | | - builder.create<LLVM::BrOp>(op->getLoc(), returnOp.getOperands(), newDest); |
2972 | | - op->erase(); |
2973 | | - } |
2974 | | - |
2975 | | - /// Handle the given inlined return by replacing the uses of the call with the |
2976 | | - /// operands of the return. This overload is called when the inlined region |
2977 | | - /// only contains one block. |
2978 | | - void handleTerminator(Operation *op, |
2979 | | - ArrayRef<Value> valuesToRepl) const final { |
2980 | | - // Return will be the only terminator present. |
2981 | | - auto returnOp = cast<LLVM::ReturnOp>(op); |
2982 | | - |
2983 | | - // Replace the values directly with the return operands. |
2984 | | - assert(returnOp.getNumOperands() == valuesToRepl.size()); |
2985 | | - for (const auto &[dst, src] : |
2986 | | - llvm::zip(valuesToRepl, returnOp.getOperands())) |
2987 | | - dst.replaceAllUsesWith(src); |
2988 | | - } |
2989 | | - |
2990 | | - Value handleArgument(OpBuilder &builder, Operation *call, Operation *callable, |
2991 | | - Value argument, Type targetType, |
2992 | | - DictionaryAttr argumentAttrs) const final { |
2993 | | - if (auto attr = argumentAttrs.getNamed(LLVMDialect::getByValAttrName())) |
2994 | | - return handleByValArgument(builder, callable, argument, *attr); |
2995 | | - return argument; |
2996 | | - } |
2997 | | - |
2998 | | - void processInlinedCallBlocks( |
2999 | | - Operation *call, |
3000 | | - iterator_range<Region::iterator> inlinedBlocks) const override { |
3001 | | - // Alloca operations with a constant size that were in the entry block of |
3002 | | - // the callee should be moved to the entry block of the caller, as this will |
3003 | | - // fold into prologue/epilogue code during code generation. |
3004 | | - // This is not implemented as a standalone pattern because we need to know |
3005 | | - // which newly inlined block was previously the entry block of the callee. |
3006 | | - moveConstantAllocasToEntryBlock(inlinedBlocks); |
3007 | | - } |
3008 | | -}; |
3009 | | -} // end anonymous namespace |
3010 | | - |
3011 | 2780 | //===----------------------------------------------------------------------===// |
3012 | 2781 | // LLVMDialect initialization, type parsing, and registration. |
3013 | 2782 | //===----------------------------------------------------------------------===// |
@@ -3037,9 +2806,9 @@ void LLVMDialect::initialize() { |
3037 | 2806 | // Support unknown operations because not all LLVM operations are registered. |
3038 | 2807 | allowUnknownOperations(); |
3039 | 2808 | // clang-format off |
3040 | | - addInterfaces<LLVMOpAsmDialectInterface, |
3041 | | - LLVMInlinerInterface>(); |
| 2809 | + addInterfaces<LLVMOpAsmDialectInterface>(); |
3042 | 2810 | // clang-format on |
| 2811 | + detail::addLLVMInlinerInterface(this); |
3043 | 2812 | } |
3044 | 2813 |
|
3045 | 2814 | #define GET_OP_CLASSES |
|
0 commit comments