#include "mlir/Dialect/MemRef/Transforms/RuntimeOpVerification.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Arith/Utils/Utils.h"
#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h"
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/Dialect/MemRef/Utils/MemRefUtils.h"
#include "mlir/Dialect/Utils/IndexingUtils.h"
#include "mlir/Interfaces/RuntimeVerifiableOpInterface.h"
using namespace mlir;
namespace mlir {
namespace memref {
namespace {
struct CastOpInterface
: public RuntimeVerifiableOpInterface::ExternalModel<CastOpInterface,
CastOp> {
void generateRuntimeVerification(Operation *op, OpBuilder &builder,
Location loc) const {
auto castOp = cast<CastOp>(op);
auto srcType = cast<BaseMemRefType>(castOp.getSource().getType());
auto resultType = dyn_cast<MemRefType>(castOp.getType());
if (!resultType)
return;
if (isa<UnrankedMemRefType>(srcType)) {
Value srcRank = builder.create<RankOp>(loc, castOp.getSource());
Value resultRank =
builder.create<arith::ConstantIndexOp>(loc, resultType.getRank());
Value isSameRank = builder.create<arith::CmpIOp>(
loc, arith::CmpIPredicate::eq, srcRank, resultRank);
builder.create<cf::AssertOp>(
loc, isSameRank,
RuntimeVerifiableOpInterface::generateErrorMessage(op,
"rank mismatch"));
}
int64_t dynamicOffset = ShapedType::kDynamic;
SmallVector<int64_t> dynamicShape(resultType.getRank(),
ShapedType::kDynamic);
auto stridedLayout = StridedLayoutAttr::get(builder.getContext(),
dynamicOffset, dynamicShape);
auto dynStridesType =
MemRefType::get(dynamicShape, resultType.getElementType(),
stridedLayout, resultType.getMemorySpace());
Value helperCast =
builder.create<CastOp>(loc, dynStridesType, castOp.getSource());
auto metadataOp = builder.create<ExtractStridedMetadataOp>(loc, helperCast);
for (const auto &it : llvm::enumerate(resultType.getShape())) {
if (auto rankedSrcType = dyn_cast<MemRefType>(srcType))
if (!rankedSrcType.isDynamicDim(it.index()))
continue;
if (resultType.isDynamicDim(it.index()))
continue;
Value srcDimSz =
builder.create<DimOp>(loc, castOp.getSource(), it.index());
Value resultDimSz =
builder.create<arith::ConstantIndexOp>(loc, it.value());
Value isSameSz = builder.create<arith::CmpIOp>(
loc, arith::CmpIPredicate::eq, srcDimSz, resultDimSz);
builder.create<cf::AssertOp>(
loc, isSameSz,
RuntimeVerifiableOpInterface::generateErrorMessage(
op, "size mismatch of dim " + std::to_string(it.index())));
}
int64_t resultOffset;
SmallVector<int64_t> resultStrides;
if (failed(getStridesAndOffset(resultType, resultStrides, resultOffset)))
return;
if (resultOffset != ShapedType::kDynamic) {
Value srcOffset = metadataOp.getResult(1);
Value resultOffsetVal =
builder.create<arith::ConstantIndexOp>(loc, resultOffset);
Value isSameOffset = builder.create<arith::CmpIOp>(
loc, arith::CmpIPredicate::eq, srcOffset, resultOffsetVal);
builder.create<cf::AssertOp>(
loc, isSameOffset,
RuntimeVerifiableOpInterface::generateErrorMessage(
op, "offset mismatch"));
}
for (const auto &it : llvm::enumerate(resultStrides)) {
if (it.value() == ShapedType::kDynamic)
continue;
Value srcStride =
metadataOp.getResult(2 + resultType.getRank() + it.index());
Value resultStrideVal =
builder.create<arith::ConstantIndexOp>(loc, it.value());
Value isSameStride = builder.create<arith::CmpIOp>(
loc, arith::CmpIPredicate::eq, srcStride, resultStrideVal);
builder.create<cf::AssertOp>(
loc, isSameStride,
RuntimeVerifiableOpInterface::generateErrorMessage(
op, "stride mismatch of dim " + std::to_string(it.index())));
}
}
};
template <typename LoadStoreOp>
struct LoadStoreOpInterface
: public RuntimeVerifiableOpInterface::ExternalModel<
LoadStoreOpInterface<LoadStoreOp>, LoadStoreOp> {
void generateRuntimeVerification(Operation *op, OpBuilder &builder,
Location loc) const {
auto loadStoreOp = cast<LoadStoreOp>(op);
auto memref = loadStoreOp.getMemref();
auto rank = memref.getType().getRank();
if (rank == 0) {
return;
}
auto indices = loadStoreOp.getIndices();
auto zero = builder.create<arith::ConstantIndexOp>(loc, 0);
Value assertCond;
for (auto i : llvm::seq<int64_t>(0, rank)) {
auto index = indices[i];
auto dimOp = builder.createOrFold<memref::DimOp>(loc, memref, i);
auto geLow = builder.createOrFold<arith::CmpIOp>(
loc, arith::CmpIPredicate::sge, index, zero);
auto ltHigh = builder.createOrFold<arith::CmpIOp>(
loc, arith::CmpIPredicate::slt, index, dimOp);
auto andOp = builder.createOrFold<arith::AndIOp>(loc, geLow, ltHigh);
assertCond =
i > 0 ? builder.createOrFold<arith::AndIOp>(loc, assertCond, andOp)
: andOp;
}
builder.create<cf::AssertOp>(
loc, assertCond,
RuntimeVerifiableOpInterface::generateErrorMessage(
op, "out-of-bounds access"));
}
};
Value computeLinearIndex(OpBuilder &builder, Location loc, OpFoldResult offset,
ArrayRef<OpFoldResult> strides,
ArrayRef<OpFoldResult> indices) {
auto [expr, values] = computeLinearIndex(offset, strides, indices);
auto index =
affine::makeComposedFoldedAffineApply(builder, loc, expr, values);
return getValueOrCreateConstantIndexOp(builder, loc, index);
}
std::pair<Value, Value> computeLinearBounds(OpBuilder &builder, Location loc,
OpFoldResult offset,
ArrayRef<OpFoldResult> strides,
ArrayRef<OpFoldResult> sizes) {
auto zeros = SmallVector<int64_t>(sizes.size(), 0);
auto indices = getAsIndexOpFoldResult(builder.getContext(), zeros);
auto lowerBound = computeLinearIndex(builder, loc, offset, strides, indices);
auto upperBound = computeLinearIndex(builder, loc, offset, strides, sizes);
return {lowerBound, upperBound};
}
std::pair<Value, Value> computeLinearBounds(OpBuilder &builder, Location loc,
TypedValue<BaseMemRefType> memref) {
auto runtimeMetadata = builder.create<ExtractStridedMetadataOp>(loc, memref);
auto offset = runtimeMetadata.getConstifiedMixedOffset();
auto strides = runtimeMetadata.getConstifiedMixedStrides();
auto sizes = runtimeMetadata.getConstifiedMixedSizes();
return computeLinearBounds(builder, loc, offset, strides, sizes);
}
struct ReinterpretCastOpInterface
: public RuntimeVerifiableOpInterface::ExternalModel<
ReinterpretCastOpInterface, ReinterpretCastOp> {
void generateRuntimeVerification(Operation *op, OpBuilder &builder,
Location loc) const {
auto reinterpretCast = cast<ReinterpretCastOp>(op);
auto baseMemref = reinterpretCast.getSource();
auto resultMemref =
cast<TypedValue<BaseMemRefType>>(reinterpretCast.getResult());
builder.setInsertionPointAfter(op);
auto [baseLow, baseHigh] = computeLinearBounds(builder, loc, baseMemref);
auto [low, high] = computeLinearBounds(builder, loc, resultMemref);
auto geLow = builder.createOrFold<arith::CmpIOp>(
loc, arith::CmpIPredicate::sge, low, baseLow);
auto leHigh = builder.createOrFold<arith::CmpIOp>(
loc, arith::CmpIPredicate::sle, high, baseHigh);
auto assertCond = builder.createOrFold<arith::AndIOp>(loc, geLow, leHigh);
builder.create<cf::AssertOp>(
loc, assertCond,
RuntimeVerifiableOpInterface::generateErrorMessage(
op,
"result of reinterpret_cast is out-of-bounds of the base memref"));
}
};
struct SubViewOpInterface
: public RuntimeVerifiableOpInterface::ExternalModel<SubViewOpInterface,
SubViewOp> {
void generateRuntimeVerification(Operation *op, OpBuilder &builder,
Location loc) const {
auto subView = cast<SubViewOp>(op);
auto baseMemref = cast<TypedValue<BaseMemRefType>>(subView.getSource());
auto resultMemref = cast<TypedValue<BaseMemRefType>>(subView.getResult());
builder.setInsertionPointAfter(op);
auto [baseLow, baseHigh] = computeLinearBounds(builder, loc, baseMemref);
auto [low, high] = computeLinearBounds(builder, loc, resultMemref);
auto geLow = builder.createOrFold<arith::CmpIOp>(
loc, arith::CmpIPredicate::sge, low, baseLow);
auto leHigh = builder.createOrFold<arith::CmpIOp>(
loc, arith::CmpIPredicate::sle, high, baseHigh);
auto assertCond = builder.createOrFold<arith::AndIOp>(loc, geLow, leHigh);
builder.create<cf::AssertOp>(
loc, assertCond,
RuntimeVerifiableOpInterface::generateErrorMessage(
op, "subview is out-of-bounds of the base memref"));
}
};
struct ExpandShapeOpInterface
: public RuntimeVerifiableOpInterface::ExternalModel<ExpandShapeOpInterface,
ExpandShapeOp> {
void generateRuntimeVerification(Operation *op, OpBuilder &builder,
Location loc) const {
auto expandShapeOp = cast<ExpandShapeOp>(op);
for (const auto &it :
llvm::enumerate(expandShapeOp.getReassociationIndices())) {
Value srcDimSz =
builder.create<DimOp>(loc, expandShapeOp.getSrc(), it.index());
int64_t groupSz = 1;
bool foundDynamicDim = false;
for (int64_t resultDim : it.value()) {
if (expandShapeOp.getResultType().isDynamicDim(resultDim)) {
assert(!foundDynamicDim &&
"more than one dynamic dim found in reassoc group");
(void)foundDynamicDim;
foundDynamicDim = true;
continue;
}
groupSz *= expandShapeOp.getResultType().getDimSize(resultDim);
}
Value staticResultDimSz =
builder.create<arith::ConstantIndexOp>(loc, groupSz);
Value mod =
builder.create<arith::RemSIOp>(loc, srcDimSz, staticResultDimSz);
Value isModZero = builder.create<arith::CmpIOp>(
loc, arith::CmpIPredicate::eq, mod,
builder.create<arith::ConstantIndexOp>(loc, 0));
builder.create<cf::AssertOp>(
loc, isModZero,
RuntimeVerifiableOpInterface::generateErrorMessage(
op, "static result dims in reassoc group do not "
"divide src dim evenly"));
}
}
};
}
}
}
void mlir::memref::registerRuntimeVerifiableOpInterfaceExternalModels(
DialectRegistry ®istry) {
registry.addExtension(+[](MLIRContext *ctx, memref::MemRefDialect *dialect) {
CastOp::attachInterface<CastOpInterface>(*ctx);
ExpandShapeOp::attachInterface<ExpandShapeOpInterface>(*ctx);
LoadOp::attachInterface<LoadStoreOpInterface<LoadOp>>(*ctx);
ReinterpretCastOp::attachInterface<ReinterpretCastOpInterface>(*ctx);
StoreOp::attachInterface<LoadStoreOpInterface<StoreOp>>(*ctx);
SubViewOp::attachInterface<SubViewOpInterface>(*ctx);
ctx->loadDialect<affine::AffineDialect, arith::ArithDialect,
cf::ControlFlowDialect>();
});
}