Files
clang-p2996/mlir/lib/Analysis/NestedMatcher.cpp
Nicolas Vasilache 81c7f2e2f3 Cleanup resource management and rename recursive matchers
This CL follows up on a memory leak issue related to SmallVector growth that
escapes the BumpPtrAllocator.
The fix is to properly use ArrayRef and placement new to define away the
issue.

The following renaming is also applied:
1. MLFunctionMatcher -> NestedPattern
2. MLFunctionMatches -> NestedMatch

As a consequence all allocations are now guaranteed to live on the BumpPtrAllocator.

PiperOrigin-RevId: 231047766
2019-03-29 15:39:53 -07:00

241 lines
8.3 KiB
C++

//===- NestedMatcher.cpp - NestedMatcher Impl ------------------*- C++ -*-===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
#include "mlir/Analysis/NestedMatcher.h"
#include "mlir/StandardOps/StandardOps.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/raw_ostream.h"
namespace mlir {
/// Underlying storage for NestedMatch.
struct NestedMatchStorage {
MutableArrayRef<NestedMatch::EntryType> matches;
};
/// Underlying storage for NestedPattern.
struct NestedPatternStorage {
NestedPatternStorage(Instruction::Kind k, ArrayRef<NestedPattern> c,
FilterFunctionType filter, Instruction *skip)
: kind(k), nestedPatterns(c), filter(filter), skip(skip) {}
Instruction::Kind kind;
ArrayRef<NestedPattern> nestedPatterns;
FilterFunctionType filter;
/// skip is needed so that we can implement match without switching on the
/// type of the Instruction.
/// The idea is that a NestedPattern first checks if it matches locally
/// and then recursively applies its nested matchers to its elem->nested.
/// Since we want to rely on the InstWalker impl rather than duplicate its
/// the logic, we allow an off-by-one traversal to account for the fact that
/// we write:
///
/// void match(Instruction *elem) {
/// for (auto &c : getNestedPatterns()) {
/// NestedPattern childPattern(...);
/// ^~~~ Needs off-by-one skip.
///
Instruction *skip;
};
} // end namespace mlir
using namespace mlir;
llvm::BumpPtrAllocator *&NestedMatch::allocator() {
static thread_local llvm::BumpPtrAllocator *allocator = nullptr;
return allocator;
}
NestedMatch NestedMatch::build(ArrayRef<NestedMatch::EntryType> elements) {
auto *matches =
allocator()->Allocate<NestedMatch::EntryType>(elements.size());
std::uninitialized_copy(elements.begin(), elements.end(), matches);
auto *storage = allocator()->Allocate<NestedMatchStorage>();
new (storage) NestedMatchStorage();
storage->matches =
MutableArrayRef<NestedMatch::EntryType>(matches, elements.size());
auto *result = allocator()->Allocate<NestedMatch>();
new (result) NestedMatch(storage);
return *result;
}
NestedMatch::iterator NestedMatch::begin() { return storage->matches.begin(); }
NestedMatch::iterator NestedMatch::end() { return storage->matches.end(); }
NestedMatch::EntryType &NestedMatch::front() {
return *storage->matches.begin();
}
NestedMatch::EntryType &NestedMatch::back() {
return *(storage->matches.begin() + size() - 1);
}
/// Calls walk on `function`.
NestedMatch NestedPattern::match(Function *function) {
assert(!matches && "NestedPattern already matched!");
this->walkPostOrder(function);
return matches;
}
/// Calls walk on `instruction`.
NestedMatch NestedPattern::match(Instruction *instruction) {
assert(!matches && "NestedPattern already matched!");
this->walkPostOrder(instruction);
return matches;
}
unsigned NestedPattern::getDepth() {
auto nested = getNestedPatterns();
if (nested.empty()) {
return 1;
}
unsigned depth = 0;
for (auto c : nested) {
depth = std::max(depth, c.getDepth());
}
return depth + 1;
}
/// Matches a single instruction in the following way:
/// 1. checks the kind of instruction against the matcher, if different then
/// there is no match;
/// 2. calls the customizable filter function to refine the single instruction
/// match with extra semantic constraints;
/// 3. if all is good, recursivey matches the nested patterns;
/// 4. if all nested match then the single instruction matches too and is
/// appended to the list of matches;
/// 5. TODO(ntv) Optionally applies actions (lambda), in which case we will
/// want to traverse in post-order DFS to avoid invalidating iterators.
void NestedPattern::matchOne(Instruction *elem) {
if (storage->skip == elem) {
return;
}
// Structural filter
if (elem->getKind() != getKind()) {
return;
}
// Local custom filter function
if (!getFilterFunction()(*elem)) {
return;
}
SmallVector<NestedMatch::EntryType, 8> nestedEntries;
for (auto c : getNestedPatterns()) {
/// We create a new nestedPattern here because a matcher holds its
/// results. So we concretely need multiple copies of a given matcher, one
/// for each matching result.
NestedPattern nestedPattern(c);
// Skip elem in the walk immediately following. Without this we would
// essentially need to reimplement walkPostOrder here.
nestedPattern.storage->skip = elem;
nestedPattern.walkPostOrder(elem);
if (!nestedPattern.matches) {
return;
}
for (auto m : nestedPattern.matches) {
nestedEntries.push_back(m);
}
}
SmallVector<NestedMatch::EntryType, 8> newEntries(
matches.storage->matches.begin(), matches.storage->matches.end());
newEntries.push_back(std::make_pair(elem, NestedMatch::build(nestedEntries)));
matches = NestedMatch::build(newEntries);
}
llvm::BumpPtrAllocator *&NestedPattern::allocator() {
static thread_local llvm::BumpPtrAllocator *allocator = nullptr;
return allocator;
}
NestedPattern::NestedPattern(Instruction::Kind k,
ArrayRef<NestedPattern> nested,
FilterFunctionType filter)
: storage(allocator()->Allocate<NestedPatternStorage>()),
matches(NestedMatch::build({})) {
auto *newChildren = allocator()->Allocate<NestedPattern>(nested.size());
std::uninitialized_copy(nested.begin(), nested.end(), newChildren);
// Initialize with placement new.
new (storage) NestedPatternStorage(
k, ArrayRef<NestedPattern>(newChildren, nested.size()), filter,
nullptr /* skip */);
}
Instruction::Kind NestedPattern::getKind() { return storage->kind; }
ArrayRef<NestedPattern> NestedPattern::getNestedPatterns() {
return storage->nestedPatterns;
}
FilterFunctionType NestedPattern::getFilterFunction() {
return storage->filter;
}
namespace mlir {
namespace matcher {
NestedPattern Op(FilterFunctionType filter) {
return NestedPattern(Instruction::Kind::OperationInst, {}, filter);
}
NestedPattern If(NestedPattern child) {
return NestedPattern(Instruction::Kind::If, child, defaultFilterFunction);
}
NestedPattern If(FilterFunctionType filter, NestedPattern child) {
return NestedPattern(Instruction::Kind::If, child, filter);
}
NestedPattern If(ArrayRef<NestedPattern> nested) {
return NestedPattern(Instruction::Kind::If, nested, defaultFilterFunction);
}
NestedPattern If(FilterFunctionType filter, ArrayRef<NestedPattern> nested) {
return NestedPattern(Instruction::Kind::If, nested, filter);
}
NestedPattern For(NestedPattern child) {
return NestedPattern(Instruction::Kind::For, child, defaultFilterFunction);
}
NestedPattern For(FilterFunctionType filter, NestedPattern child) {
return NestedPattern(Instruction::Kind::For, child, filter);
}
NestedPattern For(ArrayRef<NestedPattern> nested) {
return NestedPattern(Instruction::Kind::For, nested, defaultFilterFunction);
}
NestedPattern For(FilterFunctionType filter, ArrayRef<NestedPattern> nested) {
return NestedPattern(Instruction::Kind::For, nested, filter);
}
// TODO(ntv): parallel annotation on loops.
bool isParallelLoop(const Instruction &inst) {
const auto *loop = cast<ForInst>(&inst);
return (void *)loop || true; // loop->isParallel();
};
// TODO(ntv): reduction annotation on loops.
bool isReductionLoop(const Instruction &inst) {
const auto *loop = cast<ForInst>(&inst);
return (void *)loop || true; // loop->isReduction();
};
bool isLoadOrStore(const Instruction &inst) {
const auto *opInst = dyn_cast<OperationInst>(&inst);
return opInst && (opInst->isa<LoadOp>() || opInst->isa<StoreOp>());
};
} // end namespace matcher
} // end namespace mlir