Enzyme main
Loading...
Searching...
No Matches
FlattenEnzymeCaches.cpp
Go to the documentation of this file.
1//===- FlattenEnzymeCaches.cpp - Flatten multi-dim enzyme memory ops -------- //
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 file implements a pass to convert multi-dimensional memory operations
10// to equivalent 1-D operations.
11//
12//===----------------------------------------------------------------------===//
13
14#include "Dialect/Ops.h"
16#include "Passes/Passes.h"
17
18#include "mlir/Dialect/Arith/IR/Arith.h"
19#include "mlir/Dialect/GPU/IR/GPUDialect.h"
20#include "mlir/Dialect/MemRef/IR/MemRef.h"
21
22using namespace mlir;
23
24namespace mlir {
25namespace enzyme {
26#define GEN_PASS_DEF_FLATTENENZYMECACHESPASS
27#include "Passes/Passes.h.inc"
28} // namespace enzyme
29} // namespace mlir
30
31namespace {
32// Recursively flatten the MemRefType for nested memrefs (e.g.
33// memref<?xmemref<?x?xf32, 1>>, which represents a host memref of pointers to
34// GPU memrefs)
35Type flattenType(Type oldType, OpBuilder &builder) {
36 if (auto memrefType = dyn_cast<MemRefType>(oldType)) {
37 return MemRefType::get({ShapedType::kDynamic},
38 flattenType(memrefType.getElementType(), builder),
39 builder.getDimIdentityMap(),
40 memrefType.getMemorySpace());
41 }
42 return oldType;
43}
44
45Value createFlatAlloc(MemRefType oldType,
46 enzyme::MultidimensionalAllocInterface allocOp,
47 ImplicitLocOpBuilder &builder) {
48 auto newType = cast<MemRefType>(flattenType(oldType, builder));
49 // Compute the size of the flattened allocation
50 unsigned opidx = 0;
51
52 SmallVector<Value> dynamicDims;
53 allocOp.appendDynamicDims(dynamicDims);
54
55 Value size = nullptr;
56 for (unsigned dim = 0; dim < oldType.getRank(); dim++) {
57 Value bound;
58 if (oldType.getDimSize(dim) == ShapedType::kDynamic) {
59 bound = dynamicDims[opidx];
60 opidx++;
61 } else {
62 bound = arith::ConstantIndexOp::create(builder, oldType.getDimSize(dim));
63 }
64 if (size == nullptr) {
65 size = bound;
66 } else {
67 size = arith::MulIOp::create(builder, size, bound);
68 }
69 }
70
71 if (size == nullptr) {
72 size = arith::ConstantIndexOp::create(builder, 0);
73 }
74
75 return allocOp.allocate(builder, builder.getLoc(), newType, size);
76}
77
78Value computeFlatIndex(ValueRange indices, ValueRange dynamicSizes,
79 ArrayRef<int64_t> oldShape,
80 ImplicitLocOpBuilder &builder) {
81 if (oldShape.size() == 0) {
82 return arith::ConstantIndexOp::create(builder, 0);
83 }
84
85 // Compute the flat index by iterating over indices in reverse
86 // We assume the caches have identity layouts, so strides can be
87 // computed from sizes.
88 Value flatIndex = indices[0];
89 int64_t dynamicIndex = 0;
90 if (oldShape[0] == ShapedType::kDynamic) {
91 dynamicIndex++;
92 }
93
94 for (int64_t dim = 1; dim < oldShape.size(); dim++) {
95 Value bound;
96 if (oldShape[dim] == ShapedType::kDynamic) {
97 bound = dynamicSizes[dynamicIndex];
98 dynamicIndex++;
99 } else {
100 bound = arith::ConstantIndexOp::create(builder, oldShape[dim]);
101 }
102
103 flatIndex = arith::MulIOp::create(builder, flatIndex, bound);
104
105 flatIndex = arith::AddIOp::create(builder, flatIndex, indices[dim]);
106 }
107
108 return flatIndex;
109}
110
111struct FlattenEnzymeCaches
112 : public enzyme::impl::FlattenEnzymeCachesPassBase<FlattenEnzymeCaches> {
113 void runOnOperation() override {
114 SetVector<enzyme::MultidimensionalAllocInterface> allocations;
115 getOperation()->walk([&allocations](enzyme::StoreOp storeOp) {
116 Operation *alloc = storeOp.getMemref().getDefiningOp();
117 if (!alloc)
118 return;
119
120 MemRefType oldType = storeOp.getMemref().getType();
121 if (oldType.getRank() <= 1)
122 return;
123
124 if (auto allocOp =
125 dyn_cast<enzyme::MultidimensionalAllocInterface>(alloc)) {
126 allocations.insert(allocOp);
127 }
128 });
129
130 for (auto alloc : llvm::make_early_inc_range(allocations)) {
131 auto oldType = cast<MemRefType>(alloc->getResultTypes().front());
132 ImplicitLocOpBuilder abuilder(alloc->getLoc(), alloc);
133 Value flatAlloc = createFlatAlloc(oldType, alloc, abuilder);
134 alloc->replaceAllUsesWith(ValueRange(flatAlloc));
135 alloc->erase();
136 }
137
138 // Update users
139 getOperation()->walk([](enzyme::StoreOp storeOp) {
140 ImplicitLocOpBuilder sbuilder(storeOp.getLoc(), storeOp);
141 Value flatIndex =
142 computeFlatIndex(storeOp.getIndices(), storeOp.getSizes(),
143 storeOp.getStaticSizes(), sbuilder);
144 memref::StoreOp::create(sbuilder, storeOp.getValue(), storeOp.getMemref(),
145 flatIndex);
146 // TODO add alignment
147 storeOp.erase();
148 });
149
150 getOperation()->walk([](enzyme::LoadOp loadOp) {
151 ImplicitLocOpBuilder lbuilder(loadOp.getLoc(), loadOp);
152 Value flatIndex = computeFlatIndex(loadOp.getIndices(), loadOp.getSizes(),
153 loadOp.getStaticSizes(), lbuilder);
154 auto flatLoad =
155 memref::LoadOp::create(lbuilder, loadOp.getMemref(), flatIndex);
156 loadOp.replaceAllUsesWith(flatLoad.getResult());
157 // TODO add alignment
158 loadOp.erase();
159 });
160 }
161};
162} // namespace