blob: 9ac49e2eb1a143c8dc208dc859c3e5d05ef87d3c [file] [edit]
//===- MemRefToEmitC.cpp - MemRef to EmitC conversion ---------------------===//
//
// 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 patterns to convert memref ops into emitc ops.
//
//===----------------------------------------------------------------------===//
#include "mlir/Conversion/MemRefToEmitC/MemRefToEmitC.h"
#include "mlir/Conversion/ConvertToEmitC/ToEmitCInterface.h"
#include "mlir/Dialect/EmitC/IR/EmitC.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/TypeRange.h"
#include "mlir/IR/Value.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/STLExtras.h"
#include <cstdint>
#include <numeric>
using namespace mlir;
static bool isMemRefTypeLegalForEmitC(MemRefType memRefType) {
return memRefType.hasStaticShape() && memRefType.getLayout().isIdentity() &&
!llvm::is_contained(memRefType.getShape(), 0);
}
namespace {
/// Implement the interface to convert MemRef to EmitC.
struct MemRefToEmitCDialectInterface : public ConvertToEmitCPatternInterface {
MemRefToEmitCDialectInterface(Dialect *dialect)
: ConvertToEmitCPatternInterface(dialect) {}
/// Hook for derived dialect interface to provide conversion patterns
/// and mark dialect legal for the conversion target.
void populateConvertToEmitCConversionPatterns(
ConversionTarget &target, TypeConverter &typeConverter,
RewritePatternSet &patterns, std::optional<bool> lowerToCpp) const final {
populateMemRefToEmitCConversionPatterns(patterns, typeConverter);
}
};
} // namespace
void mlir::registerConvertMemRefToEmitCInterface(DialectRegistry &registry) {
registry.addExtension(+[](MLIRContext *ctx, memref::MemRefDialect *dialect) {
dialect->addInterfaces<MemRefToEmitCDialectInterface>();
});
}
//===----------------------------------------------------------------------===//
// Conversion Patterns
//===----------------------------------------------------------------------===//
namespace {
struct ConvertAlloca final : public OpConversionPattern<memref::AllocaOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(memref::AllocaOp op, OpAdaptor operands,
ConversionPatternRewriter &rewriter) const override {
if (!op.getType().hasStaticShape()) {
return rewriter.notifyMatchFailure(
op.getLoc(), "cannot transform alloca with dynamic shape");
}
if (op.getAlignment().value_or(1) > 1) {
// TODO: Allow alignment if it is not more than the natural alignment
// of the C array.
return rewriter.notifyMatchFailure(
op.getLoc(), "cannot transform alloca with alignment requirement");
}
Type resultTy = getTypeConverter()->convertType(op.getType());
if (!resultTy)
return rewriter.notifyMatchFailure(op.getLoc(), "cannot convert type");
auto noInit = emitc::OpaqueAttr::get(getContext(), "");
// Rank-0 path
if (op.getType().getRank() == 0) {
auto pointerTy = dyn_cast<emitc::PointerType>(resultTy);
assert(pointerTy && "expected rank-0 MemRef to convert to pointer");
Type elemTy = pointerTy.getPointee();
auto var = emitc::VariableOp::create(
rewriter, op.getLoc(), emitc::LValueType::get(elemTy), noInit);
auto ptr = emitc::AddressOfOp::create(rewriter, op.getLoc(), resultTy,
var.getResult());
rewriter.replaceOp(op, ptr.getResult());
return success();
}
// Rank > 0 path
rewriter.replaceOpWithNewOp<emitc::VariableOp>(op, resultTy, noInit);
return success();
}
};
static Value calculateMemrefTotalSizeBytes(Location loc, MemRefType memrefType,
OpBuilder &builder,
Type convertedElementType) {
assert(isMemRefTypeLegalForEmitC(memrefType) &&
"incompatible memref type for EmitC conversion");
emitc::CallOpaqueOp elementSize = emitc::CallOpaqueOp::create(
builder, loc, emitc::SizeTType::get(builder.getContext()),
builder.getStringAttr("sizeof"), ValueRange{},
ArrayAttr::get(builder.getContext(),
{TypeAttr::get(convertedElementType)}));
IndexType indexType = builder.getIndexType();
int64_t numElements = llvm::product_of(memrefType.getShape());
emitc::ConstantOp numElementsValue = emitc::ConstantOp::create(
builder, loc, indexType, builder.getIndexAttr(numElements));
Type sizeTType = emitc::SizeTType::get(builder.getContext());
emitc::MulOp totalSizeBytes = emitc::MulOp::create(
builder, loc, sizeTType, elementSize.getResult(0), numElementsValue);
return totalSizeBytes.getResult();
}
static emitc::AddressOfOp
createPointerFromEmitcArray(Location loc, OpBuilder &builder,
TypedValue<emitc::ArrayType> arrayValue) {
emitc::ConstantOp zeroIndex = emitc::ConstantOp::create(
builder, loc, builder.getIndexType(), builder.getIndexAttr(0));
emitc::ArrayType arrayType = arrayValue.getType();
llvm::SmallVector<mlir::Value> indices(arrayType.getRank(), zeroIndex);
emitc::SubscriptOp subPtr =
emitc::SubscriptOp::create(builder, loc, arrayValue, ValueRange(indices));
emitc::AddressOfOp ptr = emitc::AddressOfOp::create(
builder, loc, emitc::PointerType::get(arrayType.getElementType()),
subPtr);
return ptr;
}
static Value getMemRefPointer(Value v) {
if (isa<emitc::PointerType>(v.getType()))
return v;
// If `v` is defined through an unrealized cast and the source of that cast
// is `emitc.ptr`, return the pointer.
if (auto cast = v.getDefiningOp<UnrealizedConversionCastOp>())
if (cast.getNumOperands() == 1 &&
isa<emitc::PointerType>(cast.getOperand(0).getType()))
return cast.getOperand(0);
return Value();
}
static Value computeRowMajorLinearIndex(ImplicitLocOpBuilder &builder,
MemRefType memrefType,
ValueRange indices) {
ArrayRef<int64_t> shape = memrefType.getShape();
Type idxType =
indices.empty() ? builder.getIndexType() : indices[0].getType();
Value linearIndex =
indices.empty()
? emitc::ConstantOp::create(builder, idxType, builder.getIndexAttr(0))
: indices[0];
if (indices.empty())
return linearIndex;
for (auto [dim, idx] : llvm::zip(shape.drop_front(), indices.drop_front())) {
Value dimSize =
emitc::ConstantOp::create(builder, idxType, builder.getIndexAttr(dim));
linearIndex = emitc::MulOp::create(builder, idxType, linearIndex, dimSize);
linearIndex = emitc::AddOp::create(builder, idxType, linearIndex, idx);
}
return linearIndex;
}
struct ConvertAlloc final : public OpConversionPattern<memref::AllocOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(memref::AllocOp allocOp, OpAdaptor operands,
ConversionPatternRewriter &rewriter) const override {
Location loc = allocOp.getLoc();
MemRefType memrefType = allocOp.getType();
if (!isMemRefTypeLegalForEmitC(memrefType)) {
return rewriter.notifyMatchFailure(
loc, "incompatible memref type for EmitC conversion");
}
Type sizeTType = emitc::SizeTType::get(rewriter.getContext());
Type elementType =
getTypeConverter()->convertType(memrefType.getElementType());
if (!elementType) {
return rewriter.notifyMatchFailure(
loc, "failed to convert memref element type");
}
IndexType indexType = rewriter.getIndexType();
Value totalSizeBytes =
calculateMemrefTotalSizeBytes(loc, memrefType, rewriter, elementType);
emitc::CallOpaqueOp allocCall;
StringAttr allocFunctionName;
Value alignmentValue;
SmallVector<Value, 2> argsVec;
if (allocOp.getAlignment()) {
allocFunctionName = rewriter.getStringAttr(alignedAllocFunctionName);
alignmentValue = emitc::ConstantOp::create(
rewriter, loc, sizeTType,
rewriter.getIntegerAttr(indexType,
allocOp.getAlignment().value_or(0)));
argsVec.push_back(alignmentValue);
} else {
allocFunctionName = rewriter.getStringAttr(mallocFunctionName);
}
argsVec.push_back(totalSizeBytes);
ValueRange args(argsVec);
allocCall = emitc::CallOpaqueOp::create(
rewriter, loc,
emitc::PointerType::get(
emitc::OpaqueType::get(rewriter.getContext(), "void")),
allocFunctionName, args);
emitc::PointerType targetPointerType = emitc::PointerType::get(elementType);
emitc::CastOp castOp = emitc::CastOp::create(
rewriter, loc, targetPointerType, allocCall.getResult(0));
rewriter.replaceOp(allocOp, castOp);
return success();
}
};
struct ConvertDealloc final : public OpConversionPattern<memref::DeallocOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(memref::DeallocOp deallocOp, OpAdaptor operands,
ConversionPatternRewriter &rewriter) const override {
Location loc = deallocOp.getLoc();
// `free` can only be emitted when the dealloc operand is recoverable as an
// `emitc.ptr<T>`.
Value ptr = getMemRefPointer(operands.getMemref());
if (!ptr) {
return rewriter.notifyMatchFailure(
loc, "expected pointer-backed memref for EmitC deallocation");
}
// The allocation APIs used by MemRefToEmitC return `void *`, and `free`
// expects that same pointer type. Deallocation therefore only needs the
// recovered base pointer cast back to `void *` before calling `free`.
Type opaqueVoidPtrType = emitc::PointerType::get(
emitc::OpaqueType::get(rewriter.getContext(), "void"));
Value freeArg =
emitc::CastOp::create(rewriter, loc, opaqueVoidPtrType, ptr);
emitc::CallOpaqueOp freeCall = emitc::CallOpaqueOp::create(
rewriter, loc, TypeRange{}, rewriter.getStringAttr(freeFunctionName),
ValueRange{freeArg});
rewriter.replaceOp(deallocOp, freeCall.getResults());
return success();
}
};
struct ConvertCopy final : public OpConversionPattern<memref::CopyOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(memref::CopyOp copyOp, OpAdaptor operands,
ConversionPatternRewriter &rewriter) const override {
Location loc = copyOp.getLoc();
MemRefType srcMemrefType = cast<MemRefType>(copyOp.getSource().getType());
MemRefType targetMemrefType =
cast<MemRefType>(copyOp.getTarget().getType());
if (!isMemRefTypeLegalForEmitC(srcMemrefType))
return rewriter.notifyMatchFailure(
loc, "incompatible source memref type for EmitC conversion");
if (!isMemRefTypeLegalForEmitC(targetMemrefType))
return rewriter.notifyMatchFailure(
loc, "incompatible target memref type for EmitC conversion");
if (srcMemrefType.getRank() == 0) {
assert(targetMemrefType.getRank() == 0 &&
"target must have same rank as source");
Type elementType =
getTypeConverter()->convertType(srcMemrefType.getElementType());
if (!elementType)
return rewriter.notifyMatchFailure(loc, "cannot convert element type");
Value srcPtr = getMemRefPointer(operands.getSource());
Value targetPtr = getMemRefPointer(operands.getTarget());
if (!srcPtr || !targetPtr)
return rewriter.notifyMatchFailure(loc, "expected pointer operands");
Value zeroIndex = emitc::ConstantOp::create(
rewriter, loc, rewriter.getIndexType(), rewriter.getIndexAttr(0));
Value srcLValue = emitc::SubscriptOp::create(
rewriter, loc, cast<TypedValue<emitc::PointerType>>(srcPtr),
zeroIndex);
Value value =
emitc::LoadOp::create(rewriter, loc, elementType, srcLValue);
Value targetLValue = emitc::SubscriptOp::create(
rewriter, loc, cast<TypedValue<emitc::PointerType>>(targetPtr),
zeroIndex);
rewriter.replaceOpWithNewOp<emitc::AssignOp>(copyOp, targetLValue, value);
return success();
}
auto srcArrayValue =
cast<TypedValue<emitc::ArrayType>>(operands.getSource());
emitc::AddressOfOp srcPtr =
createPointerFromEmitcArray(loc, rewriter, srcArrayValue);
auto targetArrayValue =
cast<TypedValue<emitc::ArrayType>>(operands.getTarget());
emitc::AddressOfOp targetPtr =
createPointerFromEmitcArray(loc, rewriter, targetArrayValue);
Type convertedElementType =
getTypeConverter()->convertType(srcMemrefType.getElementType());
if (!convertedElementType) {
return rewriter.notifyMatchFailure(
loc, "failed to convert memref element type");
}
Value totalSizeInBytes = calculateMemrefTotalSizeBytes(
loc, srcMemrefType, rewriter, convertedElementType);
emitc::CallOpaqueOp memCpyCall =
emitc::CallOpaqueOp::create(rewriter, loc, TypeRange{}, "memcpy",
ValueRange{
targetPtr.getResult(),
srcPtr.getResult(),
totalSizeInBytes,
});
rewriter.replaceOp(copyOp, memCpyCall.getResults());
return success();
}
};
struct ConvertGlobal final : public OpConversionPattern<memref::GlobalOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(memref::GlobalOp op, OpAdaptor operands,
ConversionPatternRewriter &rewriter) const override {
MemRefType opTy = op.getType();
if (!op.getType().hasStaticShape()) {
return rewriter.notifyMatchFailure(
op.getLoc(), "cannot transform global with dynamic shape");
}
if (op.getAlignment().value_or(1) > 1) {
// TODO: Extend GlobalOp to specify alignment via the `alignas` specifier.
return rewriter.notifyMatchFailure(
op.getLoc(), "global variable with alignment requirement is "
"currently not supported");
}
Type resultTy = getTypeConverter()->convertType(opTy);
if (!resultTy) {
return rewriter.notifyMatchFailure(op.getLoc(),
"cannot convert result type");
}
SymbolTable::Visibility visibility = SymbolTable::getSymbolVisibility(op);
if (visibility != SymbolTable::Visibility::Public &&
visibility != SymbolTable::Visibility::Private) {
return rewriter.notifyMatchFailure(
op.getLoc(),
"only public and private visibility is currently supported");
}
// We are explicit in specifying the linkage because the default linkage
// for constants is different in C and C++.
bool staticSpecifier = visibility == SymbolTable::Visibility::Private;
bool externSpecifier = !staticSpecifier;
Attribute initialValue = operands.getInitialValueAttr();
if (opTy.getRank() == 0) {
auto pointerTy = dyn_cast<emitc::PointerType>(resultTy);
assert(pointerTy && "expected rank-0 MemRef to convert to pointer");
resultTy = pointerTy.getPointee();
// special case for `variable : memref<i32> = dense<-1>`
if (std::optional<Attribute> initValueAttr = op.getInitialValue()) {
if (auto elementsAttr = llvm::dyn_cast<ElementsAttr>(*initValueAttr)) {
initialValue = elementsAttr.getSplatValue<Attribute>();
}
}
}
if (isa_and_present<UnitAttr>(initialValue))
initialValue = {};
rewriter.replaceOpWithNewOp<emitc::GlobalOp>(
op, operands.getSymName(), resultTy, initialValue, externSpecifier,
staticSpecifier, operands.getConstant());
return success();
}
};
struct ConvertGetGlobal final
: public OpConversionPattern<memref::GetGlobalOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(memref::GetGlobalOp op, OpAdaptor operands,
ConversionPatternRewriter &rewriter) const override {
MemRefType opTy = op.getType();
Type resultTy = getTypeConverter()->convertType(opTy);
if (!resultTy) {
return rewriter.notifyMatchFailure(op.getLoc(),
"cannot convert result type");
}
if (opTy.getRank() == 0) {
auto pointerTy = dyn_cast<emitc::PointerType>(resultTy);
assert(pointerTy && "expected rank-0 MemRef to convert to pointer");
Type elemTy = pointerTy.getPointee();
emitc::LValueType lvalueType = emitc::LValueType::get(elemTy);
emitc::GetGlobalOp globalLValue = emitc::GetGlobalOp::create(
rewriter, op.getLoc(), lvalueType, operands.getNameAttr());
rewriter.replaceOpWithNewOp<emitc::AddressOfOp>(op, resultTy,
globalLValue);
return success();
}
rewriter.replaceOpWithNewOp<emitc::GetGlobalOp>(op, resultTy,
operands.getNameAttr());
return success();
}
};
struct ConvertLoad final : public OpConversionPattern<memref::LoadOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(memref::LoadOp op, OpAdaptor operands,
ConversionPatternRewriter &rewriter) const override {
Location loc = op.getLoc();
auto resultTy = getTypeConverter()->convertType(op.getType());
if (!resultTy) {
return rewriter.notifyMatchFailure(loc, "cannot convert type");
}
auto arrayValue =
dyn_cast<TypedValue<emitc::ArrayType>>(operands.getMemref());
Value ptr = getMemRefPointer(operands.getMemref());
if (!ptr && arrayValue) {
auto subscript = emitc::SubscriptOp::create(rewriter, loc, arrayValue,
operands.getIndices());
rewriter.replaceOpWithNewOp<emitc::LoadOp>(op, resultTy, subscript);
return success();
}
if (!ptr)
return rewriter.notifyMatchFailure(loc, "expected array or pointer type");
MemRefType opMemrefType = cast<MemRefType>(op.getMemref().getType());
ValueRange indices = operands.getIndices();
ImplicitLocOpBuilder b(loc, rewriter);
Value linearIndex = computeRowMajorLinearIndex(b, opMemrefType, indices);
auto typedPtr = cast<TypedValue<emitc::PointerType>>(ptr);
auto subscript =
emitc::SubscriptOp::create(rewriter, loc, typedPtr, linearIndex);
rewriter.replaceOpWithNewOp<emitc::LoadOp>(op, resultTy, subscript);
return success();
}
};
struct ConvertStore final : public OpConversionPattern<memref::StoreOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(memref::StoreOp op, OpAdaptor operands,
ConversionPatternRewriter &rewriter) const override {
Location loc = op.getLoc();
auto arrayValue =
dyn_cast<TypedValue<emitc::ArrayType>>(operands.getMemref());
Value ptr = getMemRefPointer(operands.getMemref());
if (!ptr && arrayValue) {
auto subscript = emitc::SubscriptOp::create(rewriter, loc, arrayValue,
operands.getIndices());
rewriter.replaceOpWithNewOp<emitc::AssignOp>(op, subscript,
operands.getValue());
return success();
}
if (!ptr)
return rewriter.notifyMatchFailure(loc, "expected array or pointer type");
MemRefType opMemrefType = cast<MemRefType>(op.getMemref().getType());
ValueRange indices = operands.getIndices();
ImplicitLocOpBuilder b(loc, rewriter);
Value linearIndex = computeRowMajorLinearIndex(b, opMemrefType, indices);
auto typedPtr = cast<TypedValue<emitc::PointerType>>(ptr);
auto subscript =
emitc::SubscriptOp::create(rewriter, loc, typedPtr, linearIndex);
rewriter.replaceOpWithNewOp<emitc::AssignOp>(op, subscript,
operands.getValue());
return success();
}
};
} // namespace
void mlir::populateMemRefToEmitCConversionPatterns(
RewritePatternSet &patterns, const TypeConverter &converter) {
patterns.add<ConvertAlloca, ConvertAlloc, ConvertCopy, ConvertDealloc,
ConvertGlobal, ConvertGetGlobal, ConvertLoad, ConvertStore>(
converter, patterns.getContext());
}