llvm-project/mlir/lib/IR/Operation.cpp

982 lines
36 KiB
C++

//===- Operation.cpp - Operation support code -----------------------------===//
//
// 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/IR/Operation.h"
#include "mlir/IR/BlockAndValueMapping.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/Dialect.h"
#include "mlir/IR/Function.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/OpImplementation.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/StandardTypes.h"
#include <numeric>
using namespace mlir;
/// Form the OperationName for an op with the specified string. This either is
/// a reference to an AbstractOperation if one is known, or a uniqued Identifier
/// if not.
OperationName::OperationName(StringRef name, MLIRContext *context) {
if (auto *op = AbstractOperation::lookup(name, context))
representation = op;
else
representation = Identifier::get(name, context);
}
/// Return the name of this operation. This always succeeds.
StringRef OperationName::getStringRef() const {
if (auto *op = representation.dyn_cast<const AbstractOperation *>())
return op->name;
return representation.get<Identifier>().strref();
}
const AbstractOperation *OperationName::getAbstractOperation() const {
return representation.dyn_cast<const AbstractOperation *>();
}
OperationName OperationName::getFromOpaquePointer(void *pointer) {
return OperationName(RepresentationUnion::getFromOpaqueValue(pointer));
}
OpAsmParser::~OpAsmParser() {}
//===----------------------------------------------------------------------===//
// OpResult
//===----------------------------------------------------------------------===//
/// Return the result number of this result.
unsigned OpResult::getResultNumber() {
// Results are always stored consecutively, so use pointer subtraction to
// figure out what number this is.
return this - &getOwner()->getOpResults()[0];
}
//===----------------------------------------------------------------------===//
// OpOperand
//===----------------------------------------------------------------------===//
/// Return which operand this is in the operand list.
template <> unsigned OpOperand::getOperandNumber() {
return this - &getOwner()->getOpOperands()[0];
}
//===----------------------------------------------------------------------===//
// BlockOperand
//===----------------------------------------------------------------------===//
/// Return which operand this is in the operand list.
template <> unsigned BlockOperand::getOperandNumber() {
return this - &getOwner()->getBlockOperands()[0];
}
//===----------------------------------------------------------------------===//
// Operation
//===----------------------------------------------------------------------===//
/// Create a new Operation with the specific fields.
Operation *Operation::create(Location location, OperationName name,
ArrayRef<Value *> operands,
ArrayRef<Type> resultTypes,
ArrayRef<NamedAttribute> attributes,
ArrayRef<Block *> successors, unsigned numRegions,
bool resizableOperandList, MLIRContext *context) {
return create(location, name, operands, resultTypes,
NamedAttributeList(context, attributes), successors, numRegions,
resizableOperandList, context);
}
/// Create a new Operation from operation state.
Operation *Operation::create(const OperationState &state) {
unsigned numRegions = state.regions.size();
Operation *op = create(state.location, state.name, state.operands,
state.types, state.attributes, state.successors,
numRegions, state.resizableOperandList, state.context);
for (unsigned i = 0; i < numRegions; ++i)
if (state.regions[i])
op->getRegion(i).takeBody(*state.regions[i]);
return op;
}
/// Overload of create that takes an existing NamedAttributeList to avoid
/// unnecessarily uniquing a list of attributes.
Operation *Operation::create(Location location, OperationName name,
ArrayRef<Value *> operands,
ArrayRef<Type> resultTypes,
const NamedAttributeList &attributes,
ArrayRef<Block *> successors, unsigned numRegions,
bool resizableOperandList, MLIRContext *context) {
unsigned numSuccessors = successors.size();
// Input operands are nullptr-separated for each successor, the null operands
// aren't actually stored.
unsigned numOperands = operands.size() - numSuccessors;
// Compute the byte size for the operation and the operand storage.
auto byteSize = totalSizeToAlloc<OpResult, BlockOperand, unsigned, Region,
detail::OperandStorage>(
resultTypes.size(), numSuccessors, numSuccessors, numRegions,
/*detail::OperandStorage*/ 1);
byteSize += llvm::alignTo(detail::OperandStorage::additionalAllocSize(
numOperands, resizableOperandList),
alignof(Operation));
void *rawMem = malloc(byteSize);
// Create the new Operation.
auto op =
::new (rawMem) Operation(location, name, resultTypes.size(),
numSuccessors, numRegions, attributes, context);
assert((numSuccessors == 0 || !op->isKnownNonTerminator()) &&
"unexpected successors in a non-terminator operation");
// Initialize the regions.
for (unsigned i = 0; i != numRegions; ++i)
new (&op->getRegion(i)) Region(op);
// Initialize the results and operands.
new (&op->getOperandStorage())
detail::OperandStorage(numOperands, resizableOperandList);
auto instResults = op->getOpResults();
for (unsigned i = 0, e = resultTypes.size(); i != e; ++i)
new (&instResults[i]) OpResult(resultTypes[i], op);
auto opOperands = op->getOpOperands();
// Initialize normal operands.
unsigned operandIt = 0, operandE = operands.size();
unsigned nextOperand = 0;
for (; operandIt != operandE; ++operandIt) {
// Null operands are used as sentinels between successor operand lists. If
// we encounter one here, break and handle the successor operands lists
// separately below.
if (!operands[operandIt])
break;
new (&opOperands[nextOperand++]) OpOperand(op, operands[operandIt]);
}
unsigned currentSuccNum = 0;
if (operandIt == operandE) {
// Verify that the amount of sentinel operands is equivalent to the number
// of successors.
assert(currentSuccNum == numSuccessors);
return op;
}
assert(!op->isKnownNonTerminator() &&
"Unexpected nullptr in operand list when creating non-terminator.");
auto instBlockOperands = op->getBlockOperands();
unsigned *succOperandCountIt = op->getTrailingObjects<unsigned>();
unsigned *succOperandCountE = succOperandCountIt + numSuccessors;
(void)succOperandCountE;
for (; operandIt != operandE; ++operandIt) {
// If we encounter a sentinel branch to the next operand update the count
// variable.
if (!operands[operandIt]) {
assert(currentSuccNum < numSuccessors);
// After the first iteration update the successor operand count
// variable.
if (currentSuccNum != 0) {
++succOperandCountIt;
assert(succOperandCountIt != succOperandCountE &&
"More sentinel operands than successors.");
}
new (&instBlockOperands[currentSuccNum])
BlockOperand(op, successors[currentSuccNum]);
*succOperandCountIt = 0;
++currentSuccNum;
continue;
}
new (&opOperands[nextOperand++]) OpOperand(op, operands[operandIt]);
++(*succOperandCountIt);
}
// Verify that the amount of sentinel operands is equivalent to the number of
// successors.
assert(currentSuccNum == numSuccessors);
return op;
}
Operation::Operation(Location location, OperationName name, unsigned numResults,
unsigned numSuccessors, unsigned numRegions,
const NamedAttributeList &attributes, MLIRContext *context)
: location(location), numResults(numResults), numSuccs(numSuccessors),
numRegions(numRegions), name(name), attrs(attributes) {}
// Operations are deleted through the destroy() member because they are
// allocated via malloc.
Operation::~Operation() {
assert(block == nullptr && "operation destroyed but still in a block");
// Explicitly run the destructors for the operands and results.
getOperandStorage().~OperandStorage();
for (auto &result : getOpResults())
result.~OpResult();
// Explicitly run the destructors for the successors.
for (auto &successor : getBlockOperands())
successor.~BlockOperand();
// Explicitly destroy the regions.
for (auto &region : getRegions())
region.~Region();
}
/// Destroy this operation or one of its subclasses.
void Operation::destroy() {
this->~Operation();
free(this);
}
/// Return the context this operation is associated with.
MLIRContext *Operation::getContext() {
// If we have a result or operand type, that is a constant time way to get
// to the context.
if (getNumResults())
return getResult(0)->getType().getContext();
if (getNumOperands())
return getOperand(0)->getType().getContext();
// In the very odd case where we have no operands or results, fall back to
// doing a find.
return getFunction()->getContext();
}
/// Return the dialact this operation is associated with, or nullptr if the
/// associated dialect is not registered.
Dialect *Operation::getDialect() {
if (auto *abstractOp = getAbstractOperation())
return &abstractOp->dialect;
// If this operation hasn't been registered or doesn't have abstract
// operation, fall back to a dialect which matches the prefix.
auto opName = getName().getStringRef();
auto dialectPrefix = opName.split('.').first;
return getContext()->getRegisteredDialect(dialectPrefix);
}
Region *Operation::getContainingRegion() const {
return block ? block->getParent() : nullptr;
}
Operation *Operation::getParentOp() {
return block ? block->getContainingOp() : nullptr;
}
Function *Operation::getFunction() {
return block ? block->getFunction() : nullptr;
}
//===----------------------------------------------------------------------===//
// Operation Walkers
//===----------------------------------------------------------------------===//
void Operation::walk(const std::function<void(Operation *)> &callback) {
// Visit any internal operations.
for (auto &region : getRegions())
for (auto &block : region)
block.walk(callback);
// Visit the current operation.
callback(this);
}
//===----------------------------------------------------------------------===//
// Other
//===----------------------------------------------------------------------===//
/// Emit a remark about this operation, reporting up to any diagnostic
/// handlers that may be listening.
void Operation::emitRemark(const Twine &message) {
getContext()->emitRemark(getLoc(), message);
}
/// Emit a note about this operation, reporting up to any diagnostic
/// handlers that may be listening.
void Operation::emitNote(const Twine &message) {
getContext()->getDiagEngine().emit(getLoc(), message,
DiagnosticSeverity::Note);
}
/// Emit a warning about this operation, reporting up to any diagnostic
/// handlers that may be listening.
void Operation::emitWarning(const Twine &message) {
getContext()->getDiagEngine().emit(getLoc(), message,
DiagnosticSeverity::Warning);
}
/// Emit an error about fatal conditions with this operation, reporting up to
/// any diagnostic handlers that may be listening. This function always
/// returns failure. NOTE: This may terminate the containing application, only
/// use when the IR is in an inconsistent state.
LogicalResult Operation::emitError(const Twine &message) {
return getContext()->emitError(getLoc(), message), failure();
}
/// Given an operation 'other' that is within the same parent block, return
/// whether the current operation is before 'other' in the operation list
/// of the parent block.
/// Note: This function has an average complexity of O(1), but worst case may
/// take O(N) where N is the number of operations within the parent block.
bool Operation::isBeforeInBlock(Operation *other) {
assert(block && "Operations without parent blocks have no order.");
assert(other && other->block == block &&
"Expected other operation to have the same parent block.");
// Recompute the parent ordering if necessary.
if (!block->isInstOrderValid())
block->recomputeInstOrder();
return orderIndex < other->orderIndex;
}
//===----------------------------------------------------------------------===//
// ilist_traits for Operation
//===----------------------------------------------------------------------===//
auto llvm::ilist_detail::SpecificNodeAccess<
typename llvm::ilist_detail::compute_node_options<
::mlir::Operation>::type>::getNodePtr(pointer N) -> node_type * {
return NodeAccess::getNodePtr<OptionsT>(N);
}
auto llvm::ilist_detail::SpecificNodeAccess<
typename llvm::ilist_detail::compute_node_options<
::mlir::Operation>::type>::getNodePtr(const_pointer N)
-> const node_type * {
return NodeAccess::getNodePtr<OptionsT>(N);
}
auto llvm::ilist_detail::SpecificNodeAccess<
typename llvm::ilist_detail::compute_node_options<
::mlir::Operation>::type>::getValuePtr(node_type *N) -> pointer {
return NodeAccess::getValuePtr<OptionsT>(N);
}
auto llvm::ilist_detail::SpecificNodeAccess<
typename llvm::ilist_detail::compute_node_options<
::mlir::Operation>::type>::getValuePtr(const node_type *N)
-> const_pointer {
return NodeAccess::getValuePtr<OptionsT>(N);
}
void llvm::ilist_traits<::mlir::Operation>::deleteNode(Operation *op) {
op->destroy();
}
Block *llvm::ilist_traits<::mlir::Operation>::getContainingBlock() {
size_t Offset(size_t(&((Block *)nullptr->*Block::getSublistAccess(nullptr))));
iplist<Operation> *Anchor(static_cast<iplist<Operation> *>(this));
return reinterpret_cast<Block *>(reinterpret_cast<char *>(Anchor) - Offset);
}
/// This is a trait method invoked when a operation is added to a block. We
/// keep the block pointer up to date.
void llvm::ilist_traits<::mlir::Operation>::addNodeToList(Operation *op) {
assert(!op->getBlock() && "already in a operation block!");
op->block = getContainingBlock();
// Invalidate the block ordering.
op->block->invalidateInstOrder();
}
/// This is a trait method invoked when a operation is removed from a block.
/// We keep the block pointer up to date.
void llvm::ilist_traits<::mlir::Operation>::removeNodeFromList(Operation *op) {
assert(op->block && "not already in a operation block!");
op->block = nullptr;
}
/// This is a trait method invoked when a operation is moved from one block
/// to another. We keep the block pointer up to date.
void llvm::ilist_traits<::mlir::Operation>::transferNodesFromList(
ilist_traits<Operation> &otherList, op_iterator first, op_iterator last) {
Block *curParent = getContainingBlock();
// Invalidate the ordering of the parent block.
curParent->invalidateInstOrder();
// If we are transferring operations within the same block, the block
// pointer doesn't need to be updated.
if (curParent == otherList.getContainingBlock())
return;
// Update the 'block' member of each operation.
for (; first != last; ++first)
first->block = curParent;
}
/// Remove this operation (and its descendants) from its Block and delete
/// all of them.
void Operation::erase() {
assert(getBlock() && "Operation has no block");
getBlock()->getOperations().erase(this);
}
/// Unlink this operation from its current block and insert it right before
/// `existingInst` which may be in the same or another block in the same
/// function.
void Operation::moveBefore(Operation *existingInst) {
moveBefore(existingInst->getBlock(), existingInst->getIterator());
}
/// Unlink this operation from its current basic block and insert it right
/// before `iterator` in the specified basic block.
void Operation::moveBefore(Block *block,
llvm::iplist<Operation>::iterator iterator) {
block->getOperations().splice(iterator, getBlock()->getOperations(),
getIterator());
}
/// This drops all operand uses from this operation, which is an essential
/// step in breaking cyclic dependences between references when they are to
/// be deleted.
void Operation::dropAllReferences() {
for (auto &op : getOpOperands())
op.drop();
for (auto &region : getRegions())
for (Block &block : region)
block.dropAllReferences();
for (auto &dest : getBlockOperands())
dest.drop();
}
/// This drops all uses of any values defined by this operation or its nested
/// regions, wherever they are located.
void Operation::dropAllDefinedValueUses() {
for (auto &val : getOpResults())
val.dropAllUses();
for (auto &region : getRegions())
for (auto &block : region)
block.dropAllDefinedValueUses();
}
/// Return true if there are no users of any results of this operation.
bool Operation::use_empty() {
for (auto *result : getResults())
if (!result->use_empty())
return false;
return true;
}
void Operation::setSuccessor(Block *block, unsigned index) {
assert(index < getNumSuccessors());
getBlockOperands()[index].set(block);
}
auto Operation::getNonSuccessorOperands() -> operand_range {
return {operand_iterator(this, 0),
operand_iterator(this, getSuccessorOperandIndex(0))};
}
/// Get the index of the first operand of the successor at the provided
/// index.
unsigned Operation::getSuccessorOperandIndex(unsigned index) {
assert(!isKnownNonTerminator() && "only terminators may have successors");
assert(index < getNumSuccessors());
// Count the number of operands for each of the successors after, and
// including, the one at 'index'. This is based upon the assumption that all
// non successor operands are placed at the beginning of the operand list.
auto *successorOpCountBegin = getTrailingObjects<unsigned>();
unsigned postSuccessorOpCount =
std::accumulate(successorOpCountBegin + index,
successorOpCountBegin + getNumSuccessors(), 0u);
return getNumOperands() - postSuccessorOpCount;
}
auto Operation::getSuccessorOperands(unsigned index) -> operand_range {
unsigned succOperandIndex = getSuccessorOperandIndex(index);
return {operand_iterator(this, succOperandIndex),
operand_iterator(this,
succOperandIndex + getNumSuccessorOperands(index))};
}
/// Attempt to constant fold this operation with the specified constant
/// operand values. If successful, this fills in the results vector. If not,
/// results is unspecified.
LogicalResult Operation::constantFold(ArrayRef<Attribute> operands,
SmallVectorImpl<Attribute> &results) {
if (auto *abstractOp = getAbstractOperation()) {
// If we have a registered operation definition matching this one, use it to
// try to constant fold the operation.
if (succeeded(abstractOp->constantFoldHook(this, operands, results)))
return success();
// Otherwise, fall back on the dialect hook to handle it.
return abstractOp->dialect.constantFoldHook(this, operands, results);
}
// If this operation hasn't been registered or doesn't have abstract
// operation, fall back to a dialect which matches the prefix.
auto opName = getName().getStringRef();
auto dialectPrefix = opName.split('.').first;
if (auto *dialect = getContext()->getRegisteredDialect(dialectPrefix))
return dialect->constantFoldHook(this, operands, results);
return failure();
}
/// Attempt to fold this operation using the Op's registered foldHook.
LogicalResult Operation::fold(SmallVectorImpl<Value *> &results) {
if (auto *abstractOp = getAbstractOperation()) {
// If we have a registered operation definition matching this one, use it to
// try to constant fold the operation.
if (succeeded(abstractOp->foldHook(this, results)))
return success();
}
return failure();
}
/// Emit an error with the op name prefixed, like "'dim' op " which is
/// convenient for verifiers.
LogicalResult Operation::emitOpError(const Twine &message) {
return emitError(Twine('\'') + getName().getStringRef() + "' op " + message);
}
//===----------------------------------------------------------------------===//
// Operation Cloning
//===----------------------------------------------------------------------===//
/// Create a deep copy of this operation but keep the operation regions empty.
/// Operands are remapped using `mapper` (if present), and `mapper` is updated
/// to contain the results.
Operation *Operation::cloneWithoutRegions(BlockAndValueMapping &mapper,
MLIRContext *context) {
SmallVector<Value *, 8> operands;
SmallVector<Block *, 2> successors;
operands.reserve(getNumOperands() + getNumSuccessors());
if (getNumSuccessors() == 0) {
// Non-branching operations can just add all the operands.
for (auto *opValue : getOperands())
operands.push_back(mapper.lookupOrDefault(opValue));
} else {
// We add the operands separated by nullptr's for each successor.
unsigned firstSuccOperand =
getNumSuccessors() ? getSuccessorOperandIndex(0) : getNumOperands();
auto opOperands = getOpOperands();
unsigned i = 0;
for (; i != firstSuccOperand; ++i)
operands.push_back(mapper.lookupOrDefault(opOperands[i].get()));
successors.reserve(getNumSuccessors());
for (unsigned succ = 0, e = getNumSuccessors(); succ != e; ++succ) {
successors.push_back(mapper.lookupOrDefault(getSuccessor(succ)));
// Add sentinel to delineate successor operands.
operands.push_back(nullptr);
// Remap the successors operands.
for (auto *operand : getSuccessorOperands(succ))
operands.push_back(mapper.lookupOrDefault(operand));
}
}
SmallVector<Type, 8> resultTypes;
resultTypes.reserve(getNumResults());
for (auto *result : getResults())
resultTypes.push_back(result->getType());
unsigned numRegions = getNumRegions();
auto *newOp = Operation::create(getLoc(), getName(), operands, resultTypes,
attrs, successors, numRegions,
hasResizableOperandsList(), context);
// Remember the mapping of any results.
for (unsigned i = 0, e = getNumResults(); i != e; ++i)
mapper.map(getResult(i), newOp->getResult(i));
return newOp;
}
Operation *Operation::cloneWithoutRegions(MLIRContext *context) {
BlockAndValueMapping mapper;
return cloneWithoutRegions(mapper, context);
}
/// Create a deep copy of this operation, remapping any operands that use
/// values outside of the operation using the map that is provided (leaving
/// them alone if no entry is present). Replaces references to cloned
/// sub-operations to the corresponding operation that is copied, and adds
/// those mappings to the map.
Operation *Operation::clone(BlockAndValueMapping &mapper,
MLIRContext *context) {
auto *newOp = cloneWithoutRegions(mapper, context);
// Clone the regions.
for (unsigned i = 0; i != numRegions; ++i)
getRegion(i).cloneInto(&newOp->getRegion(i), mapper, context);
return newOp;
}
Operation *Operation::clone(MLIRContext *context) {
BlockAndValueMapping mapper;
return clone(mapper, context);
}
//===----------------------------------------------------------------------===//
// OpState trait class.
//===----------------------------------------------------------------------===//
// The fallback for the parser is to reject the custom assembly form.
bool OpState::parse(OpAsmParser *parser, OperationState *result) {
return parser->emitError(parser->getNameLoc(), "has no custom assembly form");
}
// The fallback for the printer is to print in the generic assembly form.
void OpState::print(OpAsmPrinter *p) { p->printGenericOp(getOperation()); }
/// Emit an error about fatal conditions with this operation, reporting up to
/// any diagnostic handlers that may be listening. NOTE: This may terminate
/// the containing application, only use when the IR is in an inconsistent
/// state.
LogicalResult OpState::emitError(const Twine &message) {
return getOperation()->emitError(message);
}
/// Emit an error with the op name prefixed, like "'dim' op " which is
/// convenient for verifiers.
LogicalResult OpState::emitOpError(const Twine &message) {
return getOperation()->emitOpError(message);
}
/// Emit a warning about this operation, reporting up to any diagnostic
/// handlers that may be listening.
void OpState::emitWarning(const Twine &message) {
getOperation()->emitWarning(message);
}
/// Emit a note about this operation, reporting up to any diagnostic
/// handlers that may be listening.
void OpState::emitNote(const Twine &message) {
getOperation()->emitNote(message);
}
/// Emit a remark about this operation, reporting up to any diagnostic
/// handlers that may be listening.
void OpState::emitRemark(const Twine &message) {
getOperation()->emitRemark(message);
}
//===----------------------------------------------------------------------===//
// Op Trait implementations
//===----------------------------------------------------------------------===//
LogicalResult OpTrait::impl::verifyZeroOperands(Operation *op) {
if (op->getNumOperands() != 0)
return op->emitOpError("requires zero operands");
return success();
}
LogicalResult OpTrait::impl::verifyOneOperand(Operation *op) {
if (op->getNumOperands() != 1)
return op->emitOpError("requires a single operand");
return success();
}
LogicalResult OpTrait::impl::verifyNOperands(Operation *op,
unsigned numOperands) {
if (op->getNumOperands() != numOperands) {
return op->emitOpError("expected " + Twine(numOperands) +
" operands, but found " +
Twine(op->getNumOperands()));
}
return success();
}
LogicalResult OpTrait::impl::verifyAtLeastNOperands(Operation *op,
unsigned numOperands) {
if (op->getNumOperands() < numOperands)
return op->emitOpError("expected " + Twine(numOperands) +
" or more operands");
return success();
}
/// If this is a vector type, or a tensor type, return the scalar element type
/// that it is built around, otherwise return the type unmodified.
static Type getTensorOrVectorElementType(Type type) {
if (auto vec = type.dyn_cast<VectorType>())
return vec.getElementType();
// Look through tensor<vector<...>> to find the underlying element type.
if (auto tensor = type.dyn_cast<TensorType>())
return getTensorOrVectorElementType(tensor.getElementType());
return type;
}
LogicalResult OpTrait::impl::verifyOperandsAreIntegerLike(Operation *op) {
for (auto *operand : op->getOperands()) {
auto type = getTensorOrVectorElementType(operand->getType());
if (!type.isIntOrIndex())
return op->emitOpError("requires an integer or index type");
}
return success();
}
LogicalResult OpTrait::impl::verifySameTypeOperands(Operation *op) {
// Zero or one operand always have the "same" type.
unsigned nOperands = op->getNumOperands();
if (nOperands < 2)
return success();
auto type = op->getOperand(0)->getType();
for (unsigned i = 1; i < nOperands; ++i)
if (op->getOperand(i)->getType() != type)
return op->emitOpError("requires all operands to have the same type");
return success();
}
LogicalResult OpTrait::impl::verifyZeroResult(Operation *op) {
if (op->getNumResults() != 0)
return op->emitOpError("requires zero results");
return success();
}
LogicalResult OpTrait::impl::verifyOneResult(Operation *op) {
if (op->getNumResults() != 1)
return op->emitOpError("requires one result");
return success();
}
LogicalResult OpTrait::impl::verifyNResults(Operation *op,
unsigned numOperands) {
if (op->getNumResults() != numOperands)
return op->emitOpError("expected " + Twine(numOperands) + " results");
return success();
}
LogicalResult OpTrait::impl::verifyAtLeastNResults(Operation *op,
unsigned numOperands) {
if (op->getNumResults() < numOperands)
return op->emitOpError("expected " + Twine(numOperands) +
" or more results");
return success();
}
/// Returns success if the given two types have the same shape. That is,
/// they are both scalars, or they are both vectors / ranked tensors with
/// the same dimension specifications. The element type does not matter.
static LogicalResult verifyShapeMatch(Type type1, Type type2) {
// Check scalar cases
if (type1.isIntOrIndexOrFloat())
return success(type2.isIntOrIndexOrFloat());
// Check unranked tensor cases
if (type1.isa<UnrankedTensorType>() || type2.isa<UnrankedTensorType>())
return failure();
// Check normal vector/tensor cases
if (auto vtType1 = type1.dyn_cast<VectorOrTensorType>()) {
auto vtType2 = type2.dyn_cast<VectorOrTensorType>();
return success(vtType2 && vtType1.getShape() == vtType2.getShape());
}
return success();
}
LogicalResult OpTrait::impl::verifySameOperandsAndResultShape(Operation *op) {
if (op->getNumOperands() == 0 || op->getNumResults() == 0)
return failure();
auto type = op->getOperand(0)->getType();
for (unsigned i = 0, e = op->getNumResults(); i < e; ++i) {
if (failed(verifyShapeMatch(op->getResult(i)->getType(), type)))
return op->emitOpError(
"requires the same shape for all operands and results");
}
for (unsigned i = 1, e = op->getNumOperands(); i < e; ++i) {
if (failed(verifyShapeMatch(op->getOperand(i)->getType(), type)))
return op->emitOpError(
"requires the same shape for all operands and results");
}
return success();
}
LogicalResult OpTrait::impl::verifySameOperandsAndResultType(Operation *op) {
if (op->getNumOperands() == 0 || op->getNumResults() == 0)
return failure();
auto type = op->getResult(0)->getType();
for (unsigned i = 1, e = op->getNumResults(); i < e; ++i) {
if (op->getResult(i)->getType() != type)
return op->emitOpError(
"requires the same type for all operands and results");
}
for (unsigned i = 0, e = op->getNumOperands(); i < e; ++i) {
if (op->getOperand(i)->getType() != type)
return op->emitOpError(
"requires the same type for all operands and results");
}
return success();
}
static LogicalResult verifyBBArguments(Operation::operand_range operands,
Block *destBB, Operation *op) {
unsigned operandCount = std::distance(operands.begin(), operands.end());
if (operandCount != destBB->getNumArguments())
return op->emitError("branch has " + Twine(operandCount) +
" operands, but target block has " +
Twine(destBB->getNumArguments()));
auto operandIt = operands.begin();
for (unsigned i = 0, e = operandCount; i != e; ++i, ++operandIt) {
if ((*operandIt)->getType() != destBB->getArgument(i)->getType())
return op->emitError("type mismatch in bb argument #" + Twine(i));
}
return success();
}
static LogicalResult verifyTerminatorSuccessors(Operation *op) {
// Verify that the operands lines up with the BB arguments in the successor.
Function *fn = op->getFunction();
for (unsigned i = 0, e = op->getNumSuccessors(); i != e; ++i) {
auto *succ = op->getSuccessor(i);
if (succ->getFunction() != fn)
return op->emitError("reference to block defined in another function");
if (failed(verifyBBArguments(op->getSuccessorOperands(i), succ, op)))
return failure();
}
return success();
}
LogicalResult OpTrait::impl::verifyIsTerminator(Operation *op) {
Block *block = op->getBlock();
// Verify that the operation is at the end of the respective parent block.
if (!block || &block->back() != op)
return op->emitOpError("must be the last operation in the parent block");
// Verify the state of the successor blocks.
if (op->getNumSuccessors() != 0 && failed(verifyTerminatorSuccessors(op)))
return failure();
return success();
}
LogicalResult OpTrait::impl::verifyResultsAreBoolLike(Operation *op) {
for (auto *result : op->getResults()) {
auto elementType = getTensorOrVectorElementType(result->getType());
bool isBoolType = elementType.isInteger(1);
if (!isBoolType)
return op->emitOpError("requires a bool result type");
}
return success();
}
LogicalResult OpTrait::impl::verifyResultsAreFloatLike(Operation *op) {
for (auto *result : op->getResults())
if (!getTensorOrVectorElementType(result->getType()).isa<FloatType>())
return op->emitOpError("requires a floating point type");
return success();
}
LogicalResult OpTrait::impl::verifyResultsAreIntegerLike(Operation *op) {
for (auto *result : op->getResults()) {
auto type = getTensorOrVectorElementType(result->getType());
if (!type.isIntOrIndex())
return op->emitOpError("requires an integer or index type");
}
return success();
}
//===----------------------------------------------------------------------===//
// BinaryOp implementation
//===----------------------------------------------------------------------===//
// These functions are out-of-line implementations of the methods in BinaryOp,
// which avoids them being template instantiated/duplicated.
void impl::buildBinaryOp(Builder *builder, OperationState *result, Value *lhs,
Value *rhs) {
assert(lhs->getType() == rhs->getType());
result->addOperands({lhs, rhs});
result->types.push_back(lhs->getType());
}
bool impl::parseBinaryOp(OpAsmParser *parser, OperationState *result) {
SmallVector<OpAsmParser::OperandType, 2> ops;
Type type;
return parser->parseOperandList(ops, 2) ||
parser->parseOptionalAttributeDict(result->attributes) ||
parser->parseColonType(type) ||
parser->resolveOperands(ops, type, result->operands) ||
parser->addTypeToList(type, result->types);
}
void impl::printBinaryOp(Operation *op, OpAsmPrinter *p) {
assert(op->getNumOperands() == 2 && "binary op should have two operands");
assert(op->getNumResults() == 1 && "binary op should have one result");
// If not all the operand and result types are the same, just use the
// generic assembly form to avoid omitting information in printing.
auto resultType = op->getResult(0)->getType();
if (op->getOperand(0)->getType() != resultType ||
op->getOperand(1)->getType() != resultType) {
p->printGenericOp(op);
return;
}
*p << op->getName() << ' ' << *op->getOperand(0) << ", "
<< *op->getOperand(1);
p->printOptionalAttrDict(op->getAttrs());
// Now we can output only one type for all operands and the result.
*p << " : " << op->getResult(0)->getType();
}
//===----------------------------------------------------------------------===//
// CastOp implementation
//===----------------------------------------------------------------------===//
void impl::buildCastOp(Builder *builder, OperationState *result, Value *source,
Type destType) {
result->addOperands(source);
result->addTypes(destType);
}
bool impl::parseCastOp(OpAsmParser *parser, OperationState *result) {
OpAsmParser::OperandType srcInfo;
Type srcType, dstType;
return parser->parseOperand(srcInfo) || parser->parseColonType(srcType) ||
parser->resolveOperand(srcInfo, srcType, result->operands) ||
parser->parseKeywordType("to", dstType) ||
parser->addTypeToList(dstType, result->types);
}
void impl::printCastOp(Operation *op, OpAsmPrinter *p) {
*p << op->getName() << ' ' << *op->getOperand(0) << " : "
<< op->getOperand(0)->getType() << " to " << op->getResult(0)->getType();
}
Value *impl::foldCastOp(Operation *op) {
// Identity cast
if (op->getOperand(0)->getType() == op->getResult(0)->getType())
return op->getOperand(0);
return nullptr;
}