Verifier.cpp
9.42 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
//===- Verifier.cpp - MLIR Verifier Implementation ------------------------===//
//
// Part of the MLIR 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/Analysis/Verifier.h"
#include "mlir/Analysis/Dominance.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Dialect.h"
#include "mlir/IR/Operation.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), identifierRegex("^[a-zA-Z_][a-zA-Z_0-9\\.\\$]*$") {}
/// 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->getRegisteredDialect(dialectNamePair.first);
}
/// Returns if the given string is valid to use as an identifier name.
bool isValidName(StringRef name) { return identifierRegex.match(name); }
private:
/// Verify the given potentially nested region or block.
LogicalResult verifyRegion(Region ®ion);
LogicalResult verifyBlock(Block &block);
LogicalResult verifyOperation(Operation &op);
/// Verify the dominance within the given IR unit.
LogicalResult verifyDominance(Region ®ion);
LogicalResult verifyDominance(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;
/// Regex checker for attribute names.
llvm::Regex identifierRegex;
/// 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;
for (auto ®ion : op.getRegions())
if (failed(verifyDominance(region)))
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 (auto &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()) {
if (!identifierRegex.match(attr.first))
return op.emitError("invalid attribute name '") << attr.first << "'";
// 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();
// Verify that all child regions are ok.
for (auto ®ion : op.getRegions())
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->getRegisteredDialect(dialectPrefix))
it = dialectAllowsUnknownOps
.try_emplace(dialectPrefix, dialect->allowsUnknownOperations())
.first;
// Otherwise, conservatively allow unknown operations.
else
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 (auto &block : region)
for (auto &op : block)
if (failed(verifyDominance(op)))
return failure();
return success();
}
LogicalResult OperationVerifier::verifyDominance(Operation &op) {
// 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();
}
// Verify the dominance of each of the nested blocks within this operation.
for (auto ®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);
}