Skip to content

Commit 56d68e8

Browse files
[mlir][bufferization] Add optional copy operand to AllocTensorOp
If `copy` is specified, the newly allocated buffer is initialized with the given contents. Also add an optional `escape` attribute to indicate whether the buffer of the tensor may be returned from the parent block (aka. "escape") after bufferization. This change is in preparation of connecting One-Shot Bufferize to the sparse compiler. Differential Revision: https://reviews.llvm.org/D126570
1 parent 9617ffc commit 56d68e8

File tree

8 files changed

+241
-27
lines changed

8 files changed

+241
-27
lines changed

mlir/include/mlir/Dialect/Bufferization/IR/BufferizationOps.td

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,24 @@ class Bufferization_Op<string mnemonic, list<Trait> traits = []>
2424
//===----------------------------------------------------------------------===//
2525

2626
def Bufferization_AllocTensorOp : Bufferization_Op<"alloc_tensor",
27-
[BufferizableOpInterface,
27+
[AttrSizedOperandSegments, BufferizableOpInterface,
2828
DeclareOpInterfaceMethods<ReifyRankedShapedTypeOpInterface>]> {
2929
let summary = "buffer allocation in tensor land";
3030

3131
let description = [{
3232
`bufferization.alloc_tensor` materializes an uninitialized tensor with a
3333
given shape (dynamic or static). It always bufferizes to a new buffer
34-
allocation of the given shape. Reading from the result of an `alloc_tensor`
35-
op yields an undefined value.
34+
allocation of the given shape. The optional `copy` operand specifies the
35+
contents of the tensors. If no `copy` operand is specified, reading from the
36+
result of an `alloc_tensor` op yields an undefined value.
37+
38+
If `copy` is specified, no dynamic sizes should be passed, since they are
39+
the same as the dynamic sizes of the `copy` operand.
40+
41+
The optional `escape` attribute indicates whether the buffer escapes the
42+
parent block or not. In the latter case, the buffer is deallocated at the
43+
of the block (during bufferization). In the former case, the buffer is not
44+
deallocated and must be deallocated through some other mechanism.
3645

3746
`alloc_tensor` is a helper op for bufferization. The operation is provided
3847
as an anchor that marks the beginning of a new tensor SSA use-def chain. It
@@ -55,19 +64,25 @@ def Bufferization_AllocTensorOp : Bufferization_Op<"alloc_tensor",
5564
```
5665
}];
5766

58-
let arguments = (ins Variadic<Index>:$dynamicSizes);
67+
let arguments = (ins Variadic<Index>:$dynamicSizes,
68+
Optional<AnyTensor>:$copy,
69+
OptionalAttr<BoolAttr>:$escape);
5970

6071
let results = (outs AnyTensor:$result);
6172

62-
let assemblyFormat = "`(`$dynamicSizes`)` attr-dict `:` type($result)";
63-
6473
let extraClassDeclaration = [{
6574
LogicalResult bufferize(RewriterBase &rewriter, BufferizationState &state);
6675

67-
bool isMemoryWrite(OpResult opResult, const AnalysisState &state) const {
68-
// AllocTensorOps allocate but do not write.
69-
return false;
70-
}
76+
bool isMemoryWrite(OpResult opResult, const AnalysisState &state);
77+
78+
bool bufferizesToMemoryRead(OpOperand &opOperand,
79+
const AnalysisState &state);
80+
81+
bool bufferizesToMemoryWrite(OpOperand &opOperand,
82+
const AnalysisState &state);
83+
84+
SmallVector<OpResult> getAliasingOpResult(
85+
OpOperand &opOperand, const AnalysisState &state);
7186

7287
RankedTensorType getType() {
7388
return getResult().getType().cast<RankedTensorType>();
@@ -82,6 +97,7 @@ def Bufferization_AllocTensorOp : Bufferization_Op<"alloc_tensor",
8297
// the tensor at dimension `idx`. Asserts that the shape is
8398
// dynamic at that `idx`.
8499
unsigned getIndexOfDynamicSize(unsigned idx) {
100+
assert(!copy() && "no dim sizes specified when copying a tensor");
85101
assert(isDynamicDim(idx) && "expected dynamic size");
86102
ArrayRef<int64_t> shape = getType().getShape();
87103
return std::count_if(
@@ -91,9 +107,7 @@ def Bufferization_AllocTensorOp : Bufferization_Op<"alloc_tensor",
91107

92108
// Return the Value of the dynamic size of the tensor at dimension
93109
// `idx`. Asserts that the shape is dynamic at that `idx.
94-
Value getDynamicSize(unsigned idx) {
95-
return getOperand(getIndexOfDynamicSize(idx));
96-
}
110+
Value getDynamicSize(OpBuilder &b, unsigned idx);
97111

98112
// Assert that the size of the result tensor is static at `idx`
99113
// and return the shape.
@@ -103,7 +117,21 @@ def Bufferization_AllocTensorOp : Bufferization_Op<"alloc_tensor",
103117
}
104118
}];
105119

120+
let builders = [
121+
// Build an op without `copy` operand and `escape` attribute.
122+
OpBuilder<(ins "RankedTensorType":$type, "ValueRange":$dynamicSizes)>,
123+
124+
// Build an op without `escape` attribute.
125+
OpBuilder<(ins "RankedTensorType":$type, "ValueRange":$dynamicSizes,
126+
"Value":$copy)>,
127+
128+
// Build an op with `copy` and `escape` attribute.
129+
OpBuilder<(ins "RankedTensorType":$type, "ValueRange":$dynamicSizes,
130+
"Value":$copy, "bool":$escape)>,
131+
];
132+
106133
let hasCanonicalizer = 1;
134+
let hasCustomAssemblyFormat = 1;
107135
let hasVerifier = 1;
108136
}
109137

mlir/lib/Dialect/Bufferization/IR/BufferizationOps.cpp

Lines changed: 126 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,21 +139,83 @@ LogicalResult AllocTensorOp::bufferize(RewriterBase &rewriter,
139139
if (getOperation()->getUses().empty())
140140
return success();
141141

142-
FailureOr<Value> alloc = state.createAlloc(rewriter, getLoc(), getResult());
142+
Optional<bool> dealloc = llvm::None;
143+
if (escape().hasValue())
144+
dealloc = !*escape();
145+
FailureOr<Value> alloc =
146+
state.createAlloc(rewriter, getLoc(), getResult(), dealloc);
143147
if (failed(alloc))
144148
return failure();
149+
if (copy()) {
150+
FailureOr<Value> copyValueBuffer = state.getBuffer(
151+
rewriter, getOperation()->getOpOperand(getNumOperands() - 1));
152+
if (failed(copyValueBuffer))
153+
return failure();
154+
if (failed(state.getOptions().createMemCpy(rewriter, getLoc(),
155+
*copyValueBuffer, *alloc)))
156+
return failure();
157+
}
145158
replaceOpWithBufferizedValues(rewriter, getOperation(), *alloc);
146159
return success();
147160
}
148161

162+
bool AllocTensorOp::isMemoryWrite(OpResult opResult,
163+
const AnalysisState &state) {
164+
// AllocTensorOps do not write unless they have a `copy` value.
165+
return static_cast<bool>(copy());
166+
}
167+
168+
bool AllocTensorOp::bufferizesToMemoryRead(OpOperand &opOperand,
169+
const AnalysisState &state) {
170+
assert(opOperand.getOperandNumber() == getNumOperands() - 1 &&
171+
"expected copy operand");
172+
return true;
173+
}
174+
175+
bool AllocTensorOp::bufferizesToMemoryWrite(OpOperand &opOperand,
176+
const AnalysisState &state) {
177+
assert(opOperand.getOperandNumber() == getNumOperands() - 1 &&
178+
"expected copy operand");
179+
return false;
180+
}
181+
182+
SmallVector<OpResult>
183+
AllocTensorOp::getAliasingOpResult(OpOperand &opOperand,
184+
const AnalysisState &state) {
185+
// This is a new allocation. It does not alias with any other buffer.
186+
return {};
187+
}
188+
149189
LogicalResult AllocTensorOp::verify() {
150-
if (getType().getNumDynamicDims() !=
151-
static_cast<int64_t>(dynamicSizes().size()))
190+
if (copy() && !dynamicSizes().empty())
191+
return emitError("dynamic sizes not needed when copying a tensor");
192+
if (!copy() && getType().getNumDynamicDims() !=
193+
static_cast<int64_t>(dynamicSizes().size()))
152194
return emitError("expected ")
153195
<< getType().getNumDynamicDims() << " dynamic sizes";
196+
if (copy() && copy().getType() != getType())
197+
return emitError("expected that `copy` and return type match");
154198
return success();
155199
}
156200

201+
void AllocTensorOp::build(OpBuilder &builder, OperationState &result,
202+
RankedTensorType type, ValueRange dynamicSizes) {
203+
build(builder, result, type, dynamicSizes, /*copy=*/Value(),
204+
/*escape=*/BoolAttr());
205+
}
206+
207+
void AllocTensorOp::build(OpBuilder &builder, OperationState &result,
208+
RankedTensorType type, ValueRange dynamicSizes,
209+
Value copy) {
210+
build(builder, result, type, dynamicSizes, copy, /*escape=*/BoolAttr());
211+
}
212+
213+
void AllocTensorOp::build(OpBuilder &builder, OperationState &result,
214+
RankedTensorType type, ValueRange dynamicSizes,
215+
Value copy, bool escape) {
216+
build(builder, result, type, dynamicSizes, copy, builder.getBoolAttr(escape));
217+
}
218+
157219
namespace {
158220
/// Change the type of the result of a `bufferization.alloc_tensor` by making
159221
/// the result type statically sized along dimension that in the original
@@ -171,6 +233,8 @@ struct ReplaceStaticShapeDims : OpRewritePattern<AllocTensorOp> {
171233

172234
LogicalResult matchAndRewrite(AllocTensorOp op,
173235
PatternRewriter &rewriter) const override {
236+
if (op.copy())
237+
return failure();
174238
SmallVector<int64_t> newShape = llvm::to_vector(op.getType().getShape());
175239
SmallVector<Value> newDynamicSizes;
176240
unsigned int dynValCounter = 0;
@@ -189,8 +253,9 @@ struct ReplaceStaticShapeDims : OpRewritePattern<AllocTensorOp> {
189253
newShape, op.getType().getElementType(), op.getType().getEncoding());
190254
if (newType == op.getType())
191255
return failure();
192-
auto newOp =
193-
rewriter.create<AllocTensorOp>(op.getLoc(), newType, newDynamicSizes);
256+
auto newOp = rewriter.create<AllocTensorOp>(
257+
op.getLoc(), newType, newDynamicSizes, /*copy=*/Value(),
258+
/*escape=*/op.escapeAttr());
194259
rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp);
195260
return success();
196261
}
@@ -207,8 +272,8 @@ struct FoldDimOfAllocTensorOp : public OpRewritePattern<tensor::DimOp> {
207272
return failure();
208273
if (!allocTensorOp.getType().isDynamicDim(*maybeConstantIndex))
209274
return failure();
210-
rewriter.replaceOp(dimOp,
211-
allocTensorOp.getDynamicSize(*maybeConstantIndex));
275+
rewriter.replaceOp(
276+
dimOp, allocTensorOp.getDynamicSize(rewriter, *maybeConstantIndex));
212277
return success();
213278
}
214279
};
@@ -224,14 +289,67 @@ LogicalResult AllocTensorOp::reifyResultShapes(
224289
auto shapes = llvm::to_vector<4>(llvm::map_range(
225290
llvm::seq<int64_t>(0, getType().getRank()), [&](int64_t dim) -> Value {
226291
if (isDynamicDim(dim))
227-
return getDynamicSize(dim);
292+
return getDynamicSize(builder, dim);
228293
return builder.create<arith::ConstantIndexOp>(getLoc(),
229294
getStaticSize(dim));
230295
}));
231296
reifiedReturnShapes.emplace_back(std::move(shapes));
232297
return success();
233298
}
234299

300+
ParseResult AllocTensorOp::parse(OpAsmParser &parser, OperationState &result) {
301+
SmallVector<OpAsmParser::UnresolvedOperand> dynamicSizesOperands;
302+
if (parser.parseLParen() || parser.parseOperandList(dynamicSizesOperands) ||
303+
parser.parseRParen())
304+
return failure();
305+
ParseResult copyKeyword = parser.parseOptionalKeyword("copy");
306+
OpAsmParser::UnresolvedOperand copyOperand;
307+
if (copyKeyword.succeeded())
308+
if (parser.parseLParen() || parser.parseOperand(copyOperand) ||
309+
parser.parseRParen())
310+
return failure();
311+
if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon())
312+
return failure();
313+
314+
TensorType type;
315+
if (parser.parseCustomTypeWithFallback(type))
316+
return failure();
317+
result.addTypes(type);
318+
319+
Type indexType = parser.getBuilder().getIndexType();
320+
if (parser.resolveOperands(dynamicSizesOperands, indexType, result.operands))
321+
return failure();
322+
if (copyKeyword.succeeded())
323+
if (parser.resolveOperand(copyOperand, type, result.operands))
324+
return failure();
325+
result.addAttribute(AllocTensorOp::getOperandSegmentSizeAttr(),
326+
parser.getBuilder().getI32VectorAttr(
327+
{static_cast<int32_t>(dynamicSizesOperands.size()),
328+
static_cast<int32_t>(copyKeyword.succeeded())}));
329+
return success();
330+
}
331+
332+
void AllocTensorOp::print(OpAsmPrinter &p) {
333+
p << "(" << dynamicSizes() << ")";
334+
if (copy())
335+
p << " copy(" << copy() << ")";
336+
p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{
337+
AllocTensorOp::getOperandSegmentSizeAttr()});
338+
p << " : ";
339+
auto type = result().getType();
340+
if (auto validType = type.dyn_cast<::mlir::TensorType>())
341+
p.printStrippedAttrOrType(validType);
342+
else
343+
p << type;
344+
}
345+
346+
Value AllocTensorOp::getDynamicSize(OpBuilder &b, unsigned idx) {
347+
assert(isDynamicDim(idx) && "expected dynamic dim");
348+
if (copy())
349+
return b.create<tensor::DimOp>(getLoc(), copy(), idx);
350+
return getOperand(getIndexOfDynamicSize(idx));
351+
}
352+
235353
//===----------------------------------------------------------------------===//
236354
// CloneOp
237355
//===----------------------------------------------------------------------===//

mlir/python/mlir/dialects/_bufferization_ops_ext.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,20 @@ class AllocTensorOp:
1818
def __init__(self,
1919
tensor_type: Type,
2020
dynamic_sizes: Sequence[Value],
21+
copy: Value,
22+
escape: BoolAttr,
2123
*,
2224
loc=None,
2325
ip=None):
2426
"""Constructs an `alloc_tensor` with static and/or dynamic sizes."""
2527
context = get_default_loc_context(loc)
28+
attributes = {}
29+
if escape:
30+
attributes["escape"] = escape
2631
op = self.build_generic(
2732
results=[tensor_type],
28-
operands=dynamic_sizes,
29-
attributes={},
33+
operands=[dynamic_sizes, copy],
34+
attributes=attributes,
3035
loc=loc,
3136
ip=ip)
3237
OpView.__init__(self, op)

mlir/test/Dialect/Bufferization/Transforms/one-shot-bufferize.mlir

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,22 @@ func.func @select_different_tensors(%t: tensor<?xf32>, %sz: index, %c: i1) -> te
119119
%1 = arith.select %c, %0, %t : tensor<?xf32>
120120
return %1 : tensor<?xf32>
121121
}
122+
123+
// -----
124+
125+
// CHECK-LABEL: func @alloc_tensor_with_copy(
126+
// CHECK-SAME: %[[t:.*]]: tensor<5xf32>)
127+
// TODO: Add a test case with dynamic dim size. This is not possible at the
128+
// moment because this would create a tensor op during bufferization. That is
129+
// currently forbidden.
130+
func.func @alloc_tensor_with_copy(%t: tensor<5xf32>) -> tensor<5xf32> {
131+
// CHECK: %[[m:.*]] = bufferization.to_memref %[[t]]
132+
// CHECK: %[[alloc:.*]] = memref.alloc() {{.*}} : memref<5xf32>
133+
// CHECK: memref.copy %[[m]], %[[alloc]]
134+
%0 = bufferization.alloc_tensor() copy(%t) : tensor<5xf32>
135+
// CHECK: %[[r:.*]] = bufferization.to_tensor %[[alloc]]
136+
// CHECK: memref.dealloc %[[alloc]]
137+
// CHECK: return %[[r]]
138+
return %0 : tensor<5xf32>
139+
}
140+

mlir/test/Dialect/Bufferization/canonicalize.mlir

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ func.func @tensor_cast_to_memref(%arg0 : tensor<4x6x16x32xi8>) ->
224224
return %1 : memref<?x?x16x32xi8>
225225
}
226226
// CHECK: %[[M:.+]] = bufferization.to_memref %[[ARG0]] : memref<4x6x16x32xi8>
227-
// CHECK: %[[M1:.+]] = memref.cast %[[M]]
227+
// CHECK: %[[M1:.+]] = memref.cast %[[M]]
228228
// CHECK-SAME: memref<4x6x16x32xi8> to memref<?x?x16x32xi8>
229229
// CHECK: return %[[M1]] : memref<?x?x16x32xi8>
230230

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
11
// RUN: mlir-opt %s -split-input-file -verify-diagnostics
22

3-
func.func @alloc_tensor_err(%arg0 : index)
3+
func.func @alloc_tensor_missing_dims(%arg0: index)
44
{
55
// expected-error @+1 {{expected 2 dynamic sizes}}
6-
%1 = bufferization.alloc_tensor(%arg0) : tensor<4x?x?x5xf32>
6+
%0 = bufferization.alloc_tensor(%arg0) : tensor<4x?x?x5xf32>
7+
return
8+
}
9+
10+
// -----
11+
12+
// expected-note @+1 {{prior use here}}
13+
func.func @alloc_tensor_type_mismatch(%t: tensor<?xf32>) {
14+
// expected-error @+1{{expects different type than prior uses: 'tensor<5xf32>' vs 'tensor<?xf32>'}}
15+
%0 = bufferization.alloc_tensor() copy(%t) : tensor<5xf32>
16+
return
17+
}
18+
19+
// -----
20+
21+
func.func @alloc_tensor_copy_and_dims(%t: tensor<?xf32>, %sz: index) {
22+
// expected-error @+1{{dynamic sizes not needed when copying a tensor}}
23+
%0 = bufferization.alloc_tensor(%sz) copy(%t) : tensor<?xf32>
24+
return
25+
}
26+
27+
// -----
28+
29+
func.func @alloc_tensor_invalid_escape_attr(%sz: index) {
30+
// expected-error @+1{{op attribute 'escape' failed to satisfy constraint: bool attribute}}
31+
%0 = bufferization.alloc_tensor(%sz) {escape = 5} : tensor<?xf32>
732
return
833
}

0 commit comments

Comments
 (0)