Skip to content

Commit ba30cf4

Browse files
committed
[MLIR][LLVM] Move the LLVM inliner interface into a separate file.
A fully fledged LLVM inliner will require a lot of logic. Since `LLVMDialect.cpp` is large enough as it is, preemptively outline the inlining logic into a separate `.cpp` file. This will also allow us to add a `DEBUG_TYPE` for debugging the inliner. The name `LLVMInlining` was chosen over `LLVMInlinerInterface` to keep the option open for exposing inlining functionality even when not invoked through the `DialectInlinerInterface`. Depends on D146616 Differential Revision: https://reviews.llvm.org/D146628
1 parent ed114b6 commit ba30cf4

File tree

4 files changed

+287
-234
lines changed

4 files changed

+287
-234
lines changed

mlir/lib/Dialect/LLVMIR/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ add_mlir_dialect_library(MLIRLLVMDialect
44
IR/FunctionCallUtils.cpp
55
IR/LLVMAttrs.cpp
66
IR/LLVMDialect.cpp
7+
IR/LLVMInlining.cpp
78
IR/LLVMInterfaces.cpp
89
IR/LLVMTypes.cpp
910
IR/LLVMTypeSyntax.cpp

mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp

Lines changed: 3 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//
1212
//===----------------------------------------------------------------------===//
1313
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
14+
#include "LLVMInlining.h"
1415
#include "TypeDetail.h"
1516
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
1617
#include "mlir/Dialect/LLVMIR/LLVMInterfaces.h"
@@ -22,7 +23,6 @@
2223
#include "mlir/IR/FunctionImplementation.h"
2324
#include "mlir/IR/MLIRContext.h"
2425
#include "mlir/IR/Matchers.h"
25-
#include "mlir/Transforms/InliningUtils.h"
2626

2727
#include "llvm/ADT/SCCIterator.h"
2828
#include "llvm/ADT/TypeSwitch.h"
@@ -2777,237 +2777,6 @@ struct LLVMOpAsmDialectInterface : public OpAsmDialectInterface {
27772777
};
27782778
} // namespace
27792779

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-
30112780
//===----------------------------------------------------------------------===//
30122781
// LLVMDialect initialization, type parsing, and registration.
30132782
//===----------------------------------------------------------------------===//
@@ -3037,9 +2806,9 @@ void LLVMDialect::initialize() {
30372806
// Support unknown operations because not all LLVM operations are registered.
30382807
allowUnknownOperations();
30392808
// clang-format off
3040-
addInterfaces<LLVMOpAsmDialectInterface,
3041-
LLVMInlinerInterface>();
2809+
addInterfaces<LLVMOpAsmDialectInterface>();
30422810
// clang-format on
2811+
detail::addLLVMInlinerInterface(this);
30432812
}
30442813

30452814
#define GET_OP_CLASSES

0 commit comments

Comments
 (0)