Verifier.cpp
10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
//===- Verifier.cpp - MLIR Verifier Implementation ------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements the verify() methods on the various IR types, performing
// (potentially expensive) checks on the holistic structure of the code. This
// can be used for detecting bugs in compiler transformations and hand written
// .mlir files.
//
// The checks in this file are only for things that can occur as part of IR
// transformations: e.g. violation of dominance information, malformed operation
// attributes, etc. MLIR supports transformations moving IR through locally
// invalid states (e.g. unlinking an operation from a block before re-inserting
// it in a new place), but each transformation must complete with the IR in a
// valid form.
//
// This should not check for things that are always wrong by construction (e.g.
// attributes or other immutable structures that are incorrect), because those
// are not mutable and can be checked at time of construction.
//
//===----------------------------------------------------------------------===//
#include "mlir/IR/Verifier.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Dialect.h"
#include "mlir/IR/Dominance.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/RegionKindInterface.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Regex.h"
using namespace mlir;
namespace {
/// This class encapsulates all the state used to verify an operation region.
class OperationVerifier {
public:
explicit OperationVerifier(MLIRContext *ctx) : ctx(ctx) {}
/// Verify the given operation.
LogicalResult verify(Operation &op);
/// Returns the registered dialect for a dialect-specific attribute.
Dialect *getDialectForAttribute(const NamedAttribute &attr) {
assert(attr.first.strref().contains('.') && "expected dialect attribute");
auto dialectNamePair = attr.first.strref().split('.');
return ctx->getLoadedDialect(dialectNamePair.first);
}
private:
/// Verify the given potentially nested region or block.
LogicalResult verifyRegion(Region ®ion);
LogicalResult verifyBlock(Block &block);
LogicalResult verifyOperation(Operation &op);
/// Verify the dominance property of operations within the given Region.
LogicalResult verifyDominance(Region ®ion);
/// Verify the dominance property of regions contained within the given
/// Operation.
LogicalResult verifyDominanceOfContainedRegions(Operation &op);
/// Emit an error for the given block.
InFlightDiagnostic emitError(Block &bb, const Twine &message) {
// Take the location information for the first operation in the block.
if (!bb.empty())
return bb.front().emitError(message);
// Worst case, fall back to using the parent's location.
return mlir::emitError(bb.getParent()->getLoc(), message);
}
/// The current context for the verifier.
MLIRContext *ctx;
/// Dominance information for this operation, when checking dominance.
DominanceInfo *domInfo = nullptr;
/// Mapping between dialect namespace and if that dialect supports
/// unregistered operations.
llvm::StringMap<bool> dialectAllowsUnknownOps;
};
} // end anonymous namespace
/// Verify the given operation.
LogicalResult OperationVerifier::verify(Operation &op) {
// Verify the operation first.
if (failed(verifyOperation(op)))
return failure();
// Since everything looks structurally ok to this point, we do a dominance
// check for any nested regions. We do this as a second pass since malformed
// CFG's can cause dominator analysis constructure to crash and we want the
// verifier to be resilient to malformed code.
DominanceInfo theDomInfo(&op);
domInfo = &theDomInfo;
if (failed(verifyDominanceOfContainedRegions(op)))
return failure();
domInfo = nullptr;
return success();
}
LogicalResult OperationVerifier::verifyRegion(Region ®ion) {
if (region.empty())
return success();
// Verify the first block has no predecessors.
auto *firstBB = ®ion.front();
if (!firstBB->hasNoPredecessors())
return mlir::emitError(region.getLoc(),
"entry block of region may not have predecessors");
// Verify each of the blocks within the region.
for (Block &block : region)
if (failed(verifyBlock(block)))
return failure();
return success();
}
LogicalResult OperationVerifier::verifyBlock(Block &block) {
for (auto arg : block.getArguments())
if (arg.getOwner() != &block)
return emitError(block, "block argument not owned by block");
// Verify that this block has a terminator.
if (block.empty())
return emitError(block, "block with no terminator");
// Verify the non-terminator operations separately so that we can verify
// they has no successors.
for (auto &op : llvm::make_range(block.begin(), std::prev(block.end()))) {
if (op.getNumSuccessors() != 0)
return op.emitError(
"operation with block successors must terminate its parent block");
if (failed(verifyOperation(op)))
return failure();
}
// Verify the terminator.
if (failed(verifyOperation(block.back())))
return failure();
if (block.back().isKnownNonTerminator())
return emitError(block, "block with no terminator");
// Verify that this block is not branching to a block of a different
// region.
for (Block *successor : block.getSuccessors())
if (successor->getParent() != block.getParent())
return block.back().emitOpError(
"branching to block of a different region");
return success();
}
LogicalResult OperationVerifier::verifyOperation(Operation &op) {
// Check that operands are non-nil and structurally ok.
for (auto operand : op.getOperands())
if (!operand)
return op.emitError("null operand found");
/// Verify that all of the attributes are okay.
for (auto attr : op.getAttrs()) {
// Check for any optional dialect specific attributes.
if (!attr.first.strref().contains('.'))
continue;
if (auto *dialect = getDialectForAttribute(attr))
if (failed(dialect->verifyOperationAttribute(&op, attr)))
return failure();
}
// If we can get operation info for this, check the custom hook.
auto *opInfo = op.getAbstractOperation();
if (opInfo && failed(opInfo->verifyInvariants(&op)))
return failure();
auto kindInterface = dyn_cast<mlir::RegionKindInterface>(op);
// Verify that all child regions are ok.
unsigned numRegions = op.getNumRegions();
for (unsigned i = 0; i < numRegions; i++) {
Region ®ion = op.getRegion(i);
// Check that Graph Regions only have a single basic block. This is
// similar to the code in SingleBlockImplicitTerminator, but doesn't
// require the trait to be specified. This arbitrary limitation is
// designed to limit the number of cases that have to be handled by
// transforms and conversions until the concept stabilizes.
if (op.isRegistered() && kindInterface &&
kindInterface.getRegionKind(i) == RegionKind::Graph) {
// Empty regions are fine.
if (region.empty())
continue;
// Non-empty regions must contain a single basic block.
if (std::next(region.begin()) != region.end())
return op.emitOpError("expects graph region #")
<< i << " to have 0 or 1 blocks";
}
if (failed(verifyRegion(region)))
return failure();
}
// If this is a registered operation, there is nothing left to do.
if (opInfo)
return success();
// Otherwise, verify that the parent dialect allows un-registered operations.
auto dialectPrefix = op.getName().getDialect();
// Check for an existing answer for the operation dialect.
auto it = dialectAllowsUnknownOps.find(dialectPrefix);
if (it == dialectAllowsUnknownOps.end()) {
// If the operation dialect is registered, query it directly.
if (auto *dialect = ctx->getLoadedDialect(dialectPrefix))
it = dialectAllowsUnknownOps
.try_emplace(dialectPrefix, dialect->allowsUnknownOperations())
.first;
// Otherwise, unregistered dialects (when allowed by the context)
// conservatively allow unknown operations.
else {
if (!op.getContext()->allowsUnregisteredDialects() && !op.getDialect())
return op.emitOpError()
<< "created with unregistered dialect. If this is "
"intended, please call allowUnregisteredDialects() on the "
"MLIRContext, or use -allow-unregistered-dialect with "
"mlir-opt";
it = dialectAllowsUnknownOps.try_emplace(dialectPrefix, true).first;
}
}
if (!it->second) {
return op.emitError("unregistered operation '")
<< op.getName() << "' found in dialect ('" << dialectPrefix
<< "') that does not allow unknown operations";
}
return success();
}
LogicalResult OperationVerifier::verifyDominance(Region ®ion) {
// Verify the dominance of each of the held operations.
for (Block &block : region) {
// Dominance is only meaningful inside reachable blocks.
if (domInfo->isReachableFromEntry(&block))
for (Operation &op : block)
// Check that operands properly dominate this use.
for (unsigned operandNo = 0, e = op.getNumOperands(); operandNo != e;
++operandNo) {
auto operand = op.getOperand(operandNo);
if (domInfo->properlyDominates(operand, &op))
continue;
auto diag = op.emitError("operand #")
<< operandNo << " does not dominate this use";
if (auto *useOp = operand.getDefiningOp())
diag.attachNote(useOp->getLoc()) << "operand defined here";
return failure();
}
// Recursively verify dominance within each operation in the
// block, even if the block itself is not reachable, or we are in
// a region which doesn't respect dominance.
for (Operation &op : block)
if (failed(verifyDominanceOfContainedRegions(op)))
return failure();
}
return success();
}
/// Verify the dominance of each of the nested blocks within the given operation
LogicalResult
OperationVerifier::verifyDominanceOfContainedRegions(Operation &op) {
for (Region ®ion : op.getRegions()) {
if (failed(verifyDominance(region)))
return failure();
}
return success();
}
//===----------------------------------------------------------------------===//
// Entrypoint
//===----------------------------------------------------------------------===//
/// Perform (potentially expensive) checks of invariants, used to detect
/// compiler bugs. On error, this reports the error through the MLIRContext and
/// returns failure.
LogicalResult mlir::verify(Operation *op) {
return OperationVerifier(op->getContext()).verify(*op);
}