18#include "mlir/Dialect/Arith/IR/Arith.h"
19#include "mlir/Dialect/GPU/IR/GPUDialect.h"
20#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
21#include "mlir/Dialect/MemRef/IR/MemRef.h"
23#include "mlir/IR/Dominance.h"
24#include "mlir/IR/Matchers.h"
25#include "mlir/IR/PatternMatch.h"
26#include "mlir/Interfaces/SideEffectInterfaces.h"
27#include "mlir/Interfaces/ViewLikeInterface.h"
32#define GEN_PASS_DEF_LOWERLLVMEXTPASS
33#include "Passes/Passes.h.inc"
39using namespace enzyme;
51static constexpr StringLiteral kEnzymeXLADialect =
"enzymexla";
52static constexpr StringLiteral kPointer2Memref =
"enzymexla.pointer2memref";
53static constexpr StringLiteral kMemref2Pointer =
"enzymexla.memref2pointer";
54static constexpr StringLiteral kEnzymeXLAMemcpy =
"enzymexla.memcpy";
58static MemRefType deviceBufferType(OpBuilder &builder,
unsigned memorySpace) {
59 return MemRefType::get({ShapedType::kDynamic}, builder.getI8Type(),
60 MemRefLayoutAttrInterface{},
61 builder.getI64IntegerAttr(memorySpace));
64static Operation *createByName(OpBuilder &builder, Location loc, StringRef name,
65 ValueRange operands, TypeRange results) {
66 return builder.create(loc, StringAttr::get(builder.getContext(), name),
71static Value pointerToMemref(OpBuilder &builder, Location loc, Value ptr) {
72 unsigned memorySpace =
73 cast<LLVM::LLVMPointerType>(ptr.getType()).getAddressSpace();
74 return createByName(builder, loc, kPointer2Memref, ValueRange{ptr},
75 TypeRange{deviceBufferType(builder, memorySpace)})
81static Value memrefToPointer(OpBuilder &builder, Location loc, Value memref,
83 return createByName(builder, loc, kMemref2Pointer, ValueRange{memref},
94static LogicalResult checkEnzymeXLAAvailable(Operation *op) {
95 MLIRContext *ctx = op->getContext();
96 if (!ctx->getLoadedDialect(kEnzymeXLADialect) &&
97 !ctx->allowsUnregisteredDialects()) {
98 op->emitError() <<
"lowering this op requires the '" << kEnzymeXLADialect
99 <<
"' dialect to be loaded";
105static LogicalResult lowerAllocDevice(llvm_ext::AllocOp alloc) {
106 if (failed(checkEnzymeXLAAvailable(alloc)))
109 OpBuilder builder(alloc);
110 Location loc = alloc.getLoc();
112 Value size = arith::IndexCastOp::create(builder, loc, builder.getIndexType(),
115 gpu::AllocOp::create(builder, loc,
116 deviceBufferType(builder, alloc.getMemorySpace()),
117 (mlir::Type)
nullptr,
123 alloc.getResult().replaceAllUsesWith(
124 memrefToPointer(builder, loc, buffer, alloc.getResult().getType()));
129static LogicalResult lowerFreeDevice(llvm_ext::FreeOp free) {
130 if (failed(checkEnzymeXLAAvailable(free)))
133 OpBuilder builder(free);
134 Location loc = free.getLoc();
136 Value buffer = pointerToMemref(builder, loc, free.getPtr());
137 gpu::DeallocOp::create(builder, loc, (mlir::Type)
nullptr,
138 ValueRange{}, buffer);
147static LogicalResult lowerMemcpy(llvm_ext::MemcpyOp memcpy) {
148 if (failed(checkEnzymeXLAAvailable(memcpy)))
151 OpBuilder builder(memcpy);
152 Location loc = memcpy.getLoc();
156 Value size = arith::IndexCastOp::create(builder, loc, builder.getIndexType(),
158 Value dst = pointerToMemref(builder, loc, memcpy.getDst());
159 Value src = pointerToMemref(builder, loc, memcpy.getSrc());
160 createByName(builder, loc, kEnzymeXLAMemcpy, ValueRange{dst, src, size},
166LogicalResult tryLoweringToAlloca(llvm_ext::AllocOp alloc,
167 uint64_t staticThreshold) {
170 if (alloc.getMemorySpace() != 0)
174 if (!matchPattern(alloc.getSize(), m_ConstantInt(&size)))
177 if (size.getZExtValue() > staticThreshold)
180 Operation *free =
nullptr;
181 for (
auto user : alloc.getResult().getUsers()) {
182 auto oFree = dyn_cast<llvm_ext::FreeOp>(user);
189 if (oFree->getBlock() != alloc->getBlock())
194 if (!alloc->isBeforeInBlock(free))
205 OpBuilder builder(alloc);
206 auto alloca = LLVM::AllocaOp::create(builder, alloc.getLoc(),
207 alloc.getResult().getType(),
208 builder.getI8Type(), alloc.getSize());
209 alloc.getResult().replaceAllUsesWith(alloca.getResult());
211 LLVM::LifetimeStartOp::create(builder, alloc.getLoc(), alloca.getResult());
213 builder.setInsertionPoint(free);
214 LLVM::LifetimeEndOp::create(builder, alloc.getLoc(), alloca.getResult());
222LogicalResult lowerAlloc(llvm_ext::AllocOp alloc, uint64_t staticThreshold) {
223 if (tryLoweringToAlloca(alloc, staticThreshold).succeeded())
226 if (alloc.getMemorySpace() != 0)
227 return lowerAllocDevice(alloc);
229 SymbolTable symtable(SymbolTable::getNearestSymbolTable(alloc));
231 auto mallocFn = symtable.lookup<LLVM::LLVMFuncOp>(
"malloc");
233 Block *b = &symtable.getOp()->getRegion(0).front();
234 OpBuilder builder(b, b->begin());
236 auto fnType = mlir::LLVM::LLVMFunctionType::get(
237 LLVM::LLVMPointerType::get(alloc.getContext()), builder.getI64Type(),
241 LLVM::LLVMFuncOp::create(builder, alloc.getLoc(),
"malloc", fnType);
244 OpBuilder builder(alloc);
245 auto mallocCall = LLVM::CallOp::create(builder, alloc.getLoc(), mallocFn,
246 alloc->getOperands());
247 alloc.getResult().replaceAllUsesWith(mallocCall.getResult());
252LogicalResult lowerFree(llvm_ext::FreeOp free) {
253 if (free.getMemorySpace() != 0)
254 return lowerFreeDevice(free);
256 SymbolTable symtable(SymbolTable::getNearestSymbolTable(free));
258 auto freeFn = symtable.lookup<LLVM::LLVMFuncOp>(
"free");
260 Block *b = &symtable.getOp()->getRegion(0).front();
261 OpBuilder builder(b, b->begin());
263 auto fnType = mlir::LLVM::LLVMFunctionType::get(
264 LLVM::LLVMVoidType::get(free.getContext()),
265 LLVM::LLVMPointerType::get(free.getContext()),
268 freeFn = LLVM::LLVMFuncOp::create(builder, free.getLoc(),
"free", fnType);
271 OpBuilder builder(free);
272 LLVM::CallOp::create(builder, free.getLoc(), freeFn, free->getOperands());
286static void resolveDimsFromHint(llvm_ext::PtrSizeHintOp psh,
287 DominanceInfo &dom) {
288 Value ptr = psh.getPtr();
289 Value byteSize = psh.getSize();
291 for (Operation *user : llvm::make_early_inc_range(ptr.getUsers())) {
294 auto view = dyn_cast<ViewLikeOpInterface>(user);
295 if (!view || view.getViewSource() != ptr || !isMemoryEffectFree(user))
297 auto MT = dyn_cast<MemRefType>(user->getResult(0).getType());
298 if (!MT || MT.getRank() != 1 || !MT.isDynamicDim(0))
300 if (!MT.getElementType().isIntOrFloat())
302 unsigned bits = MT.getElementTypeBitWidth();
303 if (bits == 0 || bits % 8 != 0)
306 for (Operation *dimUser :
307 llvm::make_early_inc_range(user->getResult(0).getUsers())) {
308 auto dim = dyn_cast<memref::DimOp>(dimUser);
309 if (!dim || dim.getConstantIndex() != 0)
312 if (!dom.properlyDominates(byteSize, dim))
315 OpBuilder builder(dim);
316 Value extent = arith::IndexCastOp::create(
317 builder, dim.getLoc(), builder.getIndexType(), byteSize);
318 if (
unsigned bytes = bits / 8; bytes != 1) {
320 arith::ConstantIndexOp::create(builder, dim.getLoc(), bytes);
322 arith::DivUIOp::create(builder, dim.getLoc(), extent, elemSize);
324 dim.getResult().replaceAllUsesWith(extent);
330struct LowerLLVMExtPass
331 :
public enzyme::impl::LowerLLVMExtPassBase<LowerLLVMExtPass> {
332 using LowerLLVMExtPassBase::LowerLLVMExtPassBase;
334 void runOnOperation()
override {
335 Operation *op = getOperation();
337 DominanceInfo dom(op);
338 op->walk([&](llvm_ext::PtrSizeHintOp psh) {
339 resolveDimsFromHint(psh, dom);
343 SmallVector<llvm_ext::AllocOp> allocs;
344 op->walk([&](llvm_ext::AllocOp alloc) { allocs.push_back(alloc); });
346 for (
auto alloc : allocs)
347 if (failed(lowerAlloc(alloc, lowerToAllocaThreshold)))
348 return signalPassFailure();
352 SmallVector<llvm_ext::MemcpyOp> memcpys;
353 op->walk([&](llvm_ext::MemcpyOp memcpy) { memcpys.push_back(memcpy); });
355 for (
auto memcpy : memcpys)
356 if (failed(lowerMemcpy(memcpy)))
357 return signalPassFailure();
359 SmallVector<llvm_ext::FreeOp> frees;
360 op->walk([&](llvm_ext::FreeOp free) { frees.push_back(free); });
362 for (
auto free : frees)
363 if (failed(lowerFree(free)))
364 return signalPassFailure();