blob: 040af9c9cb9b14a9c7047cf97455f906214cfba4 [file]
//====- LowerToLLVM.cpp - Lowering from CIR to LLVMIR ---------------------===//
//
// 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 lowering of CIR operations to LLVMIR.
//
//===----------------------------------------------------------------------===//
#include "LowerToLLVM.h"
#include <array>
#include <optional>
#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
#include "mlir/Conversion/OpenMPToLLVM/ConvertOpenMPToLLVM.h"
#include "mlir/Dialect/DLTI/DLTI.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
#include "mlir/Dialect/OpenMP/Transforms/Passes.h"
#include "mlir/Dialect/Ptr/IR/MemorySpaceInterfaces.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinDialect.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Location.h"
#include "mlir/IR/Types.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Support/LLVM.h"
#include "mlir/Target/LLVMIR/Dialect/Builtin/BuiltinToLLVMIRTranslation.h"
#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h"
#include "mlir/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.h"
#include "mlir/Target/LLVMIR/Export.h"
#include "mlir/Transforms/DialectConversion.h"
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/CIR/Dialect/Passes.h"
#include "clang/CIR/Dialect/Transforms/CIRTransformUtils.h"
#include "clang/CIR/LoweringHelpers.h"
#include "clang/CIR/MissingFeatures.h"
#include "clang/CIR/Passes.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/raw_ostream.h"
using namespace cir;
using namespace llvm;
namespace cir {
namespace direct {
//===----------------------------------------------------------------------===//
// Helper Methods
//===----------------------------------------------------------------------===//
namespace {
/// If the given type is a vector type, return the vector's element type.
/// Otherwise return the given type unchanged.
mlir::Type elementTypeIfVector(mlir::Type type) {
return llvm::TypeSwitch<mlir::Type, mlir::Type>(type)
.Case<cir::VectorType, mlir::VectorType>(
[](auto p) { return p.getElementType(); })
.Default([](mlir::Type p) { return p; });
}
} // namespace
/// In-memory storage width in bits for a _BitInt(N): N rounded up to the type's
/// ABI alignment. This equals sizeof(_BitInt(N)) * 8 on the default target
/// (e.g. _BitInt(6) -> 8, _BitInt(17) -> 32, _BitInt(128) -> 128).
static unsigned getBitIntMemoryStorageBits(cir::IntType ty,
const mlir::DataLayout &dataLayout) {
uint64_t alignBits = ty.getABIAlignment(dataLayout, {}) * 8;
return llvm::alignTo(ty.getWidth(), alignBits);
}
/// A _BitInt(N) whose padded storage integer iM has a larger alloc size than
/// its M/8 store size is laid out by clang as a byte array, not a plain integer
/// (e.g. _BitInt(129) -> i192 with alloc size 32 != store size 24). That
/// "split" storage form is not yet implemented; lowerings must detect it and
/// report errorNYI rather than emit the wrong-sized integer.
static bool isSplitStorageBitInt(cir::IntType ty,
const mlir::DataLayout &dataLayout) {
if (!ty.isBitInt())
return false;
unsigned storageBits = getBitIntMemoryStorageBits(ty, dataLayout);
auto storageTy = mlir::IntegerType::get(ty.getContext(), storageBits);
uint64_t storeSize = storageBits / 8;
uint64_t allocSize =
llvm::alignTo(storeSize, dataLayout.getTypeABIAlignment(storageTy));
return allocSize != storeSize;
}
/// Given a type convertor and a data layout, convert the given type to a type
/// that is suitable for memory operations. For example, this can be used to
/// lower cir.bool accesses to i8.
static mlir::Type convertTypeForMemory(const mlir::TypeConverter &converter,
mlir::DataLayout const &dataLayout,
mlir::Type type) {
// TODO(cir): Handle other types similarly to clang's codegen
// convertTypeForMemory
if (isa<cir::BoolType>(type)) {
return mlir::IntegerType::get(type.getContext(),
dataLayout.getTypeSizeInBits(type));
}
if (auto vecTy = mlir::dyn_cast<cir::VectorType>(type)) {
if (mlir::isa<cir::BoolType>(vecTy.getElementType())) {
assert(!cir::MissingFeatures::hlsl());
// Pad to at least one byte.
uint64_t bytePadded = std::max<uint64_t>(vecTy.getSize(), 8);
return mlir::IntegerType::get(type.getContext(), bytePadded);
}
}
// _BitInt(N) keeps its literal width as a value but is stored in a padded
// integer iM in memory, the same way bool is i1 as a value and i8 in memory.
// The byte-array storage form for wide split widths is not implemented; a
// null return signals that, and op lowerings turn it into errorNYI.
if (auto intTy = mlir::dyn_cast<cir::IntType>(type);
intTy && intTy.isBitInt()) {
if (isSplitStorageBitInt(intTy, dataLayout))
return {};
return mlir::IntegerType::get(
type.getContext(), getBitIntMemoryStorageBits(intTy, dataLayout));
}
return converter.convertType(type);
}
/// Alignment to use for a memory access whose op carries no explicit alignment.
/// For _BitInt the storage integer iM's ABI alignment (e.g. i128's 16)
/// over-aligns the value, so use the CIR _BitInt ABI alignment (e.g. 8).
static uint64_t getMemoryFallbackAlignment(mlir::Type cirType,
mlir::Type llvmMemType,
const mlir::DataLayout &dataLayout) {
if (auto intTy = mlir::dyn_cast<cir::IntType>(cirType);
intTy && intTy.isBitInt())
return intTy.getABIAlignment(dataLayout, {});
return dataLayout.getTypeABIAlignment(llvmMemType);
}
static mlir::Value createIntCast(mlir::OpBuilder &bld, mlir::Value src,
mlir::IntegerType dstTy,
bool isSigned = false) {
mlir::Type srcTy = src.getType();
assert(mlir::isa<mlir::IntegerType>(srcTy));
unsigned srcWidth = mlir::cast<mlir::IntegerType>(srcTy).getWidth();
unsigned dstWidth = mlir::cast<mlir::IntegerType>(dstTy).getWidth();
mlir::Location loc = src.getLoc();
if (dstWidth > srcWidth && isSigned)
return mlir::LLVM::SExtOp::create(bld, loc, dstTy, src);
if (dstWidth > srcWidth)
return mlir::LLVM::ZExtOp::create(bld, loc, dstTy, src);
if (dstWidth < srcWidth)
return mlir::LLVM::TruncOp::create(bld, loc, dstTy, src);
return mlir::LLVM::BitcastOp::create(bld, loc, dstTy, src);
}
/// Cast a _BitInt(N) value between its literal width iN and its padded
/// in-memory storage iM (sign/zero-extend to memory per signedness so the
/// padding bits are well-defined, truncate back on load; matches classic
/// CodeGen). Callers must first rule out the unsupported byte-array storage
/// form (isSplitStorageBitInt / a null convertTypeForMemory result).
static mlir::Value
castBitIntMemoryStorage(mlir::ConversionPatternRewriter &rewriter,
const mlir::DataLayout &dataLayout, cir::IntType intTy,
mlir::Value value, bool toMemory) {
unsigned storageBits = getBitIntMemoryStorageBits(intTy, dataLayout);
if (storageBits == intTy.getWidth())
return value;
unsigned dstBits = toMemory ? storageBits : intTy.getWidth();
return createIntCast(rewriter, value, rewriter.getIntegerType(dstBits),
/*isSigned=*/toMemory && intTy.isSigned());
}
static mlir::LLVM::Visibility
lowerCIRVisibilityToLLVMVisibility(cir::VisibilityKind visibilityKind) {
switch (visibilityKind) {
case cir::VisibilityKind::Default:
return ::mlir::LLVM::Visibility::Default;
case cir::VisibilityKind::Hidden:
return ::mlir::LLVM::Visibility::Hidden;
case cir::VisibilityKind::Protected:
return ::mlir::LLVM::Visibility::Protected;
}
}
static mlir::Value
emitBoolVecConversion(mlir::ConversionPatternRewriter &rewriter,
mlir::Value srcVec, unsigned numElementsDst) {
auto srcTy = mlir::cast<mlir::VectorType>(srcVec.getType());
unsigned numElementsSrc = srcTy.getNumElements();
if (numElementsSrc == numElementsDst)
return srcVec;
SmallVector<int32_t, 8> mask(numElementsDst, -1);
for (unsigned i : llvm::seq(std::min(numElementsDst, numElementsSrc)))
mask[i] = i;
mlir::Location loc = srcVec.getLoc();
auto poison = mlir::LLVM::PoisonOp::create(rewriter, loc, srcVec.getType());
return mlir::LLVM::ShuffleVectorOp::create(rewriter, loc, srcVec, poison,
mask);
}
/// Emits the value from memory as expected by its users. Should be called when
/// the memory represetnation of a CIR type is not equal to its scalar
/// representation.
static mlir::Value emitFromMemory(mlir::ConversionPatternRewriter &rewriter,
const mlir::TypeConverter &converter,
mlir::DataLayout const &dataLayout,
cir::LoadOp op, mlir::Value value) {
// TODO(cir): Handle other types similarly to clang's codegen EmitFromMemory
if (auto boolTy = mlir::dyn_cast<cir::BoolType>(op.getType())) {
// Create a cast value from specified size in datalayout to i1
assert(value.getType().isInteger(dataLayout.getTypeSizeInBits(boolTy)));
return createIntCast(rewriter, value, rewriter.getI1Type());
}
// Convert the `iN` back to boolean vectors
if (auto vecTy = mlir::dyn_cast<cir::VectorType>(op.getType())) {
if (mlir::isa<cir::BoolType>(vecTy.getElementType())) {
auto rawIntTy = mlir::cast<mlir::IntegerType>(value.getType());
auto paddedVecTy =
cir::VectorType::get(vecTy.getElementType(), rawIntTy.getWidth());
mlir::Type mlirVecTy = converter.convertType(paddedVecTy);
// Bitcast iP --> <P x i1>.
auto v = mlir::LLVM::BitcastOp::create(rewriter, value.getLoc(),
mlirVecTy, value);
// Shuffle <P x i1> --> <N x i1> (N is the actual bit size).
return emitBoolVecConversion(rewriter, v, vecTy.getSize());
}
}
// Truncate the padded storage integer back to the _BitInt's literal width.
if (auto intTy = mlir::dyn_cast<cir::IntType>(op.getType());
intTy && intTy.isBitInt())
return castBitIntMemoryStorage(rewriter, dataLayout, intTy, value,
/*toMemory=*/false);
return value;
}
/// Emits a value to memory with the expected scalar type. Should be called when
/// the memory represetnation of a CIR type is not equal to its scalar
/// representation.
static mlir::Value emitToMemory(mlir::ConversionPatternRewriter &rewriter,
mlir::DataLayout const &dataLayout,
mlir::Type origType, mlir::Value value) {
// TODO(cir): Handle other types similarly to clang's codegen EmitToMemory
if (auto boolTy = mlir::dyn_cast<cir::BoolType>(origType)) {
// Create zext of value from i1 to i8
mlir::IntegerType memType =
rewriter.getIntegerType(dataLayout.getTypeSizeInBits(boolTy));
return createIntCast(rewriter, value, memType);
}
// Boolean vectors use `iN` as storage type
if (auto vecTy = mlir::dyn_cast<cir::VectorType>(origType)) {
if (mlir::isa<cir::BoolType>(vecTy.getElementType())) {
uint64_t bytePadded = std::max<uint64_t>(vecTy.getSize(), 8);
auto resultTy = mlir::IntegerType::get(origType.getContext(), bytePadded);
value = emitBoolVecConversion(rewriter, value, resultTy.getWidth());
return mlir::LLVM::BitcastOp::create(rewriter, value.getLoc(), resultTy,
value);
}
}
// Sign/zero-extend the _BitInt value to its padded storage integer.
if (auto intTy = mlir::dyn_cast<cir::IntType>(origType);
intTy && intTy.isBitInt())
return castBitIntMemoryStorage(rewriter, dataLayout, intTy, value,
/*toMemory=*/true);
return value;
}
mlir::LLVM::Linkage convertLinkage(cir::GlobalLinkageKind linkage) {
using CIR = cir::GlobalLinkageKind;
using LLVM = mlir::LLVM::Linkage;
switch (linkage) {
case CIR::AppendingLinkage:
return LLVM::Appending;
case CIR::AvailableExternallyLinkage:
return LLVM::AvailableExternally;
case CIR::CommonLinkage:
return LLVM::Common;
case CIR::ExternalLinkage:
return LLVM::External;
case CIR::ExternalWeakLinkage:
return LLVM::ExternWeak;
case CIR::InternalLinkage:
return LLVM::Internal;
case CIR::LinkOnceAnyLinkage:
return LLVM::Linkonce;
case CIR::LinkOnceODRLinkage:
return LLVM::LinkonceODR;
case CIR::PrivateLinkage:
return LLVM::Private;
case CIR::WeakAnyLinkage:
return LLVM::Weak;
case CIR::WeakODRLinkage:
return LLVM::WeakODR;
};
llvm_unreachable("Unknown CIR linkage type");
}
static mlir::LLVM::CConv convertCallingConv(cir::CallingConv callingConv) {
using CIR = cir::CallingConv;
using LLVM = mlir::LLVM::CConv;
switch (callingConv) {
case CIR::C:
return LLVM::C;
case CIR::SpirKernel:
return LLVM::SPIR_KERNEL;
case CIR::SpirFunction:
return LLVM::SPIR_FUNC;
case CIR::PTXKernel:
return LLVM::PTX_Kernel;
case CIR::AMDGPUKernel:
return LLVM::AMDGPU_KERNEL;
}
llvm_unreachable("Unknown calling convention");
}
mlir::LogicalResult CIRToLLVMCopyOpLowering::matchAndRewrite(
cir::CopyOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::DataLayout layout(op->getParentOfType<mlir::ModuleOp>());
const mlir::Value length = mlir::LLVM::ConstantOp::create(
rewriter, op.getLoc(), rewriter.getI64Type(),
op.getCopySizeInBytes(layout));
assert(!cir::MissingFeatures::aggValueSlotVolatile());
uint64_t dstTypeAlign = dataLayout.getTypeABIAlignment(convertTypeForMemory(
*getTypeConverter(), dataLayout, op.getDst().getType().getPointee()));
uint64_t srcTypeAlign = dataLayout.getTypeABIAlignment(convertTypeForMemory(
*getTypeConverter(), dataLayout, op.getSrc().getType().getPointee()));
mlir::NamedAttribute dstAlignAttr = rewriter.getNamedAttr(
mlir::LLVM::LLVMDialect::getAlignAttrName(),
rewriter.getI64IntegerAttr(op.getDstAlignment().value_or(dstTypeAlign)));
mlir::NamedAttribute srcAlignAttr = rewriter.getNamedAttr(
mlir::LLVM::LLVMDialect::getAlignAttrName(),
rewriter.getI64IntegerAttr(op.getSrcAlignment().value_or(srcTypeAlign)));
mlir::ArrayAttr argAttrs = rewriter.getArrayAttr({
/*dst_attrs=*/rewriter.getDictionaryAttr({dstAlignAttr}),
/*src_attrs=*/rewriter.getDictionaryAttr({srcAlignAttr}),
});
rewriter.replaceOpWithNewOp<mlir::LLVM::MemcpyOp>(
op, adaptor.getDst(), adaptor.getSrc(), length, op.getIsVolatile(),
/*access_groups=*/nullptr, /*alias_scopes=*/nullptr,
/*noalias_scopes=*/nullptr, /*tbaa=*/nullptr, /*arg_attrs=*/argAttrs,
/*res_attrs=*/nullptr);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMMemCpyOpLowering::matchAndRewrite(
cir::MemCpyOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
rewriter.replaceOpWithNewOp<mlir::LLVM::MemcpyOp>(
op, adaptor.getDst(), adaptor.getSrc(), adaptor.getLen(),
/*isVolatile=*/false);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMMemMoveOpLowering::matchAndRewrite(
cir::MemMoveOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
rewriter.replaceOpWithNewOp<mlir::LLVM::MemmoveOp>(
op, adaptor.getDst(), adaptor.getSrc(), adaptor.getLen(),
/*isVolatile=*/false);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMMemSetOpLowering::matchAndRewrite(
cir::MemSetOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto memset = rewriter.replaceOpWithNewOp<mlir::LLVM::MemsetOp>(
op, adaptor.getDst(), adaptor.getVal(), adaptor.getLen(),
/*isVolatile=*/false);
if (op.getAlignmentAttr()) {
// Construct a list full of empty attributes.
llvm::SmallVector<mlir::Attribute> attrs{memset.getNumOperands(),
rewriter.getDictionaryAttr({})};
llvm::SmallVector<mlir::NamedAttribute> destAttrs;
destAttrs.push_back(
{mlir::LLVM::LLVMDialect::getAlignAttrName(), op.getAlignmentAttr()});
attrs[memset.odsIndex_dst] = rewriter.getDictionaryAttr(destAttrs);
auto arrayAttr = rewriter.getArrayAttr(attrs);
memset.setArgAttrsAttr(arrayAttr);
}
return mlir::success();
}
static mlir::Value getLLVMIntCast(mlir::ConversionPatternRewriter &rewriter,
mlir::Value llvmSrc, mlir::Type llvmDstIntTy,
bool isUnsigned, uint64_t cirSrcWidth,
uint64_t cirDstIntWidth) {
if (cirSrcWidth == cirDstIntWidth)
return llvmSrc;
auto loc = llvmSrc.getLoc();
if (cirSrcWidth < cirDstIntWidth) {
if (isUnsigned)
return mlir::LLVM::ZExtOp::create(rewriter, loc, llvmDstIntTy, llvmSrc);
return mlir::LLVM::SExtOp::create(rewriter, loc, llvmDstIntTy, llvmSrc);
}
// Otherwise truncate
return mlir::LLVM::TruncOp::create(rewriter, loc, llvmDstIntTy, llvmSrc);
}
class CIRAttrToValue {
public:
CIRAttrToValue(mlir::Operation *parentOp,
mlir::ConversionPatternRewriter &rewriter,
mlir::SymbolTableCollection &symbolTables,
const mlir::TypeConverter *converter,
LLVMBlockAddressInfo *blockInfoAddr = nullptr)
: parentOp(parentOp), rewriter(rewriter), symbolTables(symbolTables),
converter(converter), blockInfoAddr(blockInfoAddr) {}
#define GET_CIR_ATTR_TO_VALUE_VISITOR_DECLS
#include "clang/CIR/Dialect/IR/CIRLowering.inc"
#undef GET_CIR_ATTR_TO_VALUE_VISITOR_DECLS
private:
mlir::Operation *parentOp;
mlir::ConversionPatternRewriter &rewriter;
mlir::SymbolTableCollection &symbolTables;
const mlir::TypeConverter *converter;
// Only available when lowering global initializers that may contain block
// address attributes. Used to resolve a BlockAddrInfoAttr to its block tag.
LLVMBlockAddressInfo *blockInfoAddr;
};
/// Switches on the type of attribute and calls the appropriate conversion.
mlir::Value lowerCirAttrAsValue(mlir::Operation *parentOp,
const mlir::Attribute attr,
mlir::ConversionPatternRewriter &rewriter,
mlir::SymbolTableCollection &symbolTables,
const mlir::TypeConverter *converter,
LLVMBlockAddressInfo *blockInfoAddr) {
CIRAttrToValue valueConverter(parentOp, rewriter, symbolTables, converter,
blockInfoAddr);
mlir::Value value = valueConverter.visit(attr);
if (!value)
llvm_unreachable("unhandled attribute type");
return value;
}
void convertSideEffectForCall(mlir::Operation *callOp, bool isNothrow,
cir::SideEffect sideEffect,
mlir::LLVM::MemoryEffectsAttr &memoryEffect,
bool &noUnwind, bool &willReturn,
bool &noReturn) {
using mlir::LLVM::ModRefInfo;
switch (sideEffect) {
case cir::SideEffect::All:
memoryEffect = {};
noUnwind = isNothrow;
willReturn = false;
break;
case cir::SideEffect::Pure:
memoryEffect = mlir::LLVM::MemoryEffectsAttr::get(
callOp->getContext(), /*other=*/ModRefInfo::Ref,
/*argMem=*/ModRefInfo::Ref,
/*inaccessibleMem=*/ModRefInfo::Ref,
/*errnoMem=*/ModRefInfo::Ref,
/*targetMem0=*/ModRefInfo::Ref,
/*targetMem1=*/ModRefInfo::Ref);
noUnwind = true;
willReturn = true;
break;
case cir::SideEffect::Const:
memoryEffect = mlir::LLVM::MemoryEffectsAttr::get(
callOp->getContext(), /*other=*/ModRefInfo::NoModRef,
/*argMem=*/ModRefInfo::NoModRef,
/*inaccessibleMem=*/ModRefInfo::NoModRef,
/*errnoMem=*/ModRefInfo::NoModRef,
/*targetMem0=*/ModRefInfo::NoModRef,
/*targetMem1=*/ModRefInfo::NoModRef);
noUnwind = true;
willReturn = true;
break;
}
noReturn = callOp->hasAttr(CIRDialect::getNoReturnAttrName());
}
static mlir::LLVM::CallIntrinsicOp
createCallLLVMIntrinsicOp(mlir::ConversionPatternRewriter &rewriter,
mlir::Location loc, const llvm::Twine &intrinsicName,
mlir::Type resultTy, mlir::ValueRange operands,
mlir::LLVM::FastmathFlags fastmathFlags = {}) {
auto intrinsicNameAttr =
mlir::StringAttr::get(rewriter.getContext(), intrinsicName);
auto fmfAttr =
mlir::LLVM::FastmathFlagsAttr::get(rewriter.getContext(), fastmathFlags);
// CallIntrinsicOp has distinct void / result create overloads. The FMF
// builders take a TypeRange for results.
if (resultTy)
return mlir::LLVM::CallIntrinsicOp::create(
rewriter, loc, mlir::TypeRange{resultTy}, intrinsicNameAttr, operands,
fmfAttr);
return mlir::LLVM::CallIntrinsicOp::create(rewriter, loc, intrinsicNameAttr,
operands, fmfAttr);
}
static mlir::LLVM::CallIntrinsicOp replaceOpWithCallLLVMIntrinsicOp(
mlir::ConversionPatternRewriter &rewriter, mlir::Operation *op,
const llvm::Twine &intrinsicName, mlir::Type resultTy,
mlir::ValueRange operands, mlir::LLVM::FastmathFlags fastmathFlags = {}) {
mlir::LLVM::CallIntrinsicOp callIntrinOp = createCallLLVMIntrinsicOp(
rewriter, op->getLoc(), intrinsicName, resultTy, operands, fastmathFlags);
rewriter.replaceOp(op, callIntrinOp.getOperation());
return callIntrinOp;
}
static llvm::StringRef getConstrainedRoundingMetadata(cir::FenvAttr fenv) {
std::optional<cir::FPDynamicRoundingMode> rounding =
fenv.getDynamicRoundingMode();
if (!rounding)
return "round.tonearest";
switch (*rounding) {
case cir::FPDynamicRoundingMode::ToNearest:
return "round.tonearest";
case cir::FPDynamicRoundingMode::Downward:
return "round.downward";
case cir::FPDynamicRoundingMode::Upward:
return "round.upward";
case cir::FPDynamicRoundingMode::UpwardZero:
return "round.towardzero";
case cir::FPDynamicRoundingMode::ToNearestAway:
return "round.tonearestaway";
case cir::FPDynamicRoundingMode::Unknown:
return "round.dynamic";
}
llvm_unreachable("unknown FP dynamic rounding mode");
}
static llvm::StringRef getConstrainedExceptMetadata(cir::FenvAttr fenv) {
std::optional<cir::FPExceptionMode> exceptMode = fenv.getExceptMode();
if (exceptMode == cir::FPExceptionMode::Masked)
return "fpexcept.ignore";
mlir::BoolAttr strictExcept = fenv.getStrictExcept();
if (!strictExcept)
return "fpexcept.ignore";
return strictExcept.getValue() ? "fpexcept.strict" : "fpexcept.maytrap";
}
static mlir::Value
createFenvMetadataValue(mlir::ConversionPatternRewriter &rewriter,
mlir::Location loc, llvm::StringRef str) {
auto mdString = mlir::LLVM::MDStringAttr::get(
rewriter.getContext(), mlir::StringAttr::get(rewriter.getContext(), str));
return mlir::LLVM::MetadataAsValueOp::create(rewriter, loc, mdString);
}
mlir::LogicalResult lowerToConstrainedFPIntrinsic(
mlir::Operation *op, mlir::ValueRange operands, cir::FenvAttr fenv,
mlir::Type llvmResTy, mlir::ConversionPatternRewriter &rewriter,
llvm::StringRef constrainedMnemonic, bool hasRoundingMode,
mlir::LLVM::FastmathFlags fastmathFlags) {
mlir::Location loc = op->getLoc();
llvm::SmallVector<mlir::Value> callOperands(operands.begin(), operands.end());
if (hasRoundingMode)
callOperands.push_back(createFenvMetadataValue(
rewriter, loc, getConstrainedRoundingMetadata(fenv)));
callOperands.push_back(createFenvMetadataValue(
rewriter, loc, getConstrainedExceptMetadata(fenv)));
replaceOpWithCallLLVMIntrinsicOp(
rewriter, op, "llvm.experimental.constrained." + constrainedMnemonic,
llvmResTy, callOperands, fastmathFlags);
return mlir::success();
}
template <typename LLVMOp>
mlir::LogicalResult lowerConstrainableFPOp(
mlir::Operation *op, mlir::ValueRange operands, cir::FenvAttr fenv,
const mlir::TypeConverter &typeConverter,
mlir::ConversionPatternRewriter &rewriter,
llvm::StringRef constrainedMnemonic, bool hasRoundingMode) {
mlir::Type llvmResTy = typeConverter.convertType(op->getResultTypes()[0]);
if (!llvmResTy)
return op->emitError("expected LLVM result type for floating-point op");
if (!fenv) {
rewriter.replaceOpWithNewOp<LLVMOp>(op, llvmResTy, operands);
return mlir::success();
}
return lowerToConstrainedFPIntrinsic(op, operands, fenv, llvmResTy, rewriter,
constrainedMnemonic, hasRoundingMode);
}
mlir::LogicalResult CIRToLLVMLLVMIntrinsicCallOpLowering::matchAndRewrite(
cir::LLVMIntrinsicCallOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// Result is Optional on the op, so void intrinsics have zero
// results; leave llvmResTy null in that case.
mlir::Type llvmResTy;
if (op->getNumResults() != 0) {
llvmResTy = getTypeConverter()->convertType(op->getResultTypes()[0]);
if (!llvmResTy)
return op.emitError("expected LLVM result type");
}
StringRef name = op.getIntrinsicName();
// Some LLVM intrinsics require ElementType attribute to be attached to
// the argument of pointer type. That prevents us from generating LLVM IR
// because from LLVM dialect, we have LLVM IR like the below which fails
// LLVM IR verification.
// %3 = call i64 @llvm.aarch64.ldxr.p0(ptr %2)
// The expected LLVM IR should be like
// %3 = call i64 @llvm.aarch64.ldxr.p0(ptr elementtype(i32) %2)
// TODO(cir): MLIR LLVM dialect should handle this part as CIR has no way
// to set LLVM IR attribute.
assert(!cir::MissingFeatures::intrinsicElementTypeSupport());
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm." + name, llvmResTy,
adaptor.getOperands());
return mlir::success();
}
/// BoolAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::BoolAttr boolAttr) {
mlir::Location loc = parentOp->getLoc();
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
mlir::Value boolVal = mlir::LLVM::ConstantOp::create(
rewriter, loc, converter->convertType(boolAttr.getType()),
boolAttr.getValue());
return emitToMemory(rewriter, layout, boolAttr.getType(), boolVal);
}
/// IntAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::IntAttr intAttr) {
mlir::Location loc = parentOp->getLoc();
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
// Materialize the value at its literal width, then widen to the in-memory
// storage type (a no-op except for _BitInt) so aggregate members built here
// match the iM struct/array fields produced by convertTypeForMemory.
mlir::Value val = mlir::LLVM::ConstantOp::create(
rewriter, loc, converter->convertType(intAttr.getType()),
intAttr.getValue());
return emitToMemory(rewriter, layout, intAttr.getType(), val);
}
/// FPAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::FPAttr fltAttr) {
mlir::Location loc = parentOp->getLoc();
return mlir::LLVM::ConstantOp::create(
rewriter, loc, converter->convertType(fltAttr.getType()),
fltAttr.getValue());
}
/// ConstComplexAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::ConstComplexAttr complexAttr) {
auto complexType = mlir::cast<cir::ComplexType>(complexAttr.getType());
mlir::Type complexElemTy = complexType.getElementType();
mlir::Type complexElemLLVMTy = converter->convertType(complexElemTy);
mlir::Attribute components[2];
if (const auto intType = mlir::dyn_cast<cir::IntType>(complexElemTy)) {
components[0] = rewriter.getIntegerAttr(
complexElemLLVMTy,
mlir::cast<cir::IntAttr>(complexAttr.getReal()).getValue());
components[1] = rewriter.getIntegerAttr(
complexElemLLVMTy,
mlir::cast<cir::IntAttr>(complexAttr.getImag()).getValue());
} else {
components[0] = rewriter.getFloatAttr(
complexElemLLVMTy,
mlir::cast<cir::FPAttr>(complexAttr.getReal()).getValue());
components[1] = rewriter.getFloatAttr(
complexElemLLVMTy,
mlir::cast<cir::FPAttr>(complexAttr.getImag()).getValue());
}
mlir::Location loc = parentOp->getLoc();
return mlir::LLVM::ConstantOp::create(
rewriter, loc, converter->convertType(complexAttr.getType()),
rewriter.getArrayAttr(components));
}
/// ConstPtrAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::ConstPtrAttr ptrAttr) {
mlir::Location loc = parentOp->getLoc();
if (ptrAttr.isNullValue()) {
return mlir::LLVM::ZeroOp::create(
rewriter, loc, converter->convertType(ptrAttr.getType()));
}
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
mlir::Value ptrVal = mlir::LLVM::ConstantOp::create(
rewriter, loc,
rewriter.getIntegerType(layout.getTypeSizeInBits(ptrAttr.getType())),
ptrAttr.getValue().getInt());
return mlir::LLVM::IntToPtrOp::create(
rewriter, loc, converter->convertType(ptrAttr.getType()), ptrVal);
}
/// BlockAddrInfoAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::BlockAddrInfoAttr blockAddrInfo) {
assert(blockInfoAddr &&
"block address lowering requires LLVMBlockAddressInfo");
// A block address is lowered to an llvm.blockaddress op that references a
// block tag inside the target function. The matching block tag may not have
// been emitted yet, in which case the address is recorded as unresolved and
// patched up later in resolveBlockAddressOp.
mlir::Location loc = parentOp->getLoc();
mlir::LLVM::BlockTagOp matchLabel =
blockInfoAddr->lookupBlockTag(blockAddrInfo);
mlir::LLVM::BlockTagAttr tagAttr =
matchLabel ? matchLabel.getTag() : mlir::LLVM::BlockTagAttr{};
auto blkAddr = mlir::LLVM::BlockAddressAttr::get(
rewriter.getContext(), blockAddrInfo.getFunc(), tagAttr);
auto blockAddressOp = mlir::LLVM::BlockAddressOp::create(
rewriter, loc, mlir::LLVM::LLVMPointerType::get(rewriter.getContext()),
blkAddr);
if (!matchLabel)
blockInfoAddr->addUnresolvedBlockAddress(blockAddressOp, blockAddrInfo);
return blockAddressOp;
}
/// BlockAddrDiffAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::BlockAddrDiffAttr blockAddrDiff) {
assert(blockInfoAddr &&
"block address lowering requires LLVMBlockAddressInfo");
// A block-address difference initializer is lowered to the difference of the
// two block addresses: trunc(ptrtoint(lhs) - ptrtoint(rhs)). Just like a
// single block address, each referenced block tag may not have been emitted
// yet, in which case it is recorded as unresolved and patched up later in
// resolveBlockAddressOp.
mlir::Location loc = parentOp->getLoc();
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
mlir::MLIRContext *ctx = rewriter.getContext();
auto ptrTy = mlir::LLVM::LLVMPointerType::get(ctx);
auto emitBlockAddr = [&](mlir::StringAttr label) -> mlir::Value {
auto info = cir::BlockAddrInfoAttr::get(
ctx, blockAddrDiff.getFunc().getValue(), label.getValue());
mlir::LLVM::BlockTagOp matchLabel = blockInfoAddr->lookupBlockTag(info);
mlir::LLVM::BlockTagAttr tagAttr =
matchLabel ? matchLabel.getTag() : mlir::LLVM::BlockTagAttr{};
auto blkAddr = mlir::LLVM::BlockAddressAttr::get(
ctx, blockAddrDiff.getFunc(), tagAttr);
auto addrOp =
mlir::LLVM::BlockAddressOp::create(rewriter, loc, ptrTy, blkAddr);
if (!matchLabel)
blockInfoAddr->addUnresolvedBlockAddress(addrOp, info);
return addrOp;
};
mlir::Value lhsAddr = emitBlockAddr(blockAddrDiff.getLhsLabel());
mlir::Value rhsAddr = emitBlockAddr(blockAddrDiff.getRhsLabel());
// Compute the difference in a pointer-sized integer, then truncate to the
// initializer's type. LLVM is sensitive about the exact format of the
// address-of-label difference, so the truncation must happen after the
// subtraction.
mlir::Type intptrTy =
rewriter.getIntegerType(layout.getTypeSizeInBits(ptrTy));
mlir::Value lhsInt =
mlir::LLVM::PtrToIntOp::create(rewriter, loc, intptrTy, lhsAddr);
mlir::Value rhsInt =
mlir::LLVM::PtrToIntOp::create(rewriter, loc, intptrTy, rhsAddr);
mlir::Value diffVal =
mlir::LLVM::SubOp::create(rewriter, loc, lhsInt, rhsInt);
mlir::Type resultTy = converter->convertType(blockAddrDiff.getType());
mlir::Value result = diffVal;
if (resultTy != intptrTy)
result = mlir::LLVM::TruncOp::create(rewriter, loc, resultTy, diffVal);
return result;
}
// ConstArrayAttr visitor
mlir::Value CIRAttrToValue::visitCirAttr(cir::ConstArrayAttr attr) {
mlir::Type llvmTy = converter->convertType(attr.getType());
mlir::Location loc = parentOp->getLoc();
mlir::Value result;
// When the array can be represented as a single dense constant, emit one
// llvm.mlir.constant instead of a chain of llvm.insertvalue ops.
if (std::optional<mlir::Attribute> denseAttr =
lowerConstArrayAttr(attr, symbolTables, converter))
return mlir::LLVM::ConstantOp::create(rewriter, loc, llvmTy, *denseAttr);
if (attr.hasTrailingZeros()) {
mlir::Type arrayTy = attr.getType();
result = mlir::LLVM::ZeroOp::create(rewriter, loc,
converter->convertType(arrayTy));
} else {
result = mlir::LLVM::UndefOp::create(rewriter, loc, llvmTy);
}
// Iteratively lower each constant element of the array.
if (auto arrayAttr = mlir::dyn_cast<mlir::ArrayAttr>(attr.getElts())) {
for (auto [idx, elt] : llvm::enumerate(arrayAttr)) {
mlir::Value init = visit(elt);
result =
mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init, idx);
}
} else if (auto strAttr = mlir::dyn_cast<mlir::StringAttr>(attr.getElts())) {
// TODO(cir): this diverges from traditional lowering. Normally the string
// would be a global constant that is memcopied.
auto arrayTy = mlir::dyn_cast<cir::ArrayType>(strAttr.getType());
assert(arrayTy && "String attribute must have an array type");
mlir::Type eltTy = arrayTy.getElementType();
for (auto [idx, elt] : llvm::enumerate(strAttr)) {
auto init = mlir::LLVM::ConstantOp::create(
rewriter, loc, converter->convertType(eltTy), elt);
result =
mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init, idx);
}
} else {
llvm_unreachable("unexpected ConstArrayAttr elements");
}
return result;
}
/// ConstRecord visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::ConstRecordAttr constRecord) {
mlir::Type llvmTy = converter->convertType(constRecord.getType());
mlir::DataLayout dataLayout(parentOp->getParentOfType<mlir::ModuleOp>());
llvmTy = adjustGlobalTypeForInit(llvmTy, constRecord, *converter, dataLayout);
const mlir::Location loc = parentOp->getLoc();
mlir::Value result = mlir::LLVM::UndefOp::create(rewriter, loc, llvmTy);
// Iteratively lower each constant element of the record.
for (auto [idx, elt] : llvm::enumerate(constRecord.getMembers())) {
mlir::Value init = visit(elt);
result =
mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init, idx);
}
return result;
}
/// ConstVectorAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::ConstVectorAttr attr) {
const mlir::Type llvmTy = converter->convertType(attr.getType());
const mlir::Location loc = parentOp->getLoc();
SmallVector<mlir::Attribute> mlirValues;
for (const mlir::Attribute elementAttr : attr.getElts()) {
mlir::Attribute mlirAttr;
if (auto intAttr = mlir::dyn_cast<cir::IntAttr>(elementAttr)) {
mlirAttr = rewriter.getIntegerAttr(
converter->convertType(intAttr.getType()), intAttr.getValue());
} else if (auto floatAttr = mlir::dyn_cast<cir::FPAttr>(elementAttr)) {
mlirAttr = rewriter.getFloatAttr(
converter->convertType(floatAttr.getType()), floatAttr.getValue());
} else {
llvm_unreachable(
"vector constant with an element that is neither an int nor a float");
}
mlirValues.push_back(mlirAttr);
}
return mlir::LLVM::ConstantOp::create(
rewriter, loc, llvmTy,
mlir::DenseElementsAttr::get(mlir::cast<mlir::ShapedType>(llvmTy),
mlirValues));
}
// GlobalViewAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::GlobalViewAttr globalAttr) {
auto moduleOp = parentOp->getParentOfType<mlir::ModuleOp>();
mlir::DataLayout dataLayout(moduleOp);
mlir::Type sourceType;
unsigned sourceAddrSpace = 0;
llvm::StringRef symName;
mlir::Operation *sourceSymbol =
symbolTables.lookupSymbolIn(moduleOp, globalAttr.getSymbol());
if (auto llvmSymbol = dyn_cast<mlir::LLVM::GlobalOp>(sourceSymbol)) {
sourceType = llvmSymbol.getType();
symName = llvmSymbol.getSymName();
sourceAddrSpace = llvmSymbol.getAddrSpace();
} else if (auto cirSymbol = dyn_cast<cir::GlobalOp>(sourceSymbol)) {
sourceType =
convertTypeForMemory(*converter, dataLayout, cirSymbol.getSymType());
symName = cirSymbol.getSymName();
if (auto targetAS = mlir::dyn_cast_if_present<cir::TargetAddressSpaceAttr>(
cirSymbol.getAddrSpaceAttr()))
sourceAddrSpace = targetAS.getValue();
} else if (auto llvmFun = dyn_cast<mlir::LLVM::LLVMFuncOp>(sourceSymbol)) {
sourceType = llvmFun.getFunctionType();
symName = llvmFun.getSymName();
} else if (auto fun = dyn_cast<cir::FuncOp>(sourceSymbol)) {
sourceType = converter->convertType(fun.getFunctionType());
symName = fun.getSymName();
} else if (auto alias = dyn_cast<mlir::LLVM::AliasOp>(sourceSymbol)) {
sourceType = alias.getType();
symName = alias.getSymName();
} else {
llvm_unreachable("Unexpected GlobalOp type");
}
mlir::Location loc = parentOp->getLoc();
mlir::Value addrOp = mlir::LLVM::AddressOfOp::create(
rewriter, loc,
mlir::LLVM::LLVMPointerType::get(rewriter.getContext(), sourceAddrSpace),
symName);
if (globalAttr.getIndices()) {
llvm::SmallVector<mlir::LLVM::GEPArg> indices;
if (mlir::isa<mlir::LLVM::LLVMArrayType, mlir::LLVM::LLVMStructType>(
sourceType))
indices.push_back(0);
for (mlir::Attribute idx : globalAttr.getIndices()) {
auto intAttr = mlir::cast<mlir::IntegerAttr>(idx);
indices.push_back(intAttr.getValue().getSExtValue());
}
mlir::Type resTy = addrOp.getType();
mlir::Type eltTy = converter->convertType(sourceType);
addrOp =
mlir::LLVM::GEPOp::create(rewriter, loc, resTy, eltTy, addrOp, indices,
mlir::LLVM::GEPNoWrapFlags::none);
}
// We can have a global view with an integer type in the case of method
// pointers. With the Itanium ABI, the #cir.method attribute is lowered to a
// #cir.global_view with a pointer-sized integer representing the address of
// the method.
if (auto intTy = mlir::dyn_cast<cir::IntType>(globalAttr.getType())) {
mlir::Type llvmDstTy = converter->convertType(globalAttr.getType());
return mlir::LLVM::PtrToIntOp::create(rewriter, parentOp->getLoc(),
llvmDstTy, addrOp);
}
if (auto ptrTy = mlir::dyn_cast<cir::PointerType>(globalAttr.getType())) {
auto llvmDstTy = converter->convertType<mlir::LLVM::LLVMPointerType>(ptrTy);
unsigned dstAddrSpace = llvmDstTy.getAddressSpace();
if (sourceAddrSpace != dstAddrSpace)
addrOp = mlir::LLVM::AddrSpaceCastOp::create(rewriter, parentOp->getLoc(),
llvmDstTy, addrOp);
mlir::Type llvmEltTy =
convertTypeForMemory(*converter, dataLayout, ptrTy.getPointee());
// No further cast needed if the pointee type already matches.
if (llvmEltTy == sourceType)
return addrOp;
// With opaque pointers, the pointer type is already correct (either from
// the original AddressOfOp or after an addrspacecast) — skip the
// redundant bitcast.
if (addrOp.getType() == llvmDstTy)
return addrOp;
return mlir::LLVM::BitcastOp::create(rewriter, parentOp->getLoc(),
llvmDstTy, addrOp);
}
if (mlir::isa<cir::VPtrType>(globalAttr.getType()))
return addrOp;
llvm_unreachable("Expecting pointer or integer type for GlobalViewAttr");
}
// TypeInfoAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::TypeInfoAttr typeInfoAttr) {
mlir::Type llvmTy = converter->convertType(typeInfoAttr.getType());
mlir::Location loc = parentOp->getLoc();
mlir::Value result = mlir::LLVM::UndefOp::create(rewriter, loc, llvmTy);
for (auto [idx, elt] : llvm::enumerate(typeInfoAttr.getData())) {
mlir::Value init = visit(elt);
result =
mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init, idx);
}
return result;
}
/// UndefAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::UndefAttr undefAttr) {
mlir::Location loc = parentOp->getLoc();
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
return mlir::LLVM::UndefOp::create(
rewriter, loc,
convertTypeForMemory(*converter, layout, undefAttr.getType()));
}
/// PoisonAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::PoisonAttr poisonAttr) {
mlir::Location loc = parentOp->getLoc();
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
return mlir::LLVM::PoisonOp::create(
rewriter, loc,
convertTypeForMemory(*converter, layout, poisonAttr.getType()));
}
// VTableAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::VTableAttr vtableArr) {
mlir::Type llvmTy = converter->convertType(vtableArr.getType());
mlir::Location loc = parentOp->getLoc();
mlir::Value result = mlir::LLVM::UndefOp::create(rewriter, loc, llvmTy);
for (auto [idx, elt] : llvm::enumerate(vtableArr.getData())) {
mlir::Value init = visit(elt);
result =
mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init, idx);
}
return result;
}
/// ZeroAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::ZeroAttr attr) {
mlir::Location loc = parentOp->getLoc();
mlir::DataLayout layout(parentOp->getParentOfType<mlir::ModuleOp>());
return mlir::LLVM::ZeroOp::create(
rewriter, loc, convertTypeForMemory(*converter, layout, attr.getType()));
}
// This class handles rewriting initializer attributes for types that do not
// require region initialization.
class GlobalInitAttrRewriter {
public:
GlobalInitAttrRewriter(mlir::Type type,
mlir::ConversionPatternRewriter &rewriter)
: llvmType(type), rewriter(rewriter) {}
mlir::Attribute visit(mlir::Attribute attr) {
return llvm::TypeSwitch<mlir::Attribute, mlir::Attribute>(attr)
.Case<cir::IntAttr, cir::FPAttr, cir::BoolAttr>(
[&](auto attrT) { return visitCirAttr(attrT); })
.Default([&](auto attrT) { return mlir::Attribute(); });
}
mlir::Attribute visitCirAttr(cir::IntAttr attr) {
// A _BitInt(N) global stores its value in a padded integer iM; sign/zero-
// extend the APInt to that width (a no-op for plain integers, whose value
// width already matches llvmType) so the IntegerAttr is well-typed.
llvm::APInt val = attr.getValue();
auto destTy = mlir::cast<mlir::IntegerType>(llvmType);
if (val.getBitWidth() != destTy.getWidth()) {
auto cirIntTy = mlir::cast<cir::IntType>(attr.getType());
val = cirIntTy.isSigned() ? val.sext(destTy.getWidth())
: val.zext(destTy.getWidth());
}
return rewriter.getIntegerAttr(llvmType, val);
}
mlir::Attribute visitCirAttr(cir::FPAttr attr) {
return rewriter.getFloatAttr(llvmType, attr.getValue());
}
mlir::Attribute visitCirAttr(cir::BoolAttr attr) {
return rewriter.getBoolAttr(attr.getValue());
}
private:
mlir::Type llvmType;
mlir::ConversionPatternRewriter &rewriter;
};
// This pass requires the CIR to be in a "flat" state. All blocks in each
// function must belong to the parent region. Once scopes and control flow
// are implemented in CIR, a pass will be run before this one to flatten
// the CIR and get it into the state that this pass requires.
struct ConvertCIRToLLVMPass
: public mlir::PassWrapper<ConvertCIRToLLVMPass,
mlir::OperationPass<mlir::ModuleOp>> {
void getDependentDialects(mlir::DialectRegistry &registry) const override {
registry.insert<mlir::BuiltinDialect, mlir::DLTIDialect,
mlir::LLVM::LLVMDialect, mlir::func::FuncDialect>();
}
void runOnOperation() final;
void processCIRAttrs(mlir::ModuleOp module);
void resolveBlockAddressOp(LLVMBlockAddressInfo &blockInfoAddr);
/// Collect (symbol_name, annotations, loc) from cir.func and cir.global ops
/// before the conversion runs (the annotations attribute is dropped during
/// FuncOp/GlobalOp lowering).
void collectGlobalAnnotations(mlir::ModuleOp module);
/// Emit @llvm.global.annotations and supporting string/args constants from
/// the previously-collected annotations. Mirrors what OGCG produces.
void buildGlobalAnnotationsVar(mlir::ModuleOp module);
StringRef getDescription() const override {
return "Convert the prepared CIR dialect module to LLVM dialect";
}
StringRef getArgument() const override { return "cir-flat-to-llvm"; }
private:
/// One annotation entry collected pre-conversion.
struct CollectedAnnotation {
mlir::StringAttr symName;
cir::AnnotationAttr annotation;
mlir::Location loc;
CollectedAnnotation(mlir::StringAttr symName,
cir::AnnotationAttr annotation, mlir::Location loc)
: symName(symName), annotation(annotation), loc(loc) {}
};
llvm::SmallVector<CollectedAnnotation> collectedAnnotations;
};
mlir::LogicalResult CIRToLLVMIsFPClassOpLowering::matchAndRewrite(
cir::IsFPClassOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value src = adaptor.getSrc();
cir::FPClassTest flags = adaptor.getFlags();
mlir::IntegerType retTy = rewriter.getI1Type();
rewriter.replaceOpWithNewOp<mlir::LLVM::IsFPClass>(
op, retTy, src, static_cast<uint32_t>(flags));
return mlir::success();
}
mlir::LogicalResult CIRToLLVMSignBitOpLowering::matchAndRewrite(
cir::SignBitOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
assert(!cir::MissingFeatures::isPPC_FP128Ty());
mlir::DataLayout layout(op->getParentOfType<mlir::ModuleOp>());
int width = layout.getTypeSizeInBits(op.getInput().getType());
if (auto longDoubleType =
mlir::dyn_cast<cir::LongDoubleType>(op.getInput().getType())) {
if (mlir::isa<cir::FP80Type>(longDoubleType.getUnderlying())) {
// If the underlying type of LongDouble is FP80Type,
// DataLayout::getTypeSizeInBits returns 128.
// See https://github.com/llvm/clangir/issues/1057.
// Set the width to 80 manually.
width = 80;
}
}
mlir::Type intTy = mlir::IntegerType::get(rewriter.getContext(), width);
auto bitcast = mlir::LLVM::BitcastOp::create(rewriter, op->getLoc(), intTy,
adaptor.getInput());
auto zero = mlir::LLVM::ConstantOp::create(rewriter, op->getLoc(), intTy, 0);
auto cmpResult = mlir::LLVM::ICmpOp::create(rewriter, op.getLoc(),
mlir::LLVM::ICmpPredicate::slt,
bitcast.getResult(), zero);
rewriter.replaceOp(op, cmpResult);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAssumeOpLowering::matchAndRewrite(
cir::AssumeOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value cond = adaptor.getPredicate();
if (op.getBundleKind() == cir::AssumeBundleKind::None) {
rewriter.replaceOpWithNewOp<mlir::LLVM::AssumeOp>(op, cond);
return mlir::success();
}
llvm::StringRef tag = cir::stringifyAssumeBundleKind(op.getBundleKind());
rewriter.replaceOpWithNewOp<mlir::LLVM::AssumeOp>(op, cond, tag,
adaptor.getBundleArgs());
return mlir::success();
}
static mlir::LLVM::AtomicOrdering
getLLVMMemOrder(std::optional<cir::MemOrder> memorder) {
if (!memorder)
return mlir::LLVM::AtomicOrdering::not_atomic;
switch (*memorder) {
case cir::MemOrder::Relaxed:
return mlir::LLVM::AtomicOrdering::monotonic;
case cir::MemOrder::Consume:
case cir::MemOrder::Acquire:
return mlir::LLVM::AtomicOrdering::acquire;
case cir::MemOrder::Release:
return mlir::LLVM::AtomicOrdering::release;
case cir::MemOrder::AcquireRelease:
return mlir::LLVM::AtomicOrdering::acq_rel;
case cir::MemOrder::SequentiallyConsistent:
return mlir::LLVM::AtomicOrdering::seq_cst;
}
llvm_unreachable("unknown memory order");
}
static llvm::StringRef getLLVMSyncScope(cir::SyncScopeKind syncScope) {
switch (syncScope) {
case cir::SyncScopeKind::SingleThread:
return "singlethread";
case cir::SyncScopeKind::Workgroup:
return "block";
default:
return "";
}
}
static std::optional<llvm::StringRef>
getLLVMSyncScope(std::optional<cir::SyncScopeKind> syncScope) {
if (syncScope.has_value())
return getLLVMSyncScope(*syncScope);
return std::nullopt;
}
mlir::LogicalResult CIRToLLVMAtomicCmpXchgOpLowering::matchAndRewrite(
cir::AtomicCmpXchgOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value expected = adaptor.getExpected();
mlir::Value desired = adaptor.getDesired();
auto cmpxchg = mlir::LLVM::AtomicCmpXchgOp::create(
rewriter, op.getLoc(), adaptor.getPtr(), expected, desired,
getLLVMMemOrder(adaptor.getSuccOrder()),
getLLVMMemOrder(adaptor.getFailOrder()),
getLLVMSyncScope(op.getSyncScope()));
cmpxchg.setAlignment(adaptor.getAlignment());
cmpxchg.setWeak(adaptor.getWeak());
cmpxchg.setVolatile_(adaptor.getIsVolatile());
// Check result and apply stores accordingly.
auto old = mlir::LLVM::ExtractValueOp::create(rewriter, op.getLoc(),
cmpxchg.getResult(), 0);
auto cmp = mlir::LLVM::ExtractValueOp::create(rewriter, op.getLoc(),
cmpxchg.getResult(), 1);
rewriter.replaceOp(op, {old, cmp});
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAtomicXchgOpLowering::matchAndRewrite(
cir::AtomicXchgOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
assert(!cir::MissingFeatures::atomicSyncScopeID());
mlir::LLVM::AtomicOrdering llvmOrder = getLLVMMemOrder(adaptor.getMemOrder());
llvm::StringRef llvmSyncScope = getLLVMSyncScope(adaptor.getSyncScope());
rewriter.replaceOpWithNewOp<mlir::LLVM::AtomicRMWOp>(
op, mlir::LLVM::AtomicBinOp::xchg, adaptor.getPtr(), adaptor.getVal(),
llvmOrder, llvmSyncScope);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAtomicTestAndSetOpLowering::matchAndRewrite(
cir::AtomicTestAndSetOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
assert(!cir::MissingFeatures::atomicSyncScopeID());
mlir::LLVM::AtomicOrdering llvmOrder = getLLVMMemOrder(op.getMemOrder());
auto one = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(),
rewriter.getI8Type(), 1);
auto rmw = mlir::LLVM::AtomicRMWOp::create(
rewriter, op.getLoc(), mlir::LLVM::AtomicBinOp::xchg, adaptor.getPtr(),
one, llvmOrder, /*syncscope=*/llvm::StringRef(),
adaptor.getAlignment().value_or(0), op.getIsVolatile());
auto zero = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(),
rewriter.getI8Type(), 0);
auto cmp = mlir::LLVM::ICmpOp::create(
rewriter, op.getLoc(), mlir::LLVM::ICmpPredicate::ne, rmw, zero);
rewriter.replaceOp(op, cmp);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAtomicClearOpLowering::matchAndRewrite(
cir::AtomicClearOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
assert(!cir::MissingFeatures::atomicSyncScopeID());
mlir::LLVM::AtomicOrdering llvmOrder = getLLVMMemOrder(op.getMemOrder());
auto zero = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(),
rewriter.getI8Type(), 0);
auto store = mlir::LLVM::StoreOp::create(
rewriter, op.getLoc(), zero, adaptor.getPtr(),
adaptor.getAlignment().value_or(0), op.getIsVolatile(),
/*isNonTemporal=*/false, /*isInvariantGroup=*/false, llvmOrder);
rewriter.replaceOp(op, store);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAtomicFenceOpLowering::matchAndRewrite(
cir::AtomicFenceOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::LLVM::AtomicOrdering llvmOrder = getLLVMMemOrder(adaptor.getOrdering());
auto fence = mlir::LLVM::FenceOp::create(rewriter, op.getLoc(), llvmOrder);
fence.setSyncscope(getLLVMSyncScope(adaptor.getSyncscope()));
rewriter.replaceOp(op, fence);
return mlir::success();
}
static mlir::LLVM::AtomicBinOp
getLLVMAtomicBinOp(cir::AtomicFetchKind k, bool isInt, bool isSignedInt) {
switch (k) {
case cir::AtomicFetchKind::Add:
return isInt ? mlir::LLVM::AtomicBinOp::add : mlir::LLVM::AtomicBinOp::fadd;
case cir::AtomicFetchKind::Sub:
return isInt ? mlir::LLVM::AtomicBinOp::sub : mlir::LLVM::AtomicBinOp::fsub;
case cir::AtomicFetchKind::And:
return mlir::LLVM::AtomicBinOp::_and;
case cir::AtomicFetchKind::Xor:
return mlir::LLVM::AtomicBinOp::_xor;
case cir::AtomicFetchKind::Or:
return mlir::LLVM::AtomicBinOp::_or;
case cir::AtomicFetchKind::Nand:
return mlir::LLVM::AtomicBinOp::nand;
case cir::AtomicFetchKind::Max: {
if (!isInt)
return mlir::LLVM::AtomicBinOp::fmax;
return isSignedInt ? mlir::LLVM::AtomicBinOp::max
: mlir::LLVM::AtomicBinOp::umax;
}
case cir::AtomicFetchKind::Min: {
if (!isInt)
return mlir::LLVM::AtomicBinOp::fmin;
return isSignedInt ? mlir::LLVM::AtomicBinOp::min
: mlir::LLVM::AtomicBinOp::umin;
}
case cir::AtomicFetchKind::UIncWrap:
return mlir::LLVM::AtomicBinOp::uinc_wrap;
case cir::AtomicFetchKind::UDecWrap:
return mlir::LLVM::AtomicBinOp::udec_wrap;
case cir::AtomicFetchKind::Maximum:
return mlir::LLVM::AtomicBinOp::fmaximum;
case cir::AtomicFetchKind::Minimum:
return mlir::LLVM::AtomicBinOp::fminimum;
case cir::AtomicFetchKind::MaximumNum:
return mlir::LLVM::AtomicBinOp::fmaximumnum;
case cir::AtomicFetchKind::MinimumNum:
return mlir::LLVM::AtomicBinOp::fminimumnum;
}
llvm_unreachable("Unknown atomic fetch opcode");
}
static llvm::StringLiteral getLLVMBinopForPostAtomic(cir::AtomicFetchKind k,
bool isInt) {
switch (k) {
case cir::AtomicFetchKind::Add:
return isInt ? mlir::LLVM::AddOp::getOperationName()
: mlir::LLVM::FAddOp::getOperationName();
case cir::AtomicFetchKind::Sub:
return isInt ? mlir::LLVM::SubOp::getOperationName()
: mlir::LLVM::FSubOp::getOperationName();
case cir::AtomicFetchKind::And:
return mlir::LLVM::AndOp::getOperationName();
case cir::AtomicFetchKind::Xor:
return mlir::LLVM::XOrOp::getOperationName();
case cir::AtomicFetchKind::Or:
return mlir::LLVM::OrOp::getOperationName();
case cir::AtomicFetchKind::Nand:
// There's no nand binop in LLVM, this is later fixed with a not.
return mlir::LLVM::AndOp::getOperationName();
case cir::AtomicFetchKind::Max:
case cir::AtomicFetchKind::Min:
llvm_unreachable("handled in buildMinMaxPostOp");
case cir::AtomicFetchKind::UIncWrap:
case cir::AtomicFetchKind::UDecWrap:
case cir::AtomicFetchKind::Maximum:
case cir::AtomicFetchKind::Minimum:
case cir::AtomicFetchKind::MaximumNum:
case cir::AtomicFetchKind::MinimumNum:
llvm_unreachable("uinc_wrap, udec_wrap, maximum, minimum, maximum_num, and "
"minimum_num are always fetch_first");
}
llvm_unreachable("Unknown atomic fetch opcode");
}
mlir::Value CIRToLLVMAtomicFetchOpLowering::buildPostOp(
cir::AtomicFetchOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter, mlir::Value rmwVal,
bool isInt) const {
SmallVector<mlir::Value> atomicOperands = {rmwVal, adaptor.getVal()};
SmallVector<mlir::Type> atomicResTys = {rmwVal.getType()};
return rewriter
.create(op.getLoc(),
rewriter.getStringAttr(
getLLVMBinopForPostAtomic(op.getBinop(), isInt)),
atomicOperands, atomicResTys, {})
->getResult(0);
}
mlir::Value CIRToLLVMAtomicFetchOpLowering::buildMinMaxPostOp(
cir::AtomicFetchOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter, mlir::Value rmwVal, bool isInt,
bool isSigned) const {
mlir::Location loc = op.getLoc();
if (!isInt) {
if (op.getBinop() == cir::AtomicFetchKind::Max)
return mlir::LLVM::MaxNumOp::create(rewriter, loc, rmwVal,
adaptor.getVal());
return mlir::LLVM::MinNumOp::create(rewriter, loc, rmwVal,
adaptor.getVal());
}
mlir::LLVM::ICmpPredicate pred;
if (op.getBinop() == cir::AtomicFetchKind::Max) {
pred = isSigned ? mlir::LLVM::ICmpPredicate::sgt
: mlir::LLVM::ICmpPredicate::ugt;
} else { // Min
pred = isSigned ? mlir::LLVM::ICmpPredicate::slt
: mlir::LLVM::ICmpPredicate::ult;
}
mlir::Value cmp = mlir::LLVM::ICmpOp::create(
rewriter, loc,
mlir::LLVM::ICmpPredicateAttr::get(rewriter.getContext(), pred), rmwVal,
adaptor.getVal());
return mlir::LLVM::SelectOp::create(rewriter, loc, cmp, rmwVal,
adaptor.getVal());
}
mlir::LogicalResult CIRToLLVMAtomicFetchOpLowering::matchAndRewrite(
cir::AtomicFetchOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
bool isInt = false;
bool isSignedInt = false;
if (auto intTy = mlir::dyn_cast<cir::IntType>(op.getVal().getType())) {
isInt = true;
isSignedInt = intTy.isSigned();
} else if (mlir::isa<cir::SingleType, cir::DoubleType>(
op.getVal().getType())) {
isInt = false;
} else {
return op.emitError() << "Unsupported type: " << op.getVal().getType();
}
mlir::LLVM::AtomicOrdering llvmOrder = getLLVMMemOrder(op.getMemOrder());
llvm::StringRef llvmSyncScope = getLLVMSyncScope(op.getSyncScope());
mlir::LLVM::AtomicBinOp llvmBinOp =
getLLVMAtomicBinOp(op.getBinop(), isInt, isSignedInt);
auto rmwVal = mlir::LLVM::AtomicRMWOp::create(
rewriter, op.getLoc(), llvmBinOp, adaptor.getPtr(), adaptor.getVal(),
llvmOrder, llvmSyncScope);
mlir::Value result = rmwVal.getResult();
if (!op.getFetchFirst()) {
if (op.getBinop() == cir::AtomicFetchKind::Max ||
op.getBinop() == cir::AtomicFetchKind::Min)
result = buildMinMaxPostOp(op, adaptor, rewriter, rmwVal.getRes(), isInt,
isSignedInt);
else
result = buildPostOp(op, adaptor, rewriter, rmwVal.getRes(), isInt);
// Compensate lack of nand binop in LLVM IR.
if (op.getBinop() == cir::AtomicFetchKind::Nand) {
auto negOne = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(),
result.getType(), -1);
result = mlir::LLVM::XOrOp::create(rewriter, op.getLoc(), result, negOne);
}
}
rewriter.replaceOp(op, result);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMBitClrsbOpLowering::matchAndRewrite(
cir::BitClrsbOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto zero = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(),
adaptor.getInput().getType(), 0);
auto isNeg = mlir::LLVM::ICmpOp::create(
rewriter, op.getLoc(),
mlir::LLVM::ICmpPredicateAttr::get(rewriter.getContext(),
mlir::LLVM::ICmpPredicate::slt),
adaptor.getInput(), zero);
auto negOne = mlir::LLVM::ConstantOp::create(
rewriter, op.getLoc(), adaptor.getInput().getType(), -1);
auto flipped = mlir::LLVM::XOrOp::create(rewriter, op.getLoc(),
adaptor.getInput(), negOne);
auto select = mlir::LLVM::SelectOp::create(rewriter, op.getLoc(), isNeg,
flipped, adaptor.getInput());
auto resTy = getTypeConverter()->convertType(op.getType());
auto clz = mlir::LLVM::CountLeadingZerosOp::create(
rewriter, op.getLoc(), resTy, select, /*is_zero_poison=*/false);
auto one = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(), resTy, 1);
auto res = mlir::LLVM::SubOp::create(rewriter, op.getLoc(), clz, one,
mlir::LLVM::IntegerOverflowFlags::nuw);
rewriter.replaceOp(op, res);
return mlir::LogicalResult::success();
}
mlir::LogicalResult CIRToLLVMBitClzOpLowering::matchAndRewrite(
cir::BitClzOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto resTy = getTypeConverter()->convertType(op.getType());
auto llvmOp = mlir::LLVM::CountLeadingZerosOp::create(
rewriter, op.getLoc(), resTy, adaptor.getInput(), op.getPoisonZero());
rewriter.replaceOp(op, llvmOp);
return mlir::LogicalResult::success();
}
mlir::LogicalResult CIRToLLVMBitCtzOpLowering::matchAndRewrite(
cir::BitCtzOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto resTy = getTypeConverter()->convertType(op.getType());
auto llvmOp = mlir::LLVM::CountTrailingZerosOp::create(
rewriter, op.getLoc(), resTy, adaptor.getInput(), op.getPoisonZero());
rewriter.replaceOp(op, llvmOp);
return mlir::LogicalResult::success();
}
mlir::LogicalResult CIRToLLVMBitFfsOpLowering::matchAndRewrite(
cir::BitFfsOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto resTy = getTypeConverter()->convertType(op.getType());
auto ctz = mlir::LLVM::CountTrailingZerosOp::create(rewriter, op.getLoc(),
resTy, adaptor.getInput(),
/*is_zero_poison=*/true);
auto one = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(), resTy, 1);
auto ctzAddOne = mlir::LLVM::AddOp::create(rewriter, op.getLoc(), ctz, one);
auto zeroInputTy = mlir::LLVM::ConstantOp::create(
rewriter, op.getLoc(), adaptor.getInput().getType(), 0);
auto isZero = mlir::LLVM::ICmpOp::create(
rewriter, op.getLoc(),
mlir::LLVM::ICmpPredicateAttr::get(rewriter.getContext(),
mlir::LLVM::ICmpPredicate::eq),
adaptor.getInput(), zeroInputTy);
auto zero = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(), resTy, 0);
auto res = mlir::LLVM::SelectOp::create(rewriter, op.getLoc(), isZero, zero,
ctzAddOne);
rewriter.replaceOp(op, res);
return mlir::LogicalResult::success();
}
mlir::LogicalResult CIRToLLVMBitParityOpLowering::matchAndRewrite(
cir::BitParityOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto resTy = getTypeConverter()->convertType(op.getType());
auto popcnt = mlir::LLVM::CtPopOp::create(rewriter, op.getLoc(), resTy,
adaptor.getInput());
auto one = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(), resTy, 1);
auto popcntMod2 =
mlir::LLVM::AndOp::create(rewriter, op.getLoc(), popcnt, one);
rewriter.replaceOp(op, popcntMod2);
return mlir::LogicalResult::success();
}
mlir::LogicalResult CIRToLLVMBitPopcountOpLowering::matchAndRewrite(
cir::BitPopcountOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto resTy = getTypeConverter()->convertType(op.getType());
auto llvmOp = mlir::LLVM::CtPopOp::create(rewriter, op.getLoc(), resTy,
adaptor.getInput());
rewriter.replaceOp(op, llvmOp);
return mlir::LogicalResult::success();
}
mlir::LogicalResult CIRToLLVMBrCondOpLowering::matchAndRewrite(
cir::BrCondOp brOp, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// When ZExtOp is implemented, we'll need to check if the condition is a
// ZExtOp and if so, delete it if it has a single use.
assert(!cir::MissingFeatures::zextOp());
mlir::Value i1Condition = adaptor.getCond();
rewriter.replaceOpWithNewOp<mlir::LLVM::CondBrOp>(
brOp, i1Condition, brOp.getDestTrue(), adaptor.getDestOperandsTrue(),
brOp.getDestFalse(), adaptor.getDestOperandsFalse());
return mlir::success();
}
mlir::Type CIRToLLVMCastOpLowering::convertTy(mlir::Type ty) const {
return getTypeConverter()->convertType(ty);
}
mlir::LogicalResult CIRToLLVMCastOpLowering::matchAndRewrite(
cir::CastOp castOp, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// For arithmetic conversions, LLVM IR uses the same instruction to convert
// both individual scalars and entire vectors. This lowering pass handles
// both situations.
switch (castOp.getKind()) {
case cir::CastKind::array_to_ptrdecay: {
const auto ptrTy = mlir::cast<cir::PointerType>(castOp.getType());
mlir::Value sourceValue = adaptor.getSrc();
mlir::Type targetType = convertTy(ptrTy);
mlir::Type elementTy = convertTypeForMemory(*getTypeConverter(), dataLayout,
ptrTy.getPointee());
llvm::SmallVector<mlir::LLVM::GEPArg> offset{0};
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
castOp, targetType, elementTy, sourceValue, offset);
break;
}
case cir::CastKind::int_to_bool: {
mlir::Value llvmSrcVal = adaptor.getSrc();
// getZeroAttr yields a splat for vector source types so this also
// handles element-wise int-to-bool conversions (e.g. an ext_vector
// __builtin_convertvector to bool).
mlir::Value zeroInt = mlir::LLVM::ConstantOp::create(
rewriter, castOp.getLoc(), llvmSrcVal.getType(),
rewriter.getZeroAttr(llvmSrcVal.getType()));
rewriter.replaceOpWithNewOp<mlir::LLVM::ICmpOp>(
castOp, mlir::LLVM::ICmpPredicate::ne, llvmSrcVal, zeroInt);
break;
}
case cir::CastKind::integral: {
mlir::Type srcType = castOp.getSrc().getType();
mlir::Type dstType = castOp.getType();
mlir::Value llvmSrcVal = adaptor.getSrc();
mlir::Type llvmDstType = getTypeConverter()->convertType(dstType);
cir::IntType srcIntType =
mlir::cast<cir::IntType>(elementTypeIfVector(srcType));
cir::IntType dstIntType =
mlir::cast<cir::IntType>(elementTypeIfVector(dstType));
rewriter.replaceOp(castOp, getLLVMIntCast(rewriter, llvmSrcVal, llvmDstType,
srcIntType.isUnsigned(),
srcIntType.getWidth(),
dstIntType.getWidth()));
break;
}
case cir::CastKind::floating: {
mlir::Value llvmSrcVal = adaptor.getSrc();
mlir::Type llvmDstTy = getTypeConverter()->convertType(castOp.getType());
mlir::Type srcTy = elementTypeIfVector(castOp.getSrc().getType());
mlir::Type dstTy = elementTypeIfVector(castOp.getType());
if (!mlir::isa<cir::FPTypeInterface>(dstTy) ||
!mlir::isa<cir::FPTypeInterface>(srcTy))
return castOp.emitError() << "NYI cast from " << srcTy << " to " << dstTy;
auto getFloatWidth = [](mlir::Type ty) -> unsigned {
return mlir::cast<cir::FPTypeInterface>(ty).getWidth();
};
bool isTrunc = getFloatWidth(srcTy) > getFloatWidth(dstTy);
if (cir::FenvAttr fenv = castOp.getFenvAttr()) {
// fptrunc takes rounding mode + exception behavior; fpext takes only
// exception behavior.
return lowerToConstrainedFPIntrinsic(
castOp, llvmSrcVal, fenv, llvmDstTy, rewriter,
isTrunc ? "fptrunc" : "fpext", /*hasRoundingMode=*/isTrunc);
}
if (isTrunc)
rewriter.replaceOpWithNewOp<mlir::LLVM::FPTruncOp>(castOp, llvmDstTy,
llvmSrcVal);
else
rewriter.replaceOpWithNewOp<mlir::LLVM::FPExtOp>(castOp, llvmDstTy,
llvmSrcVal);
return mlir::success();
}
case cir::CastKind::int_to_ptr: {
auto dstTy = mlir::cast<cir::PointerType>(castOp.getType());
mlir::Value llvmSrcVal = adaptor.getSrc();
mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy);
rewriter.replaceOpWithNewOp<mlir::LLVM::IntToPtrOp>(castOp, llvmDstTy,
llvmSrcVal);
return mlir::success();
}
case cir::CastKind::ptr_to_int: {
auto dstTy = mlir::cast<cir::IntType>(castOp.getType());
mlir::Value llvmSrcVal = adaptor.getSrc();
mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy);
rewriter.replaceOpWithNewOp<mlir::LLVM::PtrToIntOp>(castOp, llvmDstTy,
llvmSrcVal);
return mlir::success();
}
case cir::CastKind::float_to_bool: {
mlir::Value llvmSrcVal = adaptor.getSrc();
auto kind = mlir::LLVM::FCmpPredicate::une;
// Check if float is not equal to zero. getZeroAttr yields a splat
// for vector source types so this also handles element-wise
// float-to-bool conversions.
auto zeroFloat = mlir::LLVM::ConstantOp::create(
rewriter, castOp.getLoc(), llvmSrcVal.getType(),
rewriter.getZeroAttr(llvmSrcVal.getType()));
// Extend comparison result to either bool (C++) or int (C).
rewriter.replaceOpWithNewOp<mlir::LLVM::FCmpOp>(castOp, kind, llvmSrcVal,
zeroFloat);
return mlir::success();
}
case cir::CastKind::bool_to_int: {
mlir::Type dstTy = castOp.getType();
mlir::Value llvmSrcVal = adaptor.getSrc();
mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy);
// Compare element widths so this also handles vector bool -> int casts.
auto srcElemTy = mlir::cast<mlir::IntegerType>(
elementTypeIfVector(llvmSrcVal.getType()));
auto dstElemTy = mlir::cast<cir::IntType>(elementTypeIfVector(dstTy));
if (srcElemTy.getWidth() == dstElemTy.getWidth())
rewriter.replaceOpWithNewOp<mlir::LLVM::BitcastOp>(castOp, llvmDstTy,
llvmSrcVal);
else
rewriter.replaceOpWithNewOp<mlir::LLVM::ZExtOp>(castOp, llvmDstTy,
llvmSrcVal);
return mlir::success();
}
case cir::CastKind::bool_to_float: {
mlir::Type dstTy = castOp.getType();
mlir::Value llvmSrcVal = adaptor.getSrc();
mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy);
rewriter.replaceOpWithNewOp<mlir::LLVM::UIToFPOp>(castOp, llvmDstTy,
llvmSrcVal);
return mlir::success();
}
case cir::CastKind::int_to_float: {
mlir::Type dstTy = castOp.getType();
mlir::Value llvmSrcVal = adaptor.getSrc();
mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy);
bool isSigned =
mlir::cast<cir::IntType>(elementTypeIfVector(castOp.getSrc().getType()))
.isSigned();
if (cir::FenvAttr fenv = castOp.getFenvAttr()) {
return lowerToConstrainedFPIntrinsic(
castOp, llvmSrcVal, fenv, llvmDstTy, rewriter,
isSigned ? "sitofp" : "uitofp", /*hasRoundingMode=*/true);
}
if (isSigned)
rewriter.replaceOpWithNewOp<mlir::LLVM::SIToFPOp>(castOp, llvmDstTy,
llvmSrcVal);
else
rewriter.replaceOpWithNewOp<mlir::LLVM::UIToFPOp>(castOp, llvmDstTy,
llvmSrcVal);
return mlir::success();
}
case cir::CastKind::float_to_int: {
mlir::Type dstTy = castOp.getType();
mlir::Value llvmSrcVal = adaptor.getSrc();
mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy);
bool isSigned =
mlir::cast<cir::IntType>(elementTypeIfVector(castOp.getType()))
.isSigned();
if (cir::FenvAttr fenv = castOp.getFenvAttr()) {
return lowerToConstrainedFPIntrinsic(
castOp, llvmSrcVal, fenv, llvmDstTy, rewriter,
isSigned ? "fptosi" : "fptoui", /*hasRoundingMode=*/false);
}
if (isSigned)
rewriter.replaceOpWithNewOp<mlir::LLVM::FPToSIOp>(castOp, llvmDstTy,
llvmSrcVal);
else
rewriter.replaceOpWithNewOp<mlir::LLVM::FPToUIOp>(castOp, llvmDstTy,
llvmSrcVal);
return mlir::success();
}
case cir::CastKind::bitcast: {
mlir::Type dstTy = castOp.getType();
mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy);
assert(!MissingFeatures::cxxABI());
assert(!MissingFeatures::dataMemberType());
mlir::Value llvmSrcVal = adaptor.getSrc();
rewriter.replaceOpWithNewOp<mlir::LLVM::BitcastOp>(castOp, llvmDstTy,
llvmSrcVal);
return mlir::success();
}
case cir::CastKind::ptr_to_bool: {
mlir::Value llvmSrcVal = adaptor.getSrc();
mlir::Value zeroPtr = mlir::LLVM::ZeroOp::create(rewriter, castOp.getLoc(),
llvmSrcVal.getType());
rewriter.replaceOpWithNewOp<mlir::LLVM::ICmpOp>(
castOp, mlir::LLVM::ICmpPredicate::ne, llvmSrcVal, zeroPtr);
break;
}
case cir::CastKind::address_space: {
mlir::Type dstTy = castOp.getType();
mlir::Value llvmSrcVal = adaptor.getSrc();
mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy);
rewriter.replaceOpWithNewOp<mlir::LLVM::AddrSpaceCastOp>(castOp, llvmDstTy,
llvmSrcVal);
break;
}
case cir::CastKind::member_ptr_to_bool:
assert(!MissingFeatures::cxxABI());
assert(!MissingFeatures::methodType());
break;
default: {
return castOp.emitError("Unhandled cast kind: ")
<< castOp.getKindAttrName();
}
}
return mlir::success();
}
mlir::LogicalResult CIRToLLVMBuiltinIntCastOpLowering::matchAndRewrite(
cir::BuiltinIntCastOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// Both the CIR integer and the builtin integer/index lower to LLVM integer
// types, so this cast becomes an integer resize. Signedness is taken from
// the CIR integer side (the builtin/index side is treated as signless).
bool isUnsigned = true;
if (auto cirSrc = mlir::dyn_cast<cir::IntType>(op.getSrc().getType()))
isUnsigned = cirSrc.isUnsigned();
else if (auto cirDst = mlir::dyn_cast<cir::IntType>(op.getType()))
isUnsigned = cirDst.isUnsigned();
mlir::Value llvmSrc = adaptor.getSrc();
mlir::Type llvmDstTy = getTypeConverter()->convertType(op.getType());
auto srcIntTy = mlir::cast<mlir::IntegerType>(llvmSrc.getType());
auto dstIntTy = mlir::cast<mlir::IntegerType>(llvmDstTy);
unsigned srcWidth = srcIntTy.getWidth();
unsigned dstWidth = dstIntTy.getWidth();
// Fixed-width builtin integers must match the CIR integer width.
// If the converted LLVM widths differ, the non-CIR side must have been
// 'index' type (target dependent width).
assert((srcWidth == dstWidth ||
mlir::isa<mlir::IndexType>(op.getSrc().getType()) ||
mlir::isa<mlir::IndexType>(op.getType())) &&
"only index casts may change width during lowering");
// For equal widths getLLVMIntCast returns the source unchanged, so casts
// between CIR integers and fixed-width builtin integers lower to a no-op.
rewriter.replaceOp(op, getLLVMIntCast(rewriter, llvmSrc, dstIntTy, isUnsigned,
srcWidth, dstWidth));
return mlir::success();
}
static mlir::Value convertToIndexTy(mlir::ConversionPatternRewriter &rewriter,
mlir::ModuleOp mod, mlir::Value index,
mlir::Type baseTy, cir::IntType strideTy) {
mlir::Operation *indexOp = index.getDefiningOp();
if (!indexOp)
return index;
auto indexType = mlir::cast<mlir::IntegerType>(index.getType());
mlir::DataLayout llvmLayout(mod);
std::optional<uint64_t> layoutWidth = llvmLayout.getTypeIndexBitwidth(baseTy);
// If there is no change in width, don't do anything.
if (!layoutWidth || *layoutWidth == indexType.getWidth())
return index;
// If the index comes from a subtraction, make sure the extension happens
// before it. To achieve that, look at unary minus, which already got
// lowered to "sub 0, x".
auto sub = dyn_cast<mlir::LLVM::SubOp>(indexOp);
bool rewriteSub = false;
if (sub) {
if (auto lhsConst =
dyn_cast<mlir::LLVM::ConstantOp>(sub.getLhs().getDefiningOp())) {
auto lhsConstInt = mlir::dyn_cast<mlir::IntegerAttr>(lhsConst.getValue());
if (lhsConstInt && lhsConstInt.getValue() == 0) {
index = sub.getRhs();
rewriteSub = true;
}
}
}
auto llvmDstType = rewriter.getIntegerType(*layoutWidth);
bool isUnsigned = strideTy && strideTy.isUnsigned();
index = getLLVMIntCast(rewriter, index, llvmDstType, isUnsigned,
indexType.getWidth(), *layoutWidth);
if (rewriteSub) {
index = mlir::LLVM::SubOp::create(
rewriter, index.getLoc(),
mlir::LLVM::ConstantOp::create(rewriter, index.getLoc(),
index.getType(), 0),
index);
// TODO: ensure sub is trivially dead now.
rewriter.eraseOp(sub);
}
return index;
}
mlir::LogicalResult CIRToLLVMPtrStrideOpLowering::matchAndRewrite(
cir::PtrStrideOp ptrStrideOp, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::TypeConverter *tc = getTypeConverter();
const mlir::Type resultTy = tc->convertType(ptrStrideOp.getType());
mlir::Type elementTy =
convertTypeForMemory(*tc, dataLayout, ptrStrideOp.getElementType());
// void and function types doesn't really have a layout to use in GEPs,
// make it i8 instead.
if (mlir::isa<mlir::LLVM::LLVMVoidType>(elementTy) ||
mlir::isa<mlir::LLVM::LLVMFunctionType>(elementTy))
elementTy = mlir::IntegerType::get(elementTy.getContext(), 8,
mlir::IntegerType::Signless);
// Zero-extend, sign-extend or trunc the pointer value.
mlir::Value index = adaptor.getStride();
index = convertToIndexTy(
rewriter, ptrStrideOp->getParentOfType<mlir::ModuleOp>(), index,
adaptor.getBase().getType(),
dyn_cast<cir::IntType>(ptrStrideOp.getOperand(1).getType()));
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
ptrStrideOp, resultTy, elementTy, adaptor.getBase(), index);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMGetElementOpLowering::matchAndRewrite(
cir::GetElementOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
if (auto arrayTy =
mlir::dyn_cast<cir::ArrayType>(op.getBaseType().getPointee())) {
const mlir::TypeConverter *converter = getTypeConverter();
const mlir::Type llArrayTy = converter->convertType(arrayTy);
const mlir::Type llResultTy = converter->convertType(op.getType());
mlir::Type elementTy =
convertTypeForMemory(*converter, dataLayout, op.getElementType());
// void and function types don't really have a layout to use in GEPs,
// make it i8 instead.
if (mlir::isa<mlir::LLVM::LLVMVoidType>(elementTy) ||
mlir::isa<mlir::LLVM::LLVMFunctionType>(elementTy))
elementTy = rewriter.getIntegerType(8);
mlir::Value index = adaptor.getIndex();
index =
convertToIndexTy(rewriter, op->getParentOfType<mlir::ModuleOp>(), index,
adaptor.getBase().getType(),
dyn_cast<cir::IntType>(op.getOperand(1).getType()));
// Since the base address is a pointer to an aggregate, the first
// offset is always zero. The second offset tell us which member it
// will access.
std::array<mlir::LLVM::GEPArg, 2> offset{0, index};
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(op, llResultTy, llArrayTy,
adaptor.getBase(), offset);
return mlir::success();
}
op.emitError() << "NYI: GetElementOp lowering to LLVM for non-array";
return mlir::failure();
}
mlir::LogicalResult CIRToLLVMBaseClassAddrOpLowering::matchAndRewrite(
cir::BaseClassAddrOp baseClassOp, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::Type resultType =
getTypeConverter()->convertType(baseClassOp.getType());
mlir::Value derivedAddr = adaptor.getDerivedAddr();
llvm::SmallVector<mlir::LLVM::GEPArg, 1> offset = {
adaptor.getOffset().getZExtValue()};
mlir::Type byteType = mlir::IntegerType::get(resultType.getContext(), 8,
mlir::IntegerType::Signless);
if (adaptor.getOffset().getZExtValue() == 0) {
rewriter.replaceOpWithNewOp<mlir::LLVM::BitcastOp>(
baseClassOp, resultType, adaptor.getDerivedAddr());
return mlir::success();
}
if (baseClassOp.getAssumeNotNull()) {
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
baseClassOp, resultType, byteType, derivedAddr, offset);
} else {
auto loc = baseClassOp.getLoc();
mlir::Value isNull = mlir::LLVM::ICmpOp::create(
rewriter, loc, mlir::LLVM::ICmpPredicate::eq, derivedAddr,
mlir::LLVM::ZeroOp::create(rewriter, loc, derivedAddr.getType()));
mlir::Value adjusted = mlir::LLVM::GEPOp::create(
rewriter, loc, resultType, byteType, derivedAddr, offset);
rewriter.replaceOpWithNewOp<mlir::LLVM::SelectOp>(baseClassOp, isNull,
derivedAddr, adjusted);
}
return mlir::success();
}
mlir::LogicalResult CIRToLLVMDerivedClassAddrOpLowering::matchAndRewrite(
cir::DerivedClassAddrOp derivedClassOp, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::Type resultType =
getTypeConverter()->convertType(derivedClassOp.getType());
mlir::Value baseAddr = adaptor.getBaseAddr();
// The offset is set in the operation as an unsigned value, but it must be
// applied as a negative offset.
int64_t offsetVal = -(adaptor.getOffset().getZExtValue());
if (offsetVal == 0) {
// If the offset is zero, we can just return the base address,
rewriter.replaceOp(derivedClassOp, baseAddr);
return mlir::success();
}
llvm::SmallVector<mlir::LLVM::GEPArg, 1> offset = {offsetVal};
mlir::Type byteType = mlir::IntegerType::get(resultType.getContext(), 8,
mlir::IntegerType::Signless);
if (derivedClassOp.getAssumeNotNull()) {
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
derivedClassOp, resultType, byteType, baseAddr, offset,
mlir::LLVM::GEPNoWrapFlags::inbounds);
} else {
mlir::Location loc = derivedClassOp.getLoc();
mlir::Value isNull = mlir::LLVM::ICmpOp::create(
rewriter, loc, mlir::LLVM::ICmpPredicate::eq, baseAddr,
mlir::LLVM::ZeroOp::create(rewriter, loc, baseAddr.getType()));
mlir::Value adjusted =
mlir::LLVM::GEPOp::create(rewriter, loc, resultType, byteType, baseAddr,
offset, mlir::LLVM::GEPNoWrapFlags::inbounds);
rewriter.replaceOpWithNewOp<mlir::LLVM::SelectOp>(derivedClassOp, isNull,
baseAddr, adjusted);
}
return mlir::success();
}
mlir::LogicalResult CIRToLLVMFMaxNumOpLowering::matchAndRewrite(
cir::FMaxNumOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type resTy = typeConverter->convertType(op.getType());
if (cir::FenvAttr fenv = op.getFenvAttr())
return lowerToConstrainedFPIntrinsic(
op, adaptor.getOperands(), fenv, resTy, rewriter, "maxnum",
/*hasRoundingMode=*/false, mlir::LLVM::FastmathFlags::nsz);
rewriter.replaceOpWithNewOp<mlir::LLVM::MaxNumOp>(
op, resTy, adaptor.getLhs(), adaptor.getRhs(),
mlir::LLVM::FastmathFlags::nsz);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMFMinNumOpLowering::matchAndRewrite(
cir::FMinNumOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type resTy = typeConverter->convertType(op.getType());
if (cir::FenvAttr fenv = op.getFenvAttr())
return lowerToConstrainedFPIntrinsic(
op, adaptor.getOperands(), fenv, resTy, rewriter, "minnum",
/*hasRoundingMode=*/false, mlir::LLVM::FastmathFlags::nsz);
rewriter.replaceOpWithNewOp<mlir::LLVM::MinNumOp>(
op, resTy, adaptor.getLhs(), adaptor.getRhs(),
mlir::LLVM::FastmathFlags::nsz);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAllocaOpLowering::matchAndRewrite(
cir::AllocaOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value size =
op.isDynamic()
? adaptor.getDynAllocSize()
: mlir::LLVM::ConstantOp::create(
rewriter, op.getLoc(),
typeConverter->convertType(rewriter.getI32Type()), 1);
mlir::Type elementTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getAllocaType());
if (!elementTy)
return op.emitError()
<< "NYI: lowering alloca of a type with no memory representation";
mlir::Type resultTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getType());
assert(!cir::MissingFeatures::addressSpace());
assert(!cir::MissingFeatures::opAllocaAnnotations());
rewriter.replaceOpWithNewOp<mlir::LLVM::AllocaOp>(op, resultTy, elementTy,
size, op.getAlignment());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMRotateOpLowering::matchAndRewrite(
cir::RotateOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// Note that LLVM intrinsic calls to @llvm.fsh{r,l}.i* have the same type as
// the operand.
mlir::Value input = adaptor.getInput();
if (op.isRotateLeft())
rewriter.replaceOpWithNewOp<mlir::LLVM::FshlOp>(op, input, input,
adaptor.getAmount());
else
rewriter.replaceOpWithNewOp<mlir::LLVM::FshrOp>(op, input, input,
adaptor.getAmount());
return mlir::LogicalResult::success();
}
/// The `llvm.byval`, `llvm.sret`, and `llvm.byref` argument attributes carry
/// the pointee type as a TypeAttr. After the CallConvLowering pass that type
/// is still a CIR record; remap it to the lowered LLVM type so translation to
/// LLVM IR does not encounter a CIR type in an attribute. Returns the input
/// unchanged when there is nothing to convert.
static mlir::ArrayAttr
convertTypedArgAttrs(mlir::ArrayAttr argAttrs,
const mlir::TypeConverter &converter,
mlir::MLIRContext *ctx) {
if (!argAttrs)
return argAttrs;
bool changed = false;
SmallVector<mlir::Attribute> loweredArgAttrs;
loweredArgAttrs.reserve(argAttrs.size());
for (mlir::Attribute a : argAttrs) {
auto dict = cast<mlir::DictionaryAttr>(a);
SmallVector<mlir::NamedAttribute> entries(dict.begin(), dict.end());
for (mlir::NamedAttribute &entry : entries) {
StringRef name = entry.getName().strref();
if (name != mlir::LLVM::LLVMDialect::getByValAttrName() &&
name != mlir::LLVM::LLVMDialect::getStructRetAttrName() &&
name != mlir::LLVM::LLVMDialect::getByRefAttrName())
continue;
auto typeAttr = dyn_cast<mlir::TypeAttr>(entry.getValue());
if (!typeAttr)
continue;
mlir::Type lowered = converter.convertType(typeAttr.getValue());
if (lowered && lowered != typeAttr.getValue()) {
entry.setValue(mlir::TypeAttr::get(lowered));
changed = true;
}
}
loweredArgAttrs.push_back(mlir::DictionaryAttr::get(ctx, entries));
}
return changed ? mlir::ArrayAttr::get(ctx, loweredArgAttrs) : argAttrs;
}
static void lowerCallAttributes(cir::CIRCallOpInterface op,
const mlir::TypeConverter &converter,
SmallVectorImpl<mlir::NamedAttribute> &result) {
for (mlir::NamedAttribute attr : op->getAttrs()) {
if (attr.getName() == CIRDialect::getCalleeAttrName() ||
attr.getName() == CIRDialect::getSideEffectAttrName() ||
attr.getName() == CIRDialect::getNoThrowAttrName() ||
attr.getName() == CIRDialect::getNoUnwindAttrName() ||
attr.getName() == CIRDialect::getNoReturnAttrName() ||
attr.getName() == op.getInlineKindAttrName() ||
attr.getName() == CIRDialect::getMustTailAttrName())
continue;
assert(!cir::MissingFeatures::opFuncExtraAttrs());
if (attr.getName() == CIRDialect::getArgAttrsAttrName()) {
auto argAttrs = cast<mlir::ArrayAttr>(attr.getValue());
result.emplace_back(
attr.getName(),
convertTypedArgAttrs(argAttrs, converter, op->getContext()));
continue;
}
result.push_back(attr);
}
}
static mlir::LogicalResult
rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
mlir::ConversionPatternRewriter &rewriter,
const mlir::TypeConverter *converter,
mlir::SymbolTableCollection &symbolTables,
mlir::FlatSymbolRefAttr calleeAttr,
mlir::Block *continueBlock = nullptr,
mlir::Block *landingPadBlock = nullptr) {
llvm::SmallVector<mlir::Type, 8> llvmResults;
mlir::ValueTypeRange<mlir::ResultRange> cirResults = op->getResultTypes();
auto call = cast<cir::CIRCallOpInterface>(op);
if (converter->convertTypes(cirResults, llvmResults).failed())
return mlir::failure();
assert(!cir::MissingFeatures::opCallCallConv());
mlir::LLVM::MemoryEffectsAttr memoryEffects;
bool noUnwind = false;
bool willReturn = false;
bool noReturn = false;
convertSideEffectForCall(op, call.getNothrow(), call.getSideEffect(),
memoryEffects, noUnwind, willReturn, noReturn);
SmallVector<mlir::NamedAttribute, 4> attributes;
lowerCallAttributes(call, *converter, attributes);
mlir::LLVM::LLVMFunctionType llvmFnTy;
// Temporary to handle the case where we need to prepend an operand if the
// callee is an alias.
SmallVector<mlir::Value> adjustedCallOperands;
if (calleeAttr) { // direct call
mlir::Operation *callee =
symbolTables.lookupNearestSymbolFrom(op, calleeAttr);
if (auto fn = mlir::dyn_cast<mlir::FunctionOpInterface>(callee)) {
llvmFnTy = converter->convertType<mlir::LLVM::LLVMFunctionType>(
fn.getFunctionType());
assert(llvmFnTy && "Failed to convert function type");
} else if (auto alias = mlir::cast<mlir::LLVM::AliasOp>(callee)) {
// If the callee was an alias. In that case,
// we need to prepend the address of the alias to the operands. The
// way aliases work in the LLVM dialect is a little counter-intuitive.
// The AliasOp itself is a pseudo-function that returns the address of
// the global value being aliased, but when we generate the call we
// need to insert an operation that gets the address of the AliasOp.
// This all gets sorted out when the LLVM dialect is lowered to LLVM IR.
auto symAttr = mlir::cast<mlir::FlatSymbolRefAttr>(calleeAttr);
auto addrOfAlias =
mlir::LLVM::AddressOfOp::create(
rewriter, op->getLoc(),
mlir::LLVM::LLVMPointerType::get(rewriter.getContext()), symAttr)
.getResult();
adjustedCallOperands.push_back(addrOfAlias);
// Now add the regular operands and assign this to the range value.
llvm::append_range(adjustedCallOperands, callOperands);
callOperands = adjustedCallOperands;
// Clear the callee attribute because we're calling an alias.
calleeAttr = {};
llvmFnTy = mlir::cast<mlir::LLVM::LLVMFunctionType>(alias.getType());
} else {
// Was this an ifunc?
return op->emitError("Unexpected callee type!");
}
} else { // indirect call
assert(!op->getOperands().empty() &&
"operands list must no be empty for the indirect call");
auto calleeTy = op->getOperands().front().getType();
auto calleePtrTy = cast<cir::PointerType>(calleeTy);
auto calleeFuncTy = cast<cir::FuncType>(calleePtrTy.getPointee());
llvm::append_range(adjustedCallOperands, callOperands);
llvmFnTy = cast<mlir::LLVM::LLVMFunctionType>(
converter->convertType(calleeFuncTy));
}
assert(!cir::MissingFeatures::opCallCallConv());
if (landingPadBlock) {
auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::InvokeOp>(
op, llvmFnTy, calleeAttr, callOperands, continueBlock,
mlir::ValueRange{}, landingPadBlock, mlir::ValueRange{});
newOp->setAttrs(attributes);
} else {
auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(
op, llvmFnTy, calleeAttr, callOperands);
newOp->setAttrs(attributes);
if (memoryEffects)
newOp.setMemoryEffectsAttr(memoryEffects);
newOp.setNoUnwind(noUnwind);
newOp.setWillReturn(willReturn);
newOp.setNoreturn(noReturn);
if (op->hasAttr(CIRDialect::getMustTailAttrName()))
newOp.setTailCallKind(mlir::LLVM::TailCallKind::MustTail);
if (std::optional<cir::InlineKind> inlineKind = call.getInlineKind()) {
newOp.setNoInline(*inlineKind == cir::InlineKind::NoInline);
newOp.setInlineHint(*inlineKind == cir::InlineKind::InlineHint);
newOp.setAlwaysInline(*inlineKind == cir::InlineKind::AlwaysInline);
}
}
return mlir::success();
}
mlir::LogicalResult CIRToLLVMCallOpLowering::matchAndRewrite(
cir::CallOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return rewriteCallOrInvoke(op.getOperation(), adaptor.getOperands(), rewriter,
getTypeConverter(), symbolTables,
op.getCalleeAttr());
}
mlir::LogicalResult CIRToLLVMTryCallOpLowering::matchAndRewrite(
cir::TryCallOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
assert(!cir::MissingFeatures::opCallCallConv());
return rewriteCallOrInvoke(
op.getOperation(), adaptor.getOperands(), rewriter, getTypeConverter(),
symbolTables, op.getCalleeAttr(), op.getNormalDest(), op.getUnwindDest());
}
mlir::LogicalResult CIRToLLVMReturnAddrOpLowering::matchAndRewrite(
cir::ReturnAddrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::Type llvmPtrTy = getTypeConverter()->convertType(op.getType());
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm.returnaddress",
llvmPtrTy, adaptor.getOperands());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMFrameAddrOpLowering::matchAndRewrite(
cir::FrameAddrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::Type llvmPtrTy = getTypeConverter()->convertType(op.getType());
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm.frameaddress", llvmPtrTy,
adaptor.getOperands());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMClearCacheOpLowering::matchAndRewrite(
cir::ClearCacheOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value begin = adaptor.getBegin();
mlir::Value end = adaptor.getEnd();
auto intrinNameAttr =
mlir::StringAttr::get(op.getContext(), "llvm.clear_cache");
rewriter.replaceOpWithNewOp<mlir::LLVM::CallIntrinsicOp>(
op, mlir::Type{}, intrinNameAttr, mlir::ValueRange{begin, end});
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAddrOfReturnAddrOpLowering::matchAndRewrite(
cir::AddrOfReturnAddrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::Type llvmPtrTy = getTypeConverter()->convertType(op.getType());
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm.addressofreturnaddress",
llvmPtrTy, adaptor.getOperands());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMLoadOpLowering::matchAndRewrite(
cir::LoadOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::Type llvmTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getType());
if (!llvmTy)
return op.emitError()
<< "NYI: lowering load of a type with no memory representation";
mlir::LLVM::AtomicOrdering ordering = getLLVMMemOrder(op.getMemOrder());
std::optional<size_t> opAlign = op.getAlignment();
unsigned alignment = (unsigned)opAlign.value_or(
getMemoryFallbackAlignment(op.getType(), llvmTy, dataLayout));
assert(!cir::MissingFeatures::lowerModeOptLevel());
std::optional<llvm::StringRef> llvmSyncScope =
getLLVMSyncScope(op.getSyncScope());
mlir::LLVM::LoadOp newLoad = mlir::LLVM::LoadOp::create(
rewriter, op->getLoc(), llvmTy, adaptor.getAddr(), alignment,
op.getIsVolatile(), /*isNonTemporal=*/op.getIsNontemporal(),
/*isInvariant=*/op.getInvariant(), /*isInvariantGroup=*/false, ordering,
llvmSyncScope.value_or(std::string()));
if (mlir::Attribute domain = op->getAttr("cir.riscv_nontemporal_domain"))
newLoad->setAttr("cir.riscv_nontemporal_domain", domain);
// Convert adapted result to its original type if needed.
mlir::Value result = emitFromMemory(rewriter, *getTypeConverter(), dataLayout,
op, newLoad.getResult());
rewriter.replaceOp(op, result);
assert(!cir::MissingFeatures::opLoadStoreTbaa());
return mlir::LogicalResult::success();
}
mlir::LogicalResult
cir::direct::CIRToLLVMVecMaskedLoadOpLowering::matchAndRewrite(
cir::VecMaskedLoadOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::Type llvmResTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getType());
if (!llvmResTy)
return op.emitError()
<< "NYI: lowering masked load of a type with no memory "
"representation";
std::optional<size_t> opAlign = op.getAlignment();
unsigned alignment =
(unsigned)opAlign.value_or(dataLayout.getTypeABIAlignment(llvmResTy));
mlir::IntegerAttr alignAttr = rewriter.getI32IntegerAttr(alignment);
auto newLoad = mlir::LLVM::MaskedLoadOp::create(
rewriter, op.getLoc(), llvmResTy, adaptor.getAddr(), adaptor.getMask(),
adaptor.getPassThru(), alignAttr);
rewriter.replaceOp(op, newLoad.getResult());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMStoreOpLowering::matchAndRewrite(
cir::StoreOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::LLVM::AtomicOrdering memorder = getLLVMMemOrder(op.getMemOrder());
mlir::Type valueType = op.getValue().getType();
const mlir::Type llvmTy =
convertTypeForMemory(*getTypeConverter(), dataLayout, valueType);
if (!llvmTy)
return op.emitError()
<< "NYI: lowering store of a type with no memory representation";
std::optional<size_t> opAlign = op.getAlignment();
unsigned alignment = (unsigned)opAlign.value_or(
getMemoryFallbackAlignment(valueType, llvmTy, dataLayout));
assert(!cir::MissingFeatures::lowerModeOptLevel());
// Convert adapted value to its memory type if needed.
mlir::Value value = emitToMemory(rewriter, dataLayout,
op.getValue().getType(), adaptor.getValue());
assert(!cir::MissingFeatures::opLoadStoreTbaa());
std::optional<llvm::StringRef> llvmSyncScope =
getLLVMSyncScope(op.getSyncScope());
mlir::LLVM::StoreOp storeOp = mlir::LLVM::StoreOp::create(
rewriter, op->getLoc(), value, adaptor.getAddr(), alignment,
op.getIsVolatile(),
/*isNonTemporal=*/op.getIsNontemporal(), /*isInvariantGroup=*/false,
memorder, llvmSyncScope.value_or(std::string()));
if (mlir::Attribute domain = op->getAttr("cir.riscv_nontemporal_domain"))
storeOp->setAttr("cir.riscv_nontemporal_domain", domain);
rewriter.replaceOp(op, storeOp);
assert(!cir::MissingFeatures::opLoadStoreTbaa());
return mlir::LogicalResult::success();
}
static mlir::Type getConstArrayBaseElementType(mlir::Type ty) {
while (auto arrTy = mlir::dyn_cast<cir::ArrayType>(ty))
ty = arrTy.getElementType();
return ty;
}
static bool isBulkLowerableConstArrayBaseElement(mlir::Type baseElemTy) {
return mlir::isa<cir::PointerType, cir::IntType, cir::BoolType,
cir::FPTypeInterface, cir::RecordType>(baseElemTy);
}
mlir::LogicalResult CIRToLLVMConstantOpLowering::matchAndRewrite(
cir::ConstantOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Attribute attr = op.getValue();
if (mlir::isa<cir::PoisonAttr>(attr)) {
rewriter.replaceOpWithNewOp<mlir::LLVM::PoisonOp>(
op, getTypeConverter()->convertType(op.getType()));
return mlir::success();
}
if (mlir::isa<cir::UndefAttr>(attr)) {
rewriter.replaceOpWithNewOp<mlir::LLVM::UndefOp>(
op, getTypeConverter()->convertType(op.getType()));
return mlir::success();
}
if (mlir::isa<mlir::IntegerType>(op.getType())) {
// Verified cir.const operations cannot actually be of these types, but the
// lowering pass may generate temporary cir.const operations with these
// types. This is OK since MLIR allows unverified operations to be alive
// during a pass as long as they don't live past the end of the pass.
attr = op.getValue();
} else if (mlir::isa<cir::BoolType>(op.getType())) {
int value = mlir::cast<cir::BoolAttr>(op.getValue()).getValue();
attr = rewriter.getIntegerAttr(typeConverter->convertType(op.getType()),
value);
} else if (mlir::isa<cir::IntType>(op.getType())) {
// Lower GlobalViewAttr to llvm.mlir.addressof + llvm.mlir.ptrtoint
if (auto ga = mlir::dyn_cast<cir::GlobalViewAttr>(op.getValue())) {
// We can have a global view with an integer type in the case of method
// pointers, but the lowering of those doesn't go through this path.
// They are handled in the visitCirAttr. This is left as an error until
// we have a test case that reaches it.
assert(!cir::MissingFeatures::globalViewIntLowering());
op.emitError() << "global view with integer type";
return mlir::failure();
}
attr = rewriter.getIntegerAttr(
typeConverter->convertType(op.getType()),
mlir::cast<cir::IntAttr>(op.getValue()).getValue());
} else if (mlir::isa<cir::FPTypeInterface>(op.getType())) {
attr = rewriter.getFloatAttr(
typeConverter->convertType(op.getType()),
mlir::cast<cir::FPAttr>(op.getValue()).getValue());
} else if (mlir::isa<cir::PointerType>(op.getType())) {
// Optimize with dedicated LLVM op for null pointers.
if (mlir::isa<cir::ConstPtrAttr>(op.getValue())) {
if (mlir::cast<cir::ConstPtrAttr>(op.getValue()).isNullValue()) {
rewriter.replaceOpWithNewOp<mlir::LLVM::ZeroOp>(
op, typeConverter->convertType(op.getType()));
return mlir::success();
}
}
// Lower GlobalViewAttr to llvm.mlir.addressof
if (auto gv = mlir::dyn_cast<cir::GlobalViewAttr>(op.getValue())) {
auto newOp = lowerCirAttrAsValue(op, gv, rewriter, symbolTables,
getTypeConverter());
rewriter.replaceOp(op, newOp);
return mlir::success();
}
attr = op.getValue();
} else if (const auto arrTy = mlir::dyn_cast<cir::ArrayType>(op.getType())) {
const auto constArr = mlir::dyn_cast<cir::ConstArrayAttr>(op.getValue());
if (!constArr && !isa<cir::ZeroAttr, cir::UndefAttr>(op.getValue()))
return op.emitError() << "array does not have a constant initializer";
std::optional<mlir::Attribute> denseAttr;
if (constArr && (denseAttr = lowerConstArrayAttr(constArr, symbolTables,
typeConverter))) {
attr = denseAttr.value();
} else {
const mlir::Value initVal = lowerCirAttrAsValue(
op, op.getValue(), rewriter, symbolTables, typeConverter);
rewriter.replaceOp(op, initVal);
return mlir::success();
}
} else if (const auto recordAttr =
mlir::dyn_cast<cir::ConstRecordAttr>(op.getValue())) {
auto initVal = lowerCirAttrAsValue(op, recordAttr, rewriter, symbolTables,
typeConverter);
rewriter.replaceOp(op, initVal);
return mlir::success();
} else if (const auto vecTy = mlir::dyn_cast<cir::VectorType>(op.getType())) {
rewriter.replaceOp(op,
lowerCirAttrAsValue(op, op.getValue(), rewriter,
symbolTables, getTypeConverter()));
return mlir::success();
} else if (mlir::isa<cir::RecordType>(op.getType())) {
if (mlir::isa<cir::ZeroAttr, cir::UndefAttr>(attr)) {
mlir::Value initVal =
lowerCirAttrAsValue(op, attr, rewriter, symbolTables, typeConverter);
rewriter.replaceOp(op, initVal);
return mlir::success();
}
return op.emitError() << "unsupported lowering for record constant type "
<< op.getType();
} else if (auto complexTy = mlir::dyn_cast<cir::ComplexType>(op.getType())) {
mlir::Type complexElemTy = complexTy.getElementType();
mlir::Type complexElemLLVMTy = typeConverter->convertType(complexElemTy);
if (auto zeroInitAttr = mlir::dyn_cast<cir::ZeroAttr>(op.getValue())) {
mlir::TypedAttr zeroAttr = rewriter.getZeroAttr(complexElemLLVMTy);
mlir::ArrayAttr array = rewriter.getArrayAttr({zeroAttr, zeroAttr});
rewriter.replaceOpWithNewOp<mlir::LLVM::ConstantOp>(
op, getTypeConverter()->convertType(op.getType()), array);
return mlir::success();
}
if (mlir::isa<cir::UndefAttr>(op.getValue())) {
rewriter.replaceOpWithNewOp<mlir::LLVM::UndefOp>(
op, getTypeConverter()->convertType(op.getType()));
return mlir::success();
}
auto complexAttr = mlir::cast<cir::ConstComplexAttr>(op.getValue());
mlir::Attribute components[2];
if (mlir::isa<cir::IntType>(complexElemTy)) {
components[0] = rewriter.getIntegerAttr(
complexElemLLVMTy,
mlir::cast<cir::IntAttr>(complexAttr.getReal()).getValue());
components[1] = rewriter.getIntegerAttr(
complexElemLLVMTy,
mlir::cast<cir::IntAttr>(complexAttr.getImag()).getValue());
} else {
components[0] = rewriter.getFloatAttr(
complexElemLLVMTy,
mlir::cast<cir::FPAttr>(complexAttr.getReal()).getValue());
components[1] = rewriter.getFloatAttr(
complexElemLLVMTy,
mlir::cast<cir::FPAttr>(complexAttr.getImag()).getValue());
}
attr = rewriter.getArrayAttr(components);
} else {
return op.emitError() << "unsupported constant type " << op.getType();
}
rewriter.replaceOpWithNewOp<mlir::LLVM::ConstantOp>(
op, getTypeConverter()->convertType(op.getType()), attr);
return mlir::success();
}
static uint64_t getTypeSize(mlir::Type type, mlir::Operation &op) {
mlir::DataLayout layout(op.getParentOfType<mlir::ModuleOp>());
// For LLVM purposes we treat void as u8.
if (isa<cir::VoidType>(type))
type = cir::IntType::get(type.getContext(), 8, /*isSigned=*/false);
return llvm::divideCeil(layout.getTypeSizeInBits(type), 8);
}
mlir::LogicalResult CIRToLLVMPrefetchOpLowering::matchAndRewrite(
cir::PrefetchOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
rewriter.replaceOpWithNewOp<mlir::LLVM::Prefetch>(
op, adaptor.getAddr(), adaptor.getIsWrite(), adaptor.getLocality(),
/*DataCache=*/1);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMPtrDiffOpLowering::matchAndRewrite(
cir::PtrDiffOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto dstTy = mlir::cast<cir::IntType>(op.getType());
mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy);
auto lhs = mlir::LLVM::PtrToIntOp::create(rewriter, op.getLoc(), llvmDstTy,
adaptor.getLhs());
auto rhs = mlir::LLVM::PtrToIntOp::create(rewriter, op.getLoc(), llvmDstTy,
adaptor.getRhs());
auto diff =
mlir::LLVM::SubOp::create(rewriter, op.getLoc(), llvmDstTy, lhs, rhs);
cir::PointerType ptrTy = op.getLhs().getType();
assert(!cir::MissingFeatures::llvmLoweringPtrDiffConsidersPointee());
uint64_t typeSize = getTypeSize(ptrTy.getPointee(), *op);
// Avoid silly division by 1.
mlir::Value resultVal = diff.getResult();
if (typeSize != 1) {
auto typeSizeVal = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(),
llvmDstTy, typeSize);
if (dstTy.isUnsigned()) {
auto uDiv =
mlir::LLVM::UDivOp::create(rewriter, op.getLoc(), diff, typeSizeVal);
uDiv.setIsExact(true);
resultVal = uDiv.getResult();
} else {
auto sDiv =
mlir::LLVM::SDivOp::create(rewriter, op.getLoc(), diff, typeSizeVal);
sDiv.setIsExact(true);
resultVal = sDiv.getResult();
}
}
rewriter.replaceOp(op, resultVal);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMExpectOpLowering::matchAndRewrite(
cir::ExpectOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// TODO(cir): do not generate LLVM intrinsics under -O0
assert(!cir::MissingFeatures::optInfoAttr());
std::optional<llvm::APFloat> prob = op.getProb();
if (prob)
rewriter.replaceOpWithNewOp<mlir::LLVM::ExpectWithProbabilityOp>(
op, adaptor.getVal(), adaptor.getExpected(), prob.value());
else
rewriter.replaceOpWithNewOp<mlir::LLVM::ExpectOp>(op, adaptor.getVal(),
adaptor.getExpected());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAbsOpLowering::matchAndRewrite(
cir::AbsOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type resTy = typeConverter->convertType(op.getType());
auto absOp = mlir::LLVM::AbsOp::create(rewriter, op.getLoc(), resTy,
adaptor.getOperands()[0],
adaptor.getMinIsPoison());
rewriter.replaceOp(op, absOp);
return mlir::success();
}
/// Convert the `cir.func` attributes to `llvm.func` attributes.
/// Only retain those attributes that are not constructed by
/// `LLVMFuncOp::build`. If `filterArgAttrs` is set, also filter out
/// argument attributes.
void CIRToLLVMFuncOpLowering::lowerFuncAttributes(
cir::FuncOp func, bool filterArgAndResAttrs,
SmallVectorImpl<mlir::NamedAttribute> &result) const {
for (mlir::NamedAttribute attr : func->getAttrs()) {
if (attr.getName() == mlir::SymbolTable::getSymbolAttrName() ||
attr.getName() == func.getFunctionTypeAttrName() ||
attr.getName() == getLinkageAttrNameString() ||
attr.getName() == func.getCallingConvAttrName() ||
attr.getName() == func.getDsoLocalAttrName() ||
attr.getName() == func.getInlineKindAttrName() ||
attr.getName() == func.getSideEffectAttrName() ||
attr.getName() == CIRDialect::getNoReturnAttrName() ||
attr.getName() == CIRDialect::getStrictFPAttrName() ||
attr.getName() == func.getAnnotationsAttrName() ||
(filterArgAndResAttrs &&
(attr.getName() == func.getArgAttrsAttrName() ||
attr.getName() == func.getResAttrsAttrName())))
continue;
assert(!cir::MissingFeatures::opFuncExtraAttrs());
if (attr.getName() == func.getArgAttrsAttrName()) {
auto argAttrs = cast<mlir::ArrayAttr>(attr.getValue());
result.emplace_back(
attr.getName(),
convertTypedArgAttrs(argAttrs, *getTypeConverter(), getContext()));
continue;
}
result.push_back(attr);
}
}
mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewriteAlias(
cir::FuncOp op, llvm::StringRef aliasee, mlir::Type ty, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
SmallVector<mlir::NamedAttribute, 4> attributes;
lowerFuncAttributes(op, /*filterArgAndResAttrs=*/false, attributes);
mlir::Location loc = op.getLoc();
auto aliasOp = rewriter.replaceOpWithNewOp<mlir::LLVM::AliasOp>(
op, ty, convertLinkage(op.getLinkage()), op.getName(), op.getDsoLocal(),
mlir::LLVM::ThreadLocalMode::NotThreadLocal, attributes);
// Create the alias body
mlir::OpBuilder builder(op.getContext());
mlir::Block *block = builder.createBlock(&aliasOp.getInitializerRegion());
builder.setInsertionPointToStart(block);
// The type of AddressOfOp is always a pointer.
assert(!cir::MissingFeatures::addressSpace());
mlir::Type ptrTy = mlir::LLVM::LLVMPointerType::get(ty.getContext());
auto addrOp = mlir::LLVM::AddressOfOp::create(builder, loc, ptrTy, aliasee);
mlir::LLVM::ReturnOp::create(builder, loc, addrOp);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewrite(
cir::FuncOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
cir::FuncType fnType = op.getFunctionType();
bool isDsoLocal = op.getDsoLocal();
mlir::TypeConverter::SignatureConversion signatureConversion(
fnType.getNumInputs());
for (const auto &argType : llvm::enumerate(fnType.getInputs())) {
mlir::Type convertedType = typeConverter->convertType(argType.value());
if (!convertedType)
return mlir::failure();
signatureConversion.addInputs(argType.index(), convertedType);
}
mlir::Type resultType =
getTypeConverter()->convertType(fnType.getReturnType());
// Create the LLVM function operation.
mlir::Type llvmFnTy = mlir::LLVM::LLVMFunctionType::get(
resultType ? resultType : mlir::LLVM::LLVMVoidType::get(getContext()),
signatureConversion.getConvertedTypes(),
/*isVarArg=*/fnType.isVarArg());
// If this is an alias, it needs to be lowered to llvm::AliasOp.
if (std::optional<llvm::StringRef> aliasee = op.getAliasee())
return matchAndRewriteAlias(op, *aliasee, llvmFnTy, adaptor, rewriter);
// LLVMFuncOp expects a single FileLine Location instead of a fused
// location.
mlir::Location loc = op.getLoc();
if (mlir::FusedLoc fusedLoc = mlir::dyn_cast<mlir::FusedLoc>(loc))
loc = fusedLoc.getLocations()[0];
assert((mlir::isa<mlir::FileLineColLoc>(loc) ||
mlir::isa<mlir::UnknownLoc>(loc)) &&
"expected single location or unknown location here");
mlir::LLVM::Linkage linkage = convertLinkage(op.getLinkage());
mlir::LLVM::CConv cconv = convertCallingConv(op.getCallingConv());
SmallVector<mlir::NamedAttribute, 4> attributes;
lowerFuncAttributes(op, /*filterArgAndResAttrs=*/false, attributes);
mlir::LLVM::LLVMFuncOp fn = mlir::LLVM::LLVMFuncOp::create(
rewriter, loc, op.getName(), llvmFnTy, linkage, isDsoLocal, cconv,
mlir::SymbolRefAttr(), attributes);
assert(!cir::MissingFeatures::opFuncMultipleReturnVals());
if (std::optional<cir::SideEffect> sideEffectKind = op.getSideEffect()) {
switch (*sideEffectKind) {
case cir::SideEffect::All:
break;
case cir::SideEffect::Pure:
fn.setMemoryEffectsAttr(mlir::LLVM::MemoryEffectsAttr::get(
fn.getContext(),
/*other=*/mlir::LLVM::ModRefInfo::Ref,
/*argMem=*/mlir::LLVM::ModRefInfo::Ref,
/*inaccessibleMem=*/mlir::LLVM::ModRefInfo::Ref,
/*errnoMem=*/mlir::LLVM::ModRefInfo::Ref,
/*targetMem0=*/mlir::LLVM::ModRefInfo::Ref,
/*targetMem1=*/mlir::LLVM::ModRefInfo::Ref));
fn.setNoUnwind(true);
fn.setWillReturn(true);
break;
case cir::SideEffect::Const:
fn.setMemoryEffectsAttr(mlir::LLVM::MemoryEffectsAttr::get(
fn.getContext(),
/*other=*/mlir::LLVM::ModRefInfo::NoModRef,
/*argMem=*/mlir::LLVM::ModRefInfo::NoModRef,
/*inaccessibleMem=*/mlir::LLVM::ModRefInfo::NoModRef,
/*errnoMem=*/mlir::LLVM::ModRefInfo::NoModRef,
/*targetMem0=*/mlir::LLVM::ModRefInfo::NoModRef,
/*targetMem1=*/mlir::LLVM::ModRefInfo::NoModRef));
fn.setNoUnwind(true);
fn.setWillReturn(true);
break;
}
}
if (op->hasAttr(CIRDialect::getNoReturnAttrName()))
fn.setNoreturn(true);
// The LLVM dialect's LLVMFuncOp has no dedicated field for the `strictfp`
// function attribute, so route it through the `passthrough` array. The MLIR
// LLVM IR translator forwards `passthrough` entries to LLVM IR as function
// attributes.
if (op->hasAttr(CIRDialect::getStrictFPAttrName()))
fn.setPassthroughAttr(rewriter.getArrayAttr(
{rewriter.getStringAttr(CIRDialect::getStrictFPAttrName())}));
if (std::optional<cir::InlineKind> inlineKind = op.getInlineKind()) {
fn.setNoInline(*inlineKind == cir::InlineKind::NoInline);
fn.setInlineHint(*inlineKind == cir::InlineKind::InlineHint);
fn.setAlwaysInline(*inlineKind == cir::InlineKind::AlwaysInline);
}
if (std::optional<llvm::StringRef> personality = op.getPersonality())
fn.setPersonality(*personality);
fn.setVisibility_(
lowerCIRVisibilityToLLVMVisibility(op.getGlobalVisibility()));
rewriter.inlineRegionBefore(op.getBody(), fn.getBody(), fn.end());
if (failed(rewriter.convertRegionTypes(&fn.getBody(), *typeConverter,
&signatureConversion)))
return mlir::failure();
rewriter.eraseOp(op);
return mlir::LogicalResult::success();
}
mlir::LogicalResult CIRToLLVMGetGlobalOpLowering::matchAndRewrite(
cir::GetGlobalOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// FIXME(cir): Premature DCE to avoid lowering stuff we're not using.
// CIRGen should mitigate this and not emit the get_global.
if (op->getUses().empty()) {
rewriter.eraseOp(op);
return mlir::success();
}
mlir::Type type = getTypeConverter()->convertType(op.getType());
mlir::Operation *newop = mlir::LLVM::AddressOfOp::create(
rewriter, op.getLoc(), type, op.getName());
if (op.getTls()) {
// Handle access to TLS via intrinsic.
newop = mlir::LLVM::ThreadlocalAddressOp::create(rewriter, op.getLoc(),
type, newop->getResult(0));
}
rewriter.replaceOp(op, newop);
return mlir::success();
}
llvm::SmallVector<mlir::NamedAttribute>
CIRToLLVMGlobalOpLowering::lowerGlobalAttributes(
cir::GlobalOp op, mlir::ConversionPatternRewriter &rewriter) const {
SmallVector<mlir::NamedAttribute> attributes;
if (mlir::StringAttr sectionAttr = op.getSectionAttr())
attributes.push_back(rewriter.getNamedAttr("section", sectionAttr));
mlir::LLVM::VisibilityAttr visibility = mlir::LLVM::VisibilityAttr::get(
getContext(),
lowerCIRVisibilityToLLVMVisibility(op.getGlobalVisibility()));
attributes.push_back(rewriter.getNamedAttr("visibility_", visibility));
if (op->getAttr(CUDAExternallyInitializedAttr::getMnemonic()))
attributes.push_back(rewriter.getNamedAttr("externally_initialized",
rewriter.getUnitAttr()));
return attributes;
}
static mlir::LLVM::ThreadLocalMode
convertTlsModelAttrToLLVM(TLSModelAttr attr) {
// assert that we can just static-cast these.
#define CHECK_ENUM(CIR, LLVM_VAL) \
static_assert(static_cast<unsigned>(TLSModel::CIR) == \
static_cast<unsigned>(mlir::LLVM::ThreadLocalMode::LLVM_VAL))
CHECK_ENUM(GeneralDynamic, GeneralDynamic);
CHECK_ENUM(LocalDynamic, LocalDynamic);
CHECK_ENUM(InitialExec, InitialExec);
CHECK_ENUM(LocalExec, LocalExec);
#undef CHECK_ENUM
if (!attr)
return mlir::LLVM::ThreadLocalMode::NotThreadLocal;
return static_cast<mlir::LLVM::ThreadLocalMode>(attr.getValue());
}
/// Replace CIR global with a region initialized LLVM global and update
/// insertion point to the end of the initializer block.
void CIRToLLVMGlobalOpLowering::setupRegionInitializedLLVMGlobalOp(
cir::GlobalOp op, mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type llvmType =
convertTypeForMemory(*getTypeConverter(), dataLayout, op.getSymType());
// Keep the global's type in sync with the value built by CIRAttrToValue: a
// flexible array member initializer requires an oversized anonymous struct.
if (std::optional<mlir::Attribute> init = op.getInitialValue())
llvmType = adjustGlobalTypeForInit(llvmType, *init, *getTypeConverter(),
dataLayout);
// FIXME: These default values are placeholders until the the equivalent
// attributes are available on cir.global ops. This duplicates code
// in CIRToLLVMGlobalOpLowering::matchAndRewrite() but that will go
// away when the placeholders are no longer needed.
const bool isConst = op.getConstant();
unsigned addrSpace = 0;
if (auto targetAS = mlir::dyn_cast_if_present<cir::TargetAddressSpaceAttr>(
op.getAddrSpaceAttr()))
addrSpace = targetAS.getValue();
const bool isDsoLocal = op.getDsoLocal();
mlir::LLVM::ThreadLocalMode threadLocalMode =
convertTlsModelAttrToLLVM(op.getTlsModelAttr());
const uint64_t alignment = op.getAlignment().value_or(0);
const mlir::LLVM::Linkage linkage = convertLinkage(op.getLinkage());
const StringRef symbol = op.getSymName();
mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter);
SmallVector<mlir::NamedAttribute> attributes =
lowerGlobalAttributes(op, rewriter);
mlir::LLVM::GlobalOp newGlobalOp =
rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
op, llvmType, isConst, linkage, symbol, nullptr, alignment, addrSpace,
isDsoLocal, threadLocalMode, comdatAttr, attributes);
newGlobalOp.getRegion().emplaceBlock();
rewriter.setInsertionPointToEnd(newGlobalOp.getInitializerBlock());
}
mlir::LogicalResult
CIRToLLVMGlobalOpLowering::matchAndRewriteRegionInitializedGlobal(
cir::GlobalOp op, mlir::Attribute init,
mlir::ConversionPatternRewriter &rewriter) const {
// TODO: Generalize this handling when more types are needed here.
assert(
(isa<cir::BlockAddrDiffAttr, cir::BlockAddrInfoAttr, cir::ConstArrayAttr,
cir::ConstRecordAttr, cir::ConstVectorAttr, cir::ConstPtrAttr,
cir::ConstComplexAttr, cir::GlobalViewAttr, cir::TypeInfoAttr,
cir::UndefAttr, cir::PoisonAttr, cir::VTableAttr, cir::ZeroAttr>(
init)));
// TODO(cir): once LLVM's dialect has proper equivalent attributes this
// should be updated. For now, we use a custom op to initialize globals
// to the appropriate value.
const mlir::Location loc = op.getLoc();
setupRegionInitializedLLVMGlobalOp(op, rewriter);
// Pass blockInfoAddr so that block address initializers (either as the whole
// initializer or nested inside an aggregate) can be resolved by the
// BlockAddrInfoAttr visitor.
CIRAttrToValue valueConverter(op, rewriter, symbolTables, typeConverter,
&blockInfoAddr);
mlir::Value value = valueConverter.visit(init);
mlir::LLVM::ReturnOp::create(rewriter, loc, value);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
cir::GlobalOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// If this global requires non-trivial initialization or destruction,
// that needs to be moved to runtime handlers during LoweringPrepare.
if (!op.getCtorRegion().empty() || !op.getDtorRegion().empty())
return op.emitError() << "GlobalOp ctor and dtor regions should be removed "
"in LoweringPrepare";
std::optional<mlir::Attribute> init = op.getInitialValue();
// Fetch required values to create LLVM op.
const mlir::Type cirSymType = op.getSymType();
// This is the LLVM dialect type.
mlir::Type llvmType =
convertTypeForMemory(*getTypeConverter(), dataLayout, cirSymType);
if (!llvmType)
return op.emitError()
<< "NYI: lowering global of a type with no memory representation";
// A flexible array member initializer makes the constant larger than the
// record's declared type, so the global must use an oversized anonymous
// struct instead.
if (init.has_value())
llvmType = adjustGlobalTypeForInit(llvmType, *init, *getTypeConverter(),
dataLayout);
// FIXME: These default values are placeholders until the the equivalent
// attributes are available on cir.global ops.
const bool isConst = op.getConstant();
unsigned addrSpace = 0;
if (auto targetAS = mlir::dyn_cast_if_present<cir::TargetAddressSpaceAttr>(
op.getAddrSpaceAttr()))
addrSpace = targetAS.getValue();
const bool isDsoLocal = op.getDsoLocal();
mlir::LLVM::ThreadLocalMode threadLocalMode =
convertTlsModelAttrToLLVM(op.getTlsModelAttr());
const uint64_t alignment = op.getAlignment().value_or(0);
const mlir::LLVM::Linkage linkage = convertLinkage(op.getLinkage());
const StringRef symbol = op.getSymName();
SmallVector<mlir::NamedAttribute> attributes =
lowerGlobalAttributes(op, rewriter);
// If this is a variable alias, lower it to llvm.mlir.alias.
if (std::optional<llvm::StringRef> aliasee = op.getAliasee()) {
mlir::Location loc = op.getLoc();
auto aliasOp = rewriter.replaceOpWithNewOp<mlir::LLVM::AliasOp>(
op, llvmType, linkage, symbol, isDsoLocal, threadLocalMode, attributes);
mlir::OpBuilder builder(op.getContext());
mlir::Block *block = builder.createBlock(&aliasOp.getInitializerRegion());
builder.setInsertionPointToStart(block);
mlir::Type ptrTy =
mlir::LLVM::LLVMPointerType::get(getContext(), addrSpace);
auto addrOp =
mlir::LLVM::AddressOfOp::create(builder, loc, ptrTy, *aliasee);
mlir::LLVM::ReturnOp::create(builder, loc, addrOp);
return mlir::success();
}
if (init.has_value()) {
if (mlir::isa<cir::FPAttr, cir::IntAttr, cir::BoolAttr>(init.value())) {
GlobalInitAttrRewriter initRewriter(llvmType, rewriter);
init = initRewriter.visit(init.value());
// If initRewriter returned a null attribute, init will have a value but
// the value will be null. If that happens, initRewriter didn't handle the
// attribute type. It probably needs to be added to
// GlobalInitAttrRewriter.
if (!init.value()) {
op.emitError() << "unsupported initializer '" << init.value() << "'";
return mlir::failure();
}
} else if (auto constArr =
mlir::dyn_cast<cir::ConstArrayAttr>(init.value())) {
// Bulk-emit llvm.mlir.global when lowerConstArrayAttr can build the
// whole initializer as one aggregate attribute (no insertvalue
// region). Leaf type must match what lowerConstArrayAttr handles
// (pointers, integers, bools, floats, and string literals with
// trailing_zeros).
if (isBulkLowerableConstArrayBaseElement(
getConstArrayBaseElementType(constArr.getType()))) {
mlir::ModuleOp modOp = op->getParentOfType<mlir::ModuleOp>();
if (std::optional<mlir::Attribute> bulkInit = lowerConstArrayAttr(
constArr, symbolTables, typeConverter, modOp)) {
mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter);
rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
op, llvmType, isConst, linkage, symbol, bulkInit.value(),
alignment, addrSpace, isDsoLocal, threadLocalMode, comdatAttr,
attributes);
return mlir::success();
}
}
return matchAndRewriteRegionInitializedGlobal(op, init.value(), rewriter);
} else if (auto constRecord =
mlir::dyn_cast<cir::ConstRecordAttr>(init.value())) {
// Bulk-emit llvm.mlir.global when every member of the record can be
// lowered to a constant attribute. The LLVM dialect global translation
// turns an ArrayAttr (one element per struct field) into an
// llvm::ConstantStruct, so the whole initializer becomes a single
// attribute on the global instead of an insertvalue region.
mlir::ModuleOp modOp = op->getParentOfType<mlir::ModuleOp>();
if (std::optional<mlir::Attribute> bulkInit = lowerConstRecordAttr(
constRecord, symbolTables, typeConverter, modOp)) {
mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter);
rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
op, llvmType, isConst, linkage, symbol, bulkInit.value(), alignment,
addrSpace, isDsoLocal, threadLocalMode, comdatAttr, attributes);
return mlir::success();
}
return matchAndRewriteRegionInitializedGlobal(op, init.value(), rewriter);
} else if (mlir::isa<cir::BlockAddrDiffAttr, cir::BlockAddrInfoAttr,
cir::ConstVectorAttr, cir::ConstRecordAttr,
cir::ConstPtrAttr, cir::ConstComplexAttr,
cir::GlobalViewAttr, cir::TypeInfoAttr, cir::UndefAttr,
cir::PoisonAttr, cir::VTableAttr, cir::ZeroAttr>(
init.value())) {
// TODO(cir): once LLVM's dialect has proper equivalent attributes this
// should be updated. For now, we use a custom op to initialize globals
// to the appropriate value.
return matchAndRewriteRegionInitializedGlobal(op, init.value(), rewriter);
} else {
// We will only get here if new initializer types are added and this
// code is not updated to handle them.
op.emitError() << "unsupported initializer '" << init.value() << "'";
return mlir::failure();
}
}
mlir::SymbolRefAttr comdatAttr = getComdatAttr(op, rewriter);
rewriter.replaceOpWithNewOp<mlir::LLVM::GlobalOp>(
op, llvmType, isConst, linkage, symbol, init.value_or(mlir::Attribute()),
alignment, addrSpace, isDsoLocal, threadLocalMode, comdatAttr,
attributes);
return mlir::success();
}
mlir::SymbolRefAttr
CIRToLLVMGlobalOpLowering::getComdatAttr(cir::GlobalOp &op,
mlir::OpBuilder &builder) const {
if (!op.getComdat())
return mlir::SymbolRefAttr{};
mlir::ModuleOp modOp = op->getParentOfType<mlir::ModuleOp>();
mlir::OpBuilder::InsertionGuard guard(builder);
StringRef comdatName("__llvm_comdat_globals");
if (!comdatOp) {
builder.setInsertionPointToStart(modOp.getBody());
comdatOp =
mlir::LLVM::ComdatOp::create(builder, modOp.getLoc(), comdatName);
}
if (auto comdatSelector = comdatOp.lookupSymbol<mlir::LLVM::ComdatSelectorOp>(
op.getSymName())) {
return mlir::SymbolRefAttr::get(
builder.getContext(), comdatName,
mlir::FlatSymbolRefAttr::get(comdatSelector.getSymNameAttr()));
}
builder.setInsertionPointToStart(&comdatOp.getBody().back());
auto selectorOp = mlir::LLVM::ComdatSelectorOp::create(
builder, comdatOp.getLoc(), op.getSymName(),
mlir::LLVM::comdat::Comdat::Any);
return mlir::SymbolRefAttr::get(
builder.getContext(), comdatName,
mlir::FlatSymbolRefAttr::get(selectorOp.getSymNameAttr()));
}
mlir::LogicalResult CIRToLLVMSwitchFlatOpLowering::matchAndRewrite(
cir::SwitchFlatOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
llvm::SmallVector<mlir::APInt, 8> caseValues;
for (mlir::Attribute val : op.getCaseValues()) {
auto intAttr = cast<cir::IntAttr>(val);
caseValues.push_back(intAttr.getValue());
}
llvm::SmallVector<mlir::Block *, 8> caseDestinations;
llvm::SmallVector<mlir::ValueRange, 8> caseOperands;
for (mlir::Block *x : op.getCaseDestinations())
caseDestinations.push_back(x);
for (mlir::OperandRange x : op.getCaseOperands())
caseOperands.push_back(x);
// Set switch op to branch to the newly created blocks.
rewriter.setInsertionPoint(op);
rewriter.replaceOpWithNewOp<mlir::LLVM::SwitchOp>(
op, adaptor.getCondition(), op.getDefaultDestination(),
op.getDefaultOperands(), caseValues, caseDestinations, caseOperands);
return mlir::success();
}
static mlir::LLVM::IntegerOverflowFlags nswFlag(bool nsw) {
return nsw ? mlir::LLVM::IntegerOverflowFlags::nsw
: mlir::LLVM::IntegerOverflowFlags::none;
}
template <typename CIROp, typename LLVMIntOp>
static mlir::LogicalResult
lowerIncDecOp(CIROp op, typename CIROp::Adaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) {
mlir::Type llvmType = adaptor.getInput().getType();
mlir::Location loc = op.getLoc();
auto maybeNSW = nswFlag(op.getNoSignedWrap());
mlir::LLVM::ConstantOp one;
if (mlir::isa<cir::VectorType>(op.getType())) {
mlir::DenseIntElementsAttr oneVec = mlir::DenseIntElementsAttr::get(
mlir::cast<mlir::ShapedType>(llvmType), 1);
one = mlir::LLVM::ConstantOp::create(rewriter, loc, llvmType, oneVec);
} else {
one = mlir::LLVM::ConstantOp::create(rewriter, loc, llvmType, 1);
}
rewriter.replaceOpWithNewOp<LLVMIntOp>(op, adaptor.getInput(), one, maybeNSW);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMIncOpLowering::matchAndRewrite(
cir::IncOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerIncDecOp<cir::IncOp, mlir::LLVM::AddOp>(op, adaptor, rewriter);
}
mlir::LogicalResult CIRToLLVMDecOpLowering::matchAndRewrite(
cir::DecOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerIncDecOp<cir::DecOp, mlir::LLVM::SubOp>(op, adaptor, rewriter);
}
mlir::LogicalResult CIRToLLVMMinusOpLowering::matchAndRewrite(
cir::MinusOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
bool isVector = mlir::isa<cir::VectorType>(op.getType());
mlir::Type llvmType = adaptor.getInput().getType();
mlir::Location loc = op.getLoc();
auto maybeNSW = nswFlag(op.getNoSignedWrap());
mlir::Value zero;
if (isVector)
zero = mlir::LLVM::ZeroOp::create(rewriter, loc, llvmType);
else
zero = mlir::LLVM::ConstantOp::create(rewriter, loc, llvmType, 0);
rewriter.replaceOpWithNewOp<mlir::LLVM::SubOp>(op, zero, adaptor.getInput(),
maybeNSW);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMNotOpLowering::matchAndRewrite(
cir::NotOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type elementType = elementTypeIfVector(op.getType());
bool isVector = mlir::isa<cir::VectorType>(op.getType());
mlir::Type llvmType = adaptor.getInput().getType();
mlir::Location loc = op.getLoc();
if (mlir::isa<cir::IntType>(elementType)) {
mlir::Value minusOne;
if (isVector) {
const uint64_t numElements =
mlir::dyn_cast<cir::VectorType>(op.getType()).getSize();
SmallVector<int32_t> values(numElements, -1);
mlir::DenseIntElementsAttr denseVec = rewriter.getI32VectorAttr(values);
minusOne =
mlir::LLVM::ConstantOp::create(rewriter, loc, llvmType, denseVec);
} else {
minusOne = mlir::LLVM::ConstantOp::create(rewriter, loc, llvmType, -1);
}
rewriter.replaceOpWithNewOp<mlir::LLVM::XOrOp>(op, adaptor.getInput(),
minusOne);
return mlir::success();
}
if (mlir::isa<cir::BoolType>(elementType)) {
auto one = mlir::LLVM::ConstantOp::create(rewriter, loc, llvmType, 1);
rewriter.replaceOpWithNewOp<mlir::LLVM::XOrOp>(op, adaptor.getInput(), one);
return mlir::success();
}
return op.emitError() << "Unsupported type for bitwise NOT";
}
static bool isIntTypeUnsigned(mlir::Type type) {
// TODO: Ideally, we should only need to check cir::IntType here.
return mlir::isa<cir::IntType>(type)
? mlir::cast<cir::IntType>(type).isUnsigned()
: mlir::cast<mlir::IntegerType>(type).isUnsigned();
}
//===----------------------------------------------------------------------===//
// Binary Op Lowering
//===----------------------------------------------------------------------===//
template <typename BinOp>
static mlir::LLVM::IntegerOverflowFlags intOverflowFlag(BinOp op) {
if (op.getNoUnsignedWrap())
return mlir::LLVM::IntegerOverflowFlags::nuw;
if (op.getNoSignedWrap())
return mlir::LLVM::IntegerOverflowFlags::nsw;
return mlir::LLVM::IntegerOverflowFlags::none;
}
/// Lower an arithmetic op that supports saturation, overflow flags, and an FP
/// Lower an integer Add/Sub op that may use saturating-arithmetic semantics.
template <typename UIntSatOp, typename SIntSatOp, typename IntOp,
typename CIROp>
static mlir::LogicalResult
lowerSaturatableArithOp(CIROp op, mlir::Value lhs, mlir::Value rhs,
mlir::ConversionPatternRewriter &rewriter) {
const mlir::Type eltType = elementTypeIfVector(op.getRhs().getType());
assert(cir::isIntOrBoolType(eltType) &&
"saturatable arith op expects integer operand types");
if (op.getSaturated()) {
if (isIntTypeUnsigned(eltType))
rewriter.replaceOpWithNewOp<UIntSatOp>(op, lhs, rhs);
else
rewriter.replaceOpWithNewOp<SIntSatOp>(op, lhs, rhs);
return mlir::success();
}
rewriter.replaceOpWithNewOp<IntOp>(op, lhs, rhs, intOverflowFlag(op));
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAddOpLowering::matchAndRewrite(
cir::AddOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerSaturatableArithOp<mlir::LLVM::UAddSat, mlir::LLVM::SAddSat,
mlir::LLVM::AddOp>(op, adaptor.getLhs(),
adaptor.getRhs(), rewriter);
}
mlir::LogicalResult CIRToLLVMSubOpLowering::matchAndRewrite(
cir::SubOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerSaturatableArithOp<mlir::LLVM::USubSat, mlir::LLVM::SSubSat,
mlir::LLVM::SubOp>(op, adaptor.getLhs(),
adaptor.getRhs(), rewriter);
}
mlir::LogicalResult CIRToLLVMMulOpLowering::matchAndRewrite(
cir::MulOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
assert(cir::isIntOrBoolType(elementTypeIfVector(op.getRhs().getType())) &&
"cir.mul expects integer operand types");
rewriter.replaceOpWithNewOp<mlir::LLVM::MulOp>(
op, adaptor.getLhs(), adaptor.getRhs(), intOverflowFlag(op));
return mlir::success();
}
/// Lower an integer Div/Rem op to its signed or unsigned LLVM counterpart.
template <typename UIntOp, typename SIntOp, typename CIROp>
static mlir::LogicalResult
lowerIntBinaryOp(CIROp op, mlir::Value lhs, mlir::Value rhs,
mlir::ConversionPatternRewriter &rewriter) {
const mlir::Type eltType = elementTypeIfVector(op.getRhs().getType());
assert(cir::isIntOrBoolType(eltType) &&
"integer binary op expects integer operand types");
if (isIntTypeUnsigned(eltType))
rewriter.replaceOpWithNewOp<UIntOp>(op, lhs, rhs);
else
rewriter.replaceOpWithNewOp<SIntOp>(op, lhs, rhs);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMDivOpLowering::matchAndRewrite(
cir::DivOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerIntBinaryOp<mlir::LLVM::UDivOp, mlir::LLVM::SDivOp>(
op, adaptor.getLhs(), adaptor.getRhs(), rewriter);
}
mlir::LogicalResult CIRToLLVMRemOpLowering::matchAndRewrite(
cir::RemOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerIntBinaryOp<mlir::LLVM::URemOp, mlir::LLVM::SRemOp>(
op, adaptor.getLhs(), adaptor.getRhs(), rewriter);
}
template <typename CIROp, typename UIntOp, typename SIntOp>
static mlir::LogicalResult
lowerMinMaxOp(CIROp op, typename CIROp::Adaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) {
const mlir::Value lhs = adaptor.getLhs();
const mlir::Value rhs = adaptor.getRhs();
if (isIntTypeUnsigned(elementTypeIfVector(op.getRhs().getType())))
rewriter.replaceOpWithNewOp<UIntOp>(op, lhs, rhs);
else
rewriter.replaceOpWithNewOp<SIntOp>(op, lhs, rhs);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMMaxOpLowering::matchAndRewrite(
cir::MaxOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerMinMaxOp<cir::MaxOp, mlir::LLVM::UMaxOp, mlir::LLVM::SMaxOp>(
op, adaptor, rewriter);
}
mlir::LogicalResult CIRToLLVMMinOpLowering::matchAndRewrite(
cir::MinOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerMinMaxOp<cir::MinOp, mlir::LLVM::UMinOp, mlir::LLVM::SMinOp>(
op, adaptor, rewriter);
}
/// Convert from a CIR comparison kind to an LLVM IR integral comparison kind.
static mlir::LLVM::ICmpPredicate
convertCmpKindToICmpPredicate(cir::CmpOpKind kind, bool isSigned) {
using CIR = cir::CmpOpKind;
using LLVMICmp = mlir::LLVM::ICmpPredicate;
switch (kind) {
case CIR::eq:
return LLVMICmp::eq;
case CIR::ne:
return LLVMICmp::ne;
case CIR::lt:
return (isSigned ? LLVMICmp::slt : LLVMICmp::ult);
case CIR::le:
return (isSigned ? LLVMICmp::sle : LLVMICmp::ule);
case CIR::gt:
return (isSigned ? LLVMICmp::sgt : LLVMICmp::ugt);
case CIR::ge:
return (isSigned ? LLVMICmp::sge : LLVMICmp::uge);
case CIR::one:
case CIR::uno:
llvm_unreachable("FP-only comparison used with integer type");
}
llvm_unreachable("Unknown CmpOpKind");
}
/// Convert from a CIR comparison kind to an LLVM IR floating-point comparison
/// kind.
static mlir::LLVM::FCmpPredicate
convertCmpKindToFCmpPredicate(cir::CmpOpKind kind) {
using CIR = cir::CmpOpKind;
using LLVMFCmp = mlir::LLVM::FCmpPredicate;
switch (kind) {
case CIR::eq:
return LLVMFCmp::oeq;
case CIR::ne:
return LLVMFCmp::une;
case CIR::lt:
return LLVMFCmp::olt;
case CIR::le:
return LLVMFCmp::ole;
case CIR::gt:
return LLVMFCmp::ogt;
case CIR::ge:
return LLVMFCmp::oge;
case CIR::one:
return LLVMFCmp::one;
case CIR::uno:
return LLVMFCmp::uno;
}
llvm_unreachable("Unknown CmpOpKind");
}
static llvm::StringRef
convertCmpKindToConstrainedFCmpPredicate(cir::CmpOpKind kind) {
using CIR = cir::CmpOpKind;
switch (kind) {
case CIR::eq:
return "oeq";
case CIR::ne:
return "une";
case CIR::lt:
return "olt";
case CIR::le:
return "ole";
case CIR::gt:
return "ogt";
case CIR::ge:
return "oge";
case CIR::one:
return "one";
case CIR::uno:
return "uno";
}
llvm_unreachable("Unknown CmpOpKind");
}
static bool isSignalingConstrainedFCmp(cir::CmpOpKind kind) {
using CIR = cir::CmpOpKind;
switch (kind) {
case CIR::lt:
case CIR::le:
case CIR::gt:
case CIR::ge:
return true;
case CIR::eq:
case CIR::ne:
case CIR::one:
case CIR::uno:
return false;
}
llvm_unreachable("Unknown CmpOpKind");
}
static mlir::LLVM::CallIntrinsicOp
createConstrainedFCmpCall(mlir::ConversionPatternRewriter &rewriter,
mlir::Location loc, mlir::Value lhs, mlir::Value rhs,
cir::CmpOpKind kind, cir::FenvAttr fenv,
mlir::Type llvmResTy) {
llvm::SmallVector<mlir::Value, 4> callOperands = {
lhs, rhs,
createFenvMetadataValue(rewriter, loc,
convertCmpKindToConstrainedFCmpPredicate(kind)),
createFenvMetadataValue(rewriter, loc,
getConstrainedExceptMetadata(fenv))};
llvm::StringRef intrinsicName = isSignalingConstrainedFCmp(kind)
? "llvm.experimental.constrained.fcmps"
: "llvm.experimental.constrained.fcmp";
return createCallLLVMIntrinsicOp(rewriter, loc, intrinsicName, llvmResTy,
callOperands);
}
mlir::LogicalResult CIRToLLVMCmpOpLowering::matchAndRewrite(
cir::CmpOp cmpOp, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type type = cmpOp.getLhs().getType();
if (mlir::isa<cir::IntType, mlir::IntegerType>(type)) {
bool isSigned = mlir::isa<cir::IntType>(type)
? mlir::cast<cir::IntType>(type).isSigned()
: mlir::cast<mlir::IntegerType>(type).isSigned();
mlir::LLVM::ICmpPredicate kind =
convertCmpKindToICmpPredicate(cmpOp.getKind(), isSigned);
rewriter.replaceOpWithNewOp<mlir::LLVM::ICmpOp>(
cmpOp, kind, adaptor.getLhs(), adaptor.getRhs());
return mlir::success();
}
if (mlir::isa<cir::BoolType, cir::PointerType, cir::VPtrType>(type)) {
// Booleans, including enums with a boolean underlying type, compare as
// unsigned integers, as do pointers and !cir.vptr, which lowers to a
// pointer.
mlir::LLVM::ICmpPredicate kind =
convertCmpKindToICmpPredicate(cmpOp.getKind(), /*isSigned=*/false);
rewriter.replaceOpWithNewOp<mlir::LLVM::ICmpOp>(
cmpOp, kind, adaptor.getLhs(), adaptor.getRhs());
return mlir::success();
}
if (mlir::isa<cir::FPTypeInterface>(type)) {
mlir::Type llvmResTy = getTypeConverter()->convertType(cmpOp.getType());
if (cir::FenvAttr fenv = cmpOp.getFenvAttr()) {
mlir::LLVM::CallIntrinsicOp call = createConstrainedFCmpCall(
rewriter, cmpOp.getLoc(), adaptor.getLhs(), adaptor.getRhs(),
cmpOp.getKind(), fenv, llvmResTy);
rewriter.replaceOp(cmpOp, call.getResult(0));
return mlir::success();
}
mlir::LLVM::FCmpPredicate kind =
convertCmpKindToFCmpPredicate(cmpOp.getKind());
rewriter.replaceOpWithNewOp<mlir::LLVM::FCmpOp>(
cmpOp, kind, adaptor.getLhs(), adaptor.getRhs());
return mlir::success();
}
if (mlir::isa<cir::ComplexType>(type)) {
mlir::Value lhs = adaptor.getLhs();
mlir::Value rhs = adaptor.getRhs();
mlir::Location loc = cmpOp.getLoc();
auto complexType = mlir::cast<cir::ComplexType>(cmpOp.getLhs().getType());
mlir::Type complexElemTy =
getTypeConverter()->convertType(complexType.getElementType());
auto lhsReal = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, lhs, ArrayRef(int64_t{0}));
auto lhsImag = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, lhs, ArrayRef(int64_t{1}));
auto rhsReal = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, rhs, ArrayRef(int64_t{0}));
auto rhsImag = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, rhs, ArrayRef(int64_t{1}));
if (cmpOp.getKind() == cir::CmpOpKind::eq) {
if (complexElemTy.isInteger()) {
auto realCmp = mlir::LLVM::ICmpOp::create(
rewriter, loc, mlir::LLVM::ICmpPredicate::eq, lhsReal, rhsReal);
auto imagCmp = mlir::LLVM::ICmpOp::create(
rewriter, loc, mlir::LLVM::ICmpPredicate::eq, lhsImag, rhsImag);
rewriter.replaceOpWithNewOp<mlir::LLVM::AndOp>(cmpOp, realCmp, imagCmp);
return mlir::success();
}
auto realCmp = mlir::LLVM::FCmpOp::create(
rewriter, loc, mlir::LLVM::FCmpPredicate::oeq, lhsReal, rhsReal);
auto imagCmp = mlir::LLVM::FCmpOp::create(
rewriter, loc, mlir::LLVM::FCmpPredicate::oeq, lhsImag, rhsImag);
rewriter.replaceOpWithNewOp<mlir::LLVM::AndOp>(cmpOp, realCmp, imagCmp);
return mlir::success();
}
if (cmpOp.getKind() == cir::CmpOpKind::ne) {
if (complexElemTy.isInteger()) {
auto realCmp = mlir::LLVM::ICmpOp::create(
rewriter, loc, mlir::LLVM::ICmpPredicate::ne, lhsReal, rhsReal);
auto imagCmp = mlir::LLVM::ICmpOp::create(
rewriter, loc, mlir::LLVM::ICmpPredicate::ne, lhsImag, rhsImag);
rewriter.replaceOpWithNewOp<mlir::LLVM::OrOp>(cmpOp, realCmp, imagCmp);
return mlir::success();
}
auto realCmp = mlir::LLVM::FCmpOp::create(
rewriter, loc, mlir::LLVM::FCmpPredicate::une, lhsReal, rhsReal);
auto imagCmp = mlir::LLVM::FCmpOp::create(
rewriter, loc, mlir::LLVM::FCmpPredicate::une, lhsImag, rhsImag);
rewriter.replaceOpWithNewOp<mlir::LLVM::OrOp>(cmpOp, realCmp, imagCmp);
return mlir::success();
}
}
return cmpOp.emitError() << "unsupported type for CmpOp: " << type;
}
/// Shared lowering logic for checked binary arithmetic overflow operations.
/// The \p opStr parameter specifies the arithmetic operation name used in the
/// LLVM intrinsic (e.g., "add", "sub", "mul").
template <typename OpTy>
static mlir::LogicalResult
lowerBinOpOverflow(OpTy op, typename OpTy::Adaptor adaptor,
mlir::ConversionPatternRewriter &rewriter,
const mlir::TypeConverter *typeConverter,
llvm::StringRef opStr) {
mlir::Location loc = op.getLoc();
cir::IntType operandTy = op.getLhs().getType();
// The result type may be a `cir.bool`, which behaves as a 1-bit unsigned
// integer for the purposes of the checked arithmetic.
mlir::Type resultTy = op.getResult().getType();
auto resultIntTy = mlir::dyn_cast<cir::IntType>(resultTy);
unsigned resultWidth = resultIntTy ? resultIntTy.getWidth() : 1;
bool resultSigned = resultIntTy && resultIntTy.getIsSigned();
bool sign = operandTy.getIsSigned() || resultSigned;
unsigned width =
std::max(operandTy.getWidth() + (sign && operandTy.isUnsigned()),
resultWidth + (sign && !resultSigned));
mlir::IntegerType encompassedLLVMTy = rewriter.getIntegerType(width);
mlir::Value lhs = adaptor.getLhs();
mlir::Value rhs = adaptor.getRhs();
if (operandTy.getWidth() < width) {
if (operandTy.isSigned()) {
lhs = mlir::LLVM::SExtOp::create(rewriter, loc, encompassedLLVMTy, lhs);
rhs = mlir::LLVM::SExtOp::create(rewriter, loc, encompassedLLVMTy, rhs);
} else {
lhs = mlir::LLVM::ZExtOp::create(rewriter, loc, encompassedLLVMTy, lhs);
rhs = mlir::LLVM::ZExtOp::create(rewriter, loc, encompassedLLVMTy, rhs);
}
}
// The intrinsic name is `@llvm.{s|u}{op}.with.overflow.i{width}`
std::string intrinName = ("llvm." + llvm::Twine(sign ? 's' : 'u') + opStr +
".with.overflow.i" + llvm::Twine(width))
.str();
auto intrinNameAttr = mlir::StringAttr::get(op.getContext(), intrinName);
mlir::IntegerType overflowLLVMTy = rewriter.getI1Type();
auto intrinRetTy = mlir::LLVM::LLVMStructType::getLiteral(
rewriter.getContext(), {encompassedLLVMTy, overflowLLVMTy});
auto callLLVMIntrinOp = mlir::LLVM::CallIntrinsicOp::create(
rewriter, loc, intrinRetTy, intrinNameAttr, mlir::ValueRange{lhs, rhs});
mlir::Value intrinRet = callLLVMIntrinOp.getResult(0);
mlir::Value result = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, intrinRet, ArrayRef<int64_t>{0})
.getResult();
mlir::Value overflow = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, intrinRet, ArrayRef<int64_t>{1})
.getResult();
if (resultWidth < width) {
mlir::Type resultLLVMTy = typeConverter->convertType(resultTy);
auto truncResult =
mlir::LLVM::TruncOp::create(rewriter, loc, resultLLVMTy, result);
// Extend the truncated result back to the encompassing type to check for
// any overflows during the truncation.
mlir::Value truncResultExt;
if (resultSigned)
truncResultExt = mlir::LLVM::SExtOp::create(
rewriter, loc, encompassedLLVMTy, truncResult);
else
truncResultExt = mlir::LLVM::ZExtOp::create(
rewriter, loc, encompassedLLVMTy, truncResult);
auto truncOverflow = mlir::LLVM::ICmpOp::create(
rewriter, loc, mlir::LLVM::ICmpPredicate::ne, truncResultExt, result);
result = truncResult;
overflow = mlir::LLVM::OrOp::create(rewriter, loc, overflow, truncOverflow);
}
mlir::Type boolLLVMTy =
typeConverter->convertType(op.getOverflow().getType());
if (boolLLVMTy != rewriter.getI1Type())
overflow = mlir::LLVM::ZExtOp::create(rewriter, loc, boolLLVMTy, overflow);
rewriter.replaceOp(op, mlir::ValueRange{result, overflow});
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAddOverflowOpLowering::matchAndRewrite(
cir::AddOverflowOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerBinOpOverflow(op, adaptor, rewriter, getTypeConverter(), "add");
}
mlir::LogicalResult CIRToLLVMSubOverflowOpLowering::matchAndRewrite(
cir::SubOverflowOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerBinOpOverflow(op, adaptor, rewriter, getTypeConverter(), "sub");
}
mlir::LogicalResult CIRToLLVMMulOverflowOpLowering::matchAndRewrite(
cir::MulOverflowOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return lowerBinOpOverflow(op, adaptor, rewriter, getTypeConverter(), "mul");
}
mlir::LogicalResult CIRToLLVMFrexpOpLowering::matchAndRewrite(
cir::FrexpOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Location loc = op.getLoc();
mlir::Type fpLLVMTy =
getTypeConverter()->convertType(op.getResult().getType());
mlir::Type intLLVMTy = getTypeConverter()->convertType(op.getExp().getType());
auto structTy = mlir::LLVM::LLVMStructType::getLiteral(rewriter.getContext(),
{fpLLVMTy, intLLVMTy});
auto callOp = createCallLLVMIntrinsicOp(rewriter, loc, "llvm.frexp", structTy,
adaptor.getSrc());
mlir::Value result = callOp.getResult(0);
mlir::Value mantissa =
mlir::LLVM::ExtractValueOp::create(rewriter, loc, result, 0);
mlir::Value exponent =
mlir::LLVM::ExtractValueOp::create(rewriter, loc, result, 1);
rewriter.replaceOp(op, mlir::ValueRange{mantissa, exponent});
return mlir::success();
}
mlir::LogicalResult CIRToLLVMModfOpLowering::matchAndRewrite(
cir::ModfOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Location loc = op.getLoc();
mlir::Type fpLLVMTy =
getTypeConverter()->convertType(op.getFractional().getType());
auto structTy = mlir::LLVM::LLVMStructType::getLiteral(rewriter.getContext(),
{fpLLVMTy, fpLLVMTy});
auto callOp = createCallLLVMIntrinsicOp(rewriter, loc, "llvm.modf", structTy,
adaptor.getSrc());
mlir::Value result = callOp.getResult(0);
mlir::Value fractional =
mlir::LLVM::ExtractValueOp::create(rewriter, loc, result, 0);
mlir::Value integral =
mlir::LLVM::ExtractValueOp::create(rewriter, loc, result, 1);
rewriter.replaceOp(op, mlir::ValueRange{fractional, integral});
return mlir::success();
}
mlir::LogicalResult CIRToLLVMShiftOpLowering::matchAndRewrite(
cir::ShiftOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
assert((op.getValue().getType() == op.getType()) &&
"inconsistent operands' types NYI");
const mlir::Type llvmTy = getTypeConverter()->convertType(op.getType());
mlir::Value amt = adaptor.getAmount();
mlir::Value val = adaptor.getValue();
auto cirAmtTy = mlir::dyn_cast<cir::IntType>(op.getAmount().getType());
bool isUnsigned;
if (cirAmtTy) {
auto cirValTy = mlir::cast<cir::IntType>(op.getValue().getType());
isUnsigned = cirValTy.isUnsigned();
// Ensure shift amount is the same type as the value. Some undefined
// behavior might occur in the casts below as per [C99 6.5.7.3].
// Vector type shift amount needs no cast as type consistency is expected to
// be already be enforced at CIRGen.
if (cirAmtTy)
amt = getLLVMIntCast(rewriter, amt, llvmTy, true, cirAmtTy.getWidth(),
cirValTy.getWidth());
} else {
auto cirValVTy = mlir::cast<cir::VectorType>(op.getValue().getType());
isUnsigned =
mlir::cast<cir::IntType>(cirValVTy.getElementType()).isUnsigned();
}
// Lower to the proper LLVM shift operation.
if (op.getIsShiftleft()) {
rewriter.replaceOpWithNewOp<mlir::LLVM::ShlOp>(op, llvmTy, val, amt);
return mlir::success();
}
if (isUnsigned)
rewriter.replaceOpWithNewOp<mlir::LLVM::LShrOp>(op, llvmTy, val, amt);
else
rewriter.replaceOpWithNewOp<mlir::LLVM::AShrOp>(op, llvmTy, val, amt);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMSelectOpLowering::matchAndRewrite(
cir::SelectOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto getConstantBool = [](mlir::Value value) -> cir::BoolAttr {
auto definingOp = value.getDefiningOp<cir::ConstantOp>();
if (!definingOp)
return {};
auto constValue = definingOp.getValueAttr<cir::BoolAttr>();
if (!constValue)
return {};
return constValue;
};
// Two special cases in the LLVMIR codegen of select op:
// - select %0, %1, false => and %0, %1
// - select %0, true, %1 => or %0, %1
if (mlir::isa<cir::BoolType>(op.getTrueValue().getType())) {
cir::BoolAttr trueValue = getConstantBool(op.getTrueValue());
cir::BoolAttr falseValue = getConstantBool(op.getFalseValue());
if (falseValue && !falseValue.getValue()) {
// select %0, %1, false => and %0, %1
rewriter.replaceOpWithNewOp<mlir::LLVM::AndOp>(op, adaptor.getCondition(),
adaptor.getTrueValue());
return mlir::success();
}
if (trueValue && trueValue.getValue()) {
// select %0, true, %1 => or %0, %1
rewriter.replaceOpWithNewOp<mlir::LLVM::OrOp>(op, adaptor.getCondition(),
adaptor.getFalseValue());
return mlir::success();
}
}
mlir::Value llvmCondition = adaptor.getCondition();
rewriter.replaceOpWithNewOp<mlir::LLVM::SelectOp>(
op, llvmCondition, adaptor.getTrueValue(), adaptor.getFalseValue());
return mlir::success();
}
static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
mlir::DataLayout &dataLayout) {
converter.addConversion([&](cir::PointerType type) -> mlir::Type {
mlir::ptr::MemorySpaceAttrInterface addrSpaceAttr = type.getAddrSpace();
unsigned numericAS = 0;
if (auto targetAsAttr =
mlir::dyn_cast_if_present<cir::TargetAddressSpaceAttr>(
addrSpaceAttr))
numericAS = targetAsAttr.getValue();
return mlir::LLVM::LLVMPointerType::get(type.getContext(), numericAS);
});
converter.addConversion([&](cir::VPtrType type) -> mlir::Type {
assert(!cir::MissingFeatures::addressSpace());
return mlir::LLVM::LLVMPointerType::get(type.getContext());
});
converter.addConversion([&](cir::ArrayType type) -> mlir::Type {
mlir::Type ty =
convertTypeForMemory(converter, dataLayout, type.getElementType());
// A null element type means an unsupported member (e.g. a _BitInt with
// byte-array storage); propagate the conversion failure.
if (!ty)
return {};
return mlir::LLVM::LLVMArrayType::get(ty, type.getSize());
});
converter.addConversion([&](cir::VectorType type) -> mlir::Type {
// Vector-of-_BitInt memory layout is not modeled here (the element would
// stay at its literal width); report a conversion failure rather than emit
// an unvalidated vector.
if (auto intTy = mlir::dyn_cast<cir::IntType>(type.getElementType());
intTy && intTy.isBitInt())
return {};
const mlir::Type ty = converter.convertType(type.getElementType());
return mlir::VectorType::get(type.getSize(), ty, {type.getIsScalable()});
});
converter.addConversion([&](cir::BoolType type) -> mlir::Type {
return mlir::IntegerType::get(type.getContext(), 1,
mlir::IntegerType::Signless);
});
converter.addConversion([&](cir::IntType type) -> mlir::Type {
// LLVM doesn't work with signed types, so we drop the CIR signs here.
return mlir::IntegerType::get(type.getContext(), type.getWidth());
});
converter.addConversion([&](cir::SingleType type) -> mlir::Type {
return mlir::Float32Type::get(type.getContext());
});
converter.addConversion([&](cir::DoubleType type) -> mlir::Type {
return mlir::Float64Type::get(type.getContext());
});
converter.addConversion([&](cir::FP80Type type) -> mlir::Type {
return mlir::Float80Type::get(type.getContext());
});
converter.addConversion([&](cir::FP128Type type) -> mlir::Type {
return mlir::Float128Type::get(type.getContext());
});
converter.addConversion([&](cir::LongDoubleType type) -> mlir::Type {
return converter.convertType(type.getUnderlying());
});
converter.addConversion([&](cir::FP16Type type) -> mlir::Type {
return mlir::Float16Type::get(type.getContext());
});
converter.addConversion([&](cir::BF16Type type) -> mlir::Type {
return mlir::BFloat16Type::get(type.getContext());
});
converter.addConversion([&](cir::ComplexType type) -> mlir::Type {
// A complex type is lowered to an LLVM struct that contains the real and
// imaginary part as data fields.
mlir::Type elementTy = converter.convertType(type.getElementType());
mlir::Type structFields[2] = {elementTy, elementTy};
return mlir::LLVM::LLVMStructType::getLiteral(type.getContext(),
structFields);
});
converter.addConversion([&](cir::FuncType type) -> std::optional<mlir::Type> {
auto result = converter.convertType(type.getReturnType());
llvm::SmallVector<mlir::Type> arguments;
arguments.reserve(type.getNumInputs());
if (converter.convertTypes(type.getInputs(), arguments).failed())
return std::nullopt;
auto varArg = type.isVarArg();
return mlir::LLVM::LLVMFunctionType::get(result, arguments, varArg);
});
converter.addConversion([&](cir::StructType type) -> mlir::Type {
llvm::SmallVector<mlir::Type> llvmMembers;
for (mlir::Type ty : type.getMembers()) {
mlir::Type memberTy = convertTypeForMemory(converter, dataLayout, ty);
// A null member means an unsupported type (e.g. a _BitInt with byte-array
// storage); propagate the conversion failure instead of building an
// invalid struct body.
if (!memberTy)
return {};
llvmMembers.push_back(memberTy);
}
mlir::LLVM::LLVMStructType llvmStruct;
if (type.getName()) {
llvmStruct = mlir::LLVM::LLVMStructType::getIdentified(
type.getContext(), type.getPrefixedName());
if (llvmStruct.setBody(llvmMembers, type.getPacked()).failed())
llvm_unreachable("Failed to set body of record");
} else {
llvmStruct = mlir::LLVM::LLVMStructType::getLiteral(
type.getContext(), llvmMembers, type.getPacked());
}
return llvmStruct;
});
// Unions are lowered as only the largest member.
converter.addConversion([&](cir::UnionType type) -> mlir::Type {
llvm::SmallVector<mlir::Type> llvmMembers;
if (!type.getMembers().empty())
if (auto storage = type.getUnionStorageType(dataLayout)) {
mlir::Type storageTy =
convertTypeForMemory(converter, dataLayout, storage);
if (!storageTy)
return {};
llvmMembers.push_back(storageTy);
}
if (mlir::Type pad = type.getPadding()) {
mlir::Type padTy = convertTypeForMemory(converter, dataLayout, pad);
if (!padTy)
return {};
llvmMembers.push_back(padTy);
}
mlir::LLVM::LLVMStructType llvmStruct;
if (type.getName()) {
llvmStruct = mlir::LLVM::LLVMStructType::getIdentified(
type.getContext(), type.getPrefixedName());
if (llvmStruct.setBody(llvmMembers, type.getPacked()).failed())
llvm_unreachable("Failed to set body of record");
} else {
llvmStruct = mlir::LLVM::LLVMStructType::getLiteral(
type.getContext(), llvmMembers, type.getPacked());
}
return llvmStruct;
});
converter.addConversion([&](cir::VoidType type) -> mlir::Type {
return mlir::LLVM::LLVMVoidType::get(type.getContext());
});
}
static void buildCtorDtorList(
mlir::ModuleOp module, StringRef globalXtorName, StringRef llvmXtorName,
llvm::function_ref<std::pair<StringRef, int>(mlir::Attribute)> createXtor) {
llvm::SmallVector<std::pair<StringRef, int>> globalXtors;
for (const mlir::NamedAttribute namedAttr : module->getAttrs()) {
if (namedAttr.getName() == globalXtorName) {
for (auto attr : mlir::cast<mlir::ArrayAttr>(namedAttr.getValue()))
globalXtors.emplace_back(createXtor(attr));
break;
}
}
if (globalXtors.empty())
return;
mlir::OpBuilder builder(module.getContext());
builder.setInsertionPointToEnd(&module.getBodyRegion().back());
// Create a global array llvm.global_ctors with element type of
// struct { i32, ptr, ptr }
auto ctorPFTy = mlir::LLVM::LLVMPointerType::get(builder.getContext());
llvm::SmallVector<mlir::Type> ctorStructFields;
ctorStructFields.push_back(builder.getI32Type());
ctorStructFields.push_back(ctorPFTy);
ctorStructFields.push_back(ctorPFTy);
auto ctorStructTy = mlir::LLVM::LLVMStructType::getLiteral(
builder.getContext(), ctorStructFields);
auto ctorStructArrayTy =
mlir::LLVM::LLVMArrayType::get(ctorStructTy, globalXtors.size());
mlir::Location loc = module.getLoc();
auto newGlobalOp = mlir::LLVM::GlobalOp::create(
builder, loc, ctorStructArrayTy, /*constant=*/false,
mlir::LLVM::Linkage::Appending, llvmXtorName, mlir::Attribute());
builder.createBlock(&newGlobalOp.getRegion());
builder.setInsertionPointToEnd(newGlobalOp.getInitializerBlock());
mlir::Value result =
mlir::LLVM::UndefOp::create(builder, loc, ctorStructArrayTy);
for (auto [index, fn] : llvm::enumerate(globalXtors)) {
mlir::Value structInit =
mlir::LLVM::UndefOp::create(builder, loc, ctorStructTy);
mlir::Value initPriority = mlir::LLVM::ConstantOp::create(
builder, loc, ctorStructFields[0], fn.second);
mlir::Value initFuncAddr = mlir::LLVM::AddressOfOp::create(
builder, loc, ctorStructFields[1], fn.first);
mlir::Value initAssociate =
mlir::LLVM::ZeroOp::create(builder, loc, ctorStructFields[2]);
// Literal zero makes the InsertValueOp::create ambiguous.
llvm::SmallVector<int64_t> zero{0};
structInit = mlir::LLVM::InsertValueOp::create(builder, loc, structInit,
initPriority, zero);
structInit = mlir::LLVM::InsertValueOp::create(builder, loc, structInit,
initFuncAddr, 1);
// TODO: handle associated data for initializers.
structInit = mlir::LLVM::InsertValueOp::create(builder, loc, structInit,
initAssociate, 2);
result = mlir::LLVM::InsertValueOp::create(builder, loc, result, structInit,
index);
}
mlir::LLVM::ReturnOp::create(builder, loc, result);
}
mlir::LogicalResult CIRToLLVMObjSizeOpLowering::matchAndRewrite(
cir::ObjSizeOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type llvmResTy = getTypeConverter()->convertType(op.getType());
mlir::Location loc = op->getLoc();
mlir::IntegerType i1Ty = rewriter.getI1Type();
auto i1Val = [&rewriter, &loc, &i1Ty](bool val) {
return mlir::LLVM::ConstantOp::create(rewriter, loc, i1Ty, val);
};
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm.objectsize", llvmResTy,
{
adaptor.getPtr(),
i1Val(op.getMin()),
i1Val(op.getNullunknown()),
i1Val(op.getDynamic()),
});
return mlir::LogicalResult::success();
}
//===----------------------------------------------------------------------===//
// @llvm.global.annotations emission
//===----------------------------------------------------------------------===//
namespace {
constexpr StringRef llvmMetadataSectionName = "llvm.metadata";
/// Get-or-create a private constant string global in the llvm.metadata
/// section, deduplicated by string content.
mlir::LLVM::GlobalOp
getOrCreateAnnotationStringGlobal(mlir::OpBuilder &builder, mlir::Location loc,
mlir::ModuleOp module, llvm::StringRef str,
llvm::StringMap<mlir::LLVM::GlobalOp> &cache,
bool isArg) {
auto it = cache.find(str);
if (it != cache.end())
return it->second;
auto i8Ty = mlir::IntegerType::get(module.getContext(), 8);
auto arrayTy = mlir::LLVM::LLVMArrayType::get(i8Ty, str.size() + 1);
std::string name = ".str";
if (!cache.empty())
name += "." + std::to_string(cache.size());
name += ".annotation";
if (isArg)
name += ".arg";
mlir::LLVM::GlobalOp strGlobal = mlir::LLVM::GlobalOp::create(
builder, loc, arrayTy, /*isConstant=*/true, mlir::LLVM::Linkage::Private,
name, mlir::StringAttr::get(module.getContext(), std::string(str) + '\0'),
/*alignment=*/isArg ? 1 : 0);
if (!isArg)
strGlobal.setSection(llvmMetadataSectionName);
strGlobal.setUnnamedAddr(mlir::LLVM::UnnamedAddr::Global);
strGlobal.setDsoLocal(true);
cache[str] = strGlobal;
return strGlobal;
}
/// Get-or-create a private constant struct holding the annotation arguments,
/// deduplicated by ArrayAttr identity.
mlir::LLVM::GlobalOp getOrCreateAnnotationArgsVar(
mlir::OpBuilder &builder, mlir::Location loc, mlir::ModuleOp module,
mlir::ArrayAttr argsAttr,
llvm::StringMap<mlir::LLVM::GlobalOp> &argStringCache,
llvm::MapVector<mlir::ArrayAttr, mlir::LLVM::GlobalOp> &argsCache) {
auto it = argsCache.find(argsAttr);
if (it != argsCache.end())
return it->second;
auto ptrTy = mlir::LLVM::LLVMPointerType::get(builder.getContext());
llvm::SmallVector<mlir::Type> fieldTypes;
for (mlir::Attribute arg : argsAttr) {
if (mlir::isa<mlir::StringAttr>(arg))
fieldTypes.push_back(ptrTy);
else if (auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(arg))
fieldTypes.push_back(intAttr.getType());
else
llvm_unreachable("Unsupported annotation arg type");
}
auto structTy =
mlir::LLVM::LLVMStructType::getLiteral(builder.getContext(), fieldTypes);
std::string name = ".args";
if (!argsCache.empty())
name += "." + std::to_string(argsCache.size());
name += ".annotation";
mlir::LLVM::GlobalOp argsGlobal = mlir::LLVM::GlobalOp::create(
builder, loc, structTy, /*isConstant=*/true, mlir::LLVM::Linkage::Private,
name, mlir::Attribute());
argsGlobal.setSection(llvmMetadataSectionName);
argsGlobal.setUnnamedAddr(mlir::LLVM::UnnamedAddr::Global);
argsGlobal.setDsoLocal(true);
// Build the initializer block.
argsGlobal.getRegion().push_back(new mlir::Block());
mlir::OpBuilder initBuilder(module.getContext());
initBuilder.setInsertionPointToEnd(argsGlobal.getInitializerBlock());
mlir::Value structInit =
mlir::LLVM::UndefOp::create(initBuilder, loc, structTy);
for (auto [idx, arg] : llvm::enumerate(argsAttr)) {
if (auto strArg = mlir::dyn_cast<mlir::StringAttr>(arg)) {
mlir::LLVM::GlobalOp strGlobal = getOrCreateAnnotationStringGlobal(
builder, loc, module, strArg.getValue(), argStringCache,
/*isArg=*/true);
mlir::LLVM::AddressOfOp strAddr = mlir::LLVM::AddressOfOp::create(
initBuilder, loc, ptrTy, strGlobal.getSymName());
structInit = mlir::LLVM::InsertValueOp::create(initBuilder, loc,
structInit, strAddr, idx);
} else if (auto intArg = mlir::dyn_cast<mlir::IntegerAttr>(arg)) {
mlir::LLVM::ConstantOp intConst = mlir::LLVM::ConstantOp::create(
initBuilder, loc, intArg.getType(), intArg.getValue());
structInit = mlir::LLVM::InsertValueOp::create(initBuilder, loc,
structInit, intConst, idx);
} else {
llvm_unreachable("Unsupported annotation arg type");
}
}
mlir::LLVM::ReturnOp::create(initBuilder, loc, structInit);
argsCache[argsAttr] = argsGlobal;
return argsGlobal;
}
/// Resolve a possibly-fused MLIR Location to a FileLineColLoc, returning
/// {filename, line}. Returns {empty, 0} if no usable file location is found.
std::pair<llvm::StringRef, unsigned> extractFileLine(mlir::Location loc) {
mlir::Location resolved = loc;
if (auto fused = mlir::dyn_cast<mlir::FusedLoc>(resolved)) {
if (!fused.getLocations().empty())
resolved = fused.getLocations()[0];
}
if (auto fl = mlir::dyn_cast<mlir::FileLineColLoc>(resolved))
return {fl.getFilename().getValue(), fl.getLine()};
return {"", 0};
}
} // namespace
void ConvertCIRToLLVMPass::collectGlobalAnnotations(mlir::ModuleOp module) {
auto handleArray = [&](mlir::StringAttr symName, mlir::ArrayAttr arr,
mlir::Location loc) {
if (!arr)
return;
for (mlir::Attribute a : arr)
if (auto annot = mlir::dyn_cast<cir::AnnotationAttr>(a))
collectedAnnotations.emplace_back(symName, annot, loc);
};
// Walk in IR order: GlobalOps first (they appear before functions in the
// module body), then FuncOps. This matches OGCG's emission order.
module.walk([&](cir::GlobalOp op) {
handleArray(op.getSymNameAttr(), op.getAnnotationsAttr(), op.getLoc());
});
module.walk([&](cir::FuncOp op) {
handleArray(op.getSymNameAttr(), op.getAnnotationsAttr(), op.getLoc());
});
}
void ConvertCIRToLLVMPass::buildGlobalAnnotationsVar(mlir::ModuleOp module) {
if (collectedAnnotations.empty())
return;
mlir::MLIRContext *ctx = module.getContext();
mlir::OpBuilder builder(ctx);
builder.setInsertionPointToEnd(&module.getBodyRegion().back());
auto ptrTy = mlir::LLVM::LLVMPointerType::get(ctx);
auto i32Ty = builder.getI32Type();
// Each entry: { ptr, ptr, ptr, i32, ptr }.
auto entryTy = mlir::LLVM::LLVMStructType::getLiteral(
ctx, {ptrTy, ptrTy, ptrTy, i32Ty, ptrTy});
auto arrayTy =
mlir::LLVM::LLVMArrayType::get(entryTy, collectedAnnotations.size());
mlir::Location moduleLoc = module.getLoc();
auto annotationsGlobal = mlir::LLVM::GlobalOp::create(
builder, moduleLoc, arrayTy, /*isConstant=*/false,
mlir::LLVM::Linkage::Appending, "llvm.global.annotations",
mlir::Attribute());
annotationsGlobal.setSection(llvmMetadataSectionName);
// Strings/args constants must come *before* @llvm.global.annotations to
// match OGCG output order. Insert them just before the annotations global.
mlir::OpBuilder constsBuilder(ctx);
constsBuilder.setInsertionPoint(annotationsGlobal);
llvm::StringMap<mlir::LLVM::GlobalOp> stringCache;
llvm::StringMap<mlir::LLVM::GlobalOp> argStringCache;
llvm::MapVector<mlir::ArrayAttr, mlir::LLVM::GlobalOp> argsCache;
// Build the initializer block of @llvm.global.annotations.
annotationsGlobal.getRegion().push_back(new mlir::Block());
mlir::OpBuilder initBuilder(ctx);
initBuilder.setInsertionPointToEnd(annotationsGlobal.getInitializerBlock());
mlir::Value arrayVal =
mlir::LLVM::UndefOp::create(initBuilder, moduleLoc, arrayTy);
for (auto [idx, entry] : llvm::enumerate(collectedAnnotations)) {
mlir::Value entryVal =
mlir::LLVM::UndefOp::create(initBuilder, moduleLoc, entryTy);
// Field 0: ptr to the annotated symbol. (Literal zero is ambiguous on
// InsertValueOp::create, wrap in a SmallVector.)
llvm::SmallVector<int64_t> zero{0};
mlir::LLVM::AddressOfOp symAddr = mlir::LLVM::AddressOfOp::create(
initBuilder, moduleLoc, ptrTy, entry.symName.getValue());
entryVal = mlir::LLVM::InsertValueOp::create(initBuilder, moduleLoc,
entryVal, symAddr, zero);
// Field 1: ptr to the annotation name string.
mlir::LLVM::GlobalOp nameGlobal = getOrCreateAnnotationStringGlobal(
constsBuilder, moduleLoc, module, entry.annotation.getName().getValue(),
stringCache, /*isArg=*/false);
mlir::LLVM::AddressOfOp nameAddr = mlir::LLVM::AddressOfOp::create(
initBuilder, moduleLoc, ptrTy, nameGlobal.getSymName());
entryVal = mlir::LLVM::InsertValueOp::create(initBuilder, moduleLoc,
entryVal, nameAddr, 1);
// Fields 2 and 3: ptr to filename string and line number.
auto [filename, line] = extractFileLine(entry.loc);
mlir::LLVM::GlobalOp fileGlobal = getOrCreateAnnotationStringGlobal(
constsBuilder, moduleLoc, module, filename, stringCache,
/*isArg=*/false);
mlir::LLVM::AddressOfOp fileAddr = mlir::LLVM::AddressOfOp::create(
initBuilder, moduleLoc, ptrTy, fileGlobal.getSymName());
entryVal = mlir::LLVM::InsertValueOp::create(initBuilder, moduleLoc,
entryVal, fileAddr, 2);
mlir::LLVM::ConstantOp lineConst =
mlir::LLVM::ConstantOp::create(initBuilder, moduleLoc, i32Ty, line);
entryVal = mlir::LLVM::InsertValueOp::create(initBuilder, moduleLoc,
entryVal, lineConst, 3);
// Field 4: ptr to args, or null if none.
mlir::ArrayAttr args = entry.annotation.getArgs();
mlir::Value argsField;
if (!args || args.empty()) {
argsField = mlir::LLVM::ZeroOp::create(initBuilder, moduleLoc, ptrTy);
} else {
mlir::LLVM::GlobalOp argsGlobal = getOrCreateAnnotationArgsVar(
constsBuilder, moduleLoc, module, args, argStringCache, argsCache);
argsField = mlir::LLVM::AddressOfOp::create(initBuilder, moduleLoc, ptrTy,
argsGlobal.getSymName());
}
entryVal = mlir::LLVM::InsertValueOp::create(initBuilder, moduleLoc,
entryVal, argsField, 4);
arrayVal = mlir::LLVM::InsertValueOp::create(initBuilder, moduleLoc,
arrayVal, entryVal, idx);
}
mlir::LLVM::ReturnOp::create(initBuilder, moduleLoc, arrayVal);
}
void ConvertCIRToLLVMPass::resolveBlockAddressOp(
LLVMBlockAddressInfo &blockInfoAddr) {
mlir::ModuleOp module = getOperation();
mlir::OpBuilder opBuilder(module.getContext());
for (auto &[blockAddOp, blockInfo] :
blockInfoAddr.getUnresolvedBlockAddress()) {
mlir::LLVM::BlockTagOp resolvedLabel =
blockInfoAddr.lookupBlockTag(blockInfo);
assert(resolvedLabel && "expected BlockTagOp to already be emitted");
mlir::FlatSymbolRefAttr fnSym = blockInfo.getFunc();
auto blkAddTag = mlir::LLVM::BlockAddressAttr::get(
opBuilder.getContext(), fnSym, resolvedLabel.getTagAttr());
blockAddOp.setBlockAddrAttr(blkAddTag);
}
blockInfoAddr.clearUnresolvedMap();
}
void ConvertCIRToLLVMPass::processCIRAttrs(mlir::ModuleOp module) {
// Lower the module attributes to LLVM equivalents.
if (mlir::Attribute tripleAttr =
module->getAttr(cir::CIRDialect::getTripleAttrName()))
module->setAttr(mlir::LLVM::LLVMDialect::getTargetTripleAttrName(),
tripleAttr);
if (mlir::Attribute asmAttr =
module->getAttr(cir::CIRDialect::getModuleLevelAsmAttrName()))
module->setAttr(mlir::LLVM::LLVMDialect::getModuleLevelAsmAttrName(),
asmAttr);
}
void ConvertCIRToLLVMPass::runOnOperation() {
llvm::TimeTraceScope scope("Convert CIR to LLVM Pass");
mlir::ModuleOp module = getOperation();
mlir::DataLayout dl(module);
mlir::LLVMTypeConverter converter(&getContext());
prepareTypeConverter(converter, dl);
/// Tracks the state required to lower CIR `LabelOp` and `BlockAddressOp`.
/// Maps labels to their corresponding `BlockTagOp` and keeps bookkeeping
/// of unresolved `BlockAddressOp`s until they are matched with the
/// corresponding `BlockTagOp` in `resolveBlockAddressOp`.
LLVMBlockAddressInfo blockInfoAddr;
/// Cached symbol table collection used by call lowering patterns to avoid
/// repeated O(M) module-wide symbol scans for every call site.
mlir::SymbolTableCollection symbolTables;
mlir::RewritePatternSet patterns(&getContext());
patterns.add<CIRToLLVMBlockAddressOpLowering, CIRToLLVMGlobalOpLowering,
CIRToLLVMLabelOpLowering>(converter, patterns.getContext(), dl,
symbolTables, blockInfoAddr);
patterns.add<
#define GET_LLVM_LOWERING_PATTERNS_LIST
#include "clang/CIR/Dialect/IR/CIRLowering.inc"
#undef GET_LLVM_LOWERING_PATTERNS_LIST
>(converter, patterns.getContext(), dl, symbolTables);
processCIRAttrs(module);
// Collect annotation info from cir.func / cir.global before conversion;
// the annotations attribute is filtered out during FuncOp/GlobalOp lowering.
collectGlobalAnnotations(module);
mlir::ConversionTarget target(getContext());
target.addLegalOp<mlir::ModuleOp>();
target.addLegalDialect<mlir::LLVM::LLVMDialect>();
mlir::configureOpenMPToLLVMConversionLegality(target, converter);
target.addLegalDialect<mlir::omp::OpenMPDialect>();
mlir::populateOpenMPToLLVMConversionPatterns(converter, patterns);
target.addIllegalDialect<mlir::BuiltinDialect, cir::CIRDialect,
mlir::func::FuncDialect>();
llvm::SmallVector<mlir::Operation *> ops;
ops.push_back(module);
cir::collectUnreachable(module, ops);
if (failed(applyPartialConversion(ops, target, std::move(patterns))))
signalPassFailure();
// Drop the cir.ptr-keyed data-layout entries: they drove pointer-width
// queries up to this point, but the LLVM IR exporter rejects CIR types.
if (auto dlSpec = mlir::dyn_cast_or_null<mlir::DataLayoutSpecAttr>(
module->getAttr(mlir::DLTIDialect::kDataLayoutAttrName))) {
llvm::SmallVector<mlir::DataLayoutEntryInterface> kept;
for (mlir::DataLayoutEntryInterface entry : dlSpec.getEntries()) {
if (entry.isTypeEntry() &&
mlir::isa<cir::PointerType>(mlir::cast<mlir::Type>(entry.getKey())))
continue;
kept.push_back(entry);
}
module->setAttr(mlir::DLTIDialect::kDataLayoutAttrName,
mlir::DataLayoutSpecAttr::get(module.getContext(), kept));
}
// Emit the llvm.global_ctors array.
buildCtorDtorList(module, cir::CIRDialect::getGlobalCtorsAttrName(),
"llvm.global_ctors", [](mlir::Attribute attr) {
auto ctorAttr = mlir::cast<cir::GlobalCtorAttr>(attr);
return std::make_pair(ctorAttr.getName(),
ctorAttr.getPriority());
});
// Emit the llvm.global_dtors array.
buildCtorDtorList(module, cir::CIRDialect::getGlobalDtorsAttrName(),
"llvm.global_dtors", [](mlir::Attribute attr) {
auto dtorAttr = mlir::cast<cir::GlobalDtorAttr>(attr);
return std::make_pair(dtorAttr.getName(),
dtorAttr.getPriority());
});
// Emit @llvm.global.annotations from the previously-collected entries.
buildGlobalAnnotationsVar(module);
resolveBlockAddressOp(blockInfoAddr);
}
mlir::LogicalResult CIRToLLVMBrOpLowering::matchAndRewrite(
cir::BrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
rewriter.replaceOpWithNewOp<mlir::LLVM::BrOp>(op, adaptor.getOperands(),
op.getDest());
return mlir::LogicalResult::success();
}
mlir::LogicalResult CIRToLLVMGetMemberOpLowering::matchAndRewrite(
cir::GetMemberOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type llResTy = getTypeConverter()->convertType(op.getType());
mlir::Type pointee = op.getAddrTy().getPointee();
if (mlir::isa<cir::UnionType>(pointee)) {
// Union members share the address space, so we just need a bitcast to
// conform to type-checking.
rewriter.replaceOpWithNewOp<mlir::LLVM::BitcastOp>(op, llResTy,
adaptor.getAddr());
return mlir::success();
}
auto structTy = mlir::cast<cir::StructType>(pointee);
// Since the base address is a pointer to an aggregate, the first offset
// is always zero. The second offset tells us which member it will access.
llvm::SmallVector<mlir::LLVM::GEPArg, 2> offset{0, op.getIndex()};
const mlir::Type elementTy = getTypeConverter()->convertType(structTy);
// Struct member accesses are always inbounds and nuw: the base pointer
// is valid and the member offset is a positive, constant offset within
// the struct layout, so it cannot wrap. This matches LLVM's
// IRBuilder::CreateStructGEP.
mlir::LLVM::GEPNoWrapFlags flags =
mlir::LLVM::GEPNoWrapFlags::inbounds | mlir::LLVM::GEPNoWrapFlags::nuw;
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
op, llResTy, elementTy, adaptor.getAddr(), offset, flags);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMExtractMemberOpLowering::matchAndRewrite(
cir::ExtractMemberOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
std::int64_t indices[1] = {static_cast<std::int64_t>(op.getIndex())};
if (mlir::isa<cir::UnionType>(op.getRecord().getType())) {
op.emitError("cir.extract_member cannot extract member from a union");
return mlir::failure();
}
rewriter.replaceOpWithNewOp<mlir::LLVM::ExtractValueOp>(
op, adaptor.getRecord(), indices);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMInsertMemberOpLowering::matchAndRewrite(
cir::InsertMemberOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
std::int64_t indecies[1] = {static_cast<std::int64_t>(op.getIndex())};
if (mlir::isa<cir::UnionType>(op.getRecord().getType())) {
op.emitError("cir.update_member cannot update member of a union");
return mlir::failure();
}
rewriter.replaceOpWithNewOp<mlir::LLVM::InsertValueOp>(
op, adaptor.getRecord(), adaptor.getValue(), indecies);
return mlir::success();
}
void createLLVMFuncOpIfNotExist(mlir::ConversionPatternRewriter &rewriter,
mlir::SymbolTableCollection &symbolTables,
mlir::Operation *srcOp, llvm::StringRef fnName,
mlir::Type fnTy,
mlir::ArrayAttr argAttrs = nullptr,
mlir::ArrayAttr resAttrs = nullptr) {
mlir::ModuleOp modOp = srcOp->getParentOfType<mlir::ModuleOp>();
mlir::Operation *sourceSymbol = symbolTables.lookupSymbolIn(
modOp, mlir::StringAttr::get(fnTy.getContext(), fnName));
if (!sourceSymbol) {
mlir::OpBuilder::InsertionGuard guard(rewriter);
auto enclosingFnOp = srcOp->getParentOfType<mlir::LLVM::LLVMFuncOp>();
rewriter.setInsertionPoint(enclosingFnOp);
auto fn =
mlir::LLVM::LLVMFuncOp::create(rewriter, srcOp->getLoc(), fnName, fnTy);
if (argAttrs)
fn.setArgAttrsAttr(argAttrs);
if (resAttrs)
fn.setResAttrsAttr(resAttrs);
// Update the symbol table.
symbolTables.getSymbolTable(fn->getParentOp()).insert(fn);
}
}
mlir::LogicalResult CIRToLLVMThrowOpLowering::matchAndRewrite(
cir::ThrowOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Location loc = op.getLoc();
auto voidTy = mlir::LLVM::LLVMVoidType::get(getContext());
if (op.rethrows()) {
auto funcTy = mlir::LLVM::LLVMFunctionType::get(voidTy, {});
// Get or create `declare void @__cxa_rethrow()`
const llvm::StringRef functionName = "__cxa_rethrow";
createLLVMFuncOpIfNotExist(rewriter, symbolTables, op, functionName,
funcTy);
auto cxaRethrow = mlir::LLVM::CallOp::create(
rewriter, loc, mlir::TypeRange{}, functionName);
rewriter.replaceOp(op, cxaRethrow);
return mlir::success();
}
auto llvmPtrTy = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
auto fnTy = mlir::LLVM::LLVMFunctionType::get(
voidTy, {llvmPtrTy, llvmPtrTy, llvmPtrTy});
// Get or create `declare void @__cxa_throw(ptr, ptr, ptr)`
const llvm::StringRef fnName = "__cxa_throw";
createLLVMFuncOpIfNotExist(rewriter, symbolTables, op, fnName, fnTy);
mlir::Value typeInfo = mlir::LLVM::AddressOfOp::create(
rewriter, loc, mlir::LLVM::LLVMPointerType::get(rewriter.getContext()),
adaptor.getTypeInfoAttr());
mlir::Value dtor;
if (op.getDtor()) {
dtor = mlir::LLVM::AddressOfOp::create(rewriter, loc, llvmPtrTy,
adaptor.getDtorAttr());
} else {
dtor = mlir::LLVM::ZeroOp::create(rewriter, loc, llvmPtrTy);
}
auto cxaThrowCall = mlir::LLVM::CallOp::create(
rewriter, loc, mlir::TypeRange{}, fnName,
mlir::ValueRange{adaptor.getExceptionPtr(), typeInfo, dtor});
rewriter.replaceOp(op, cxaThrowCall);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMAllocExceptionOpLowering::matchAndRewrite(
cir::AllocExceptionOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// Get or create `declare ptr @__cxa_allocate_exception(i64)`
StringRef fnName = "__cxa_allocate_exception";
auto llvmPtrTy = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
auto int64Ty = mlir::IntegerType::get(rewriter.getContext(), 64);
auto fnTy = mlir::LLVM::LLVMFunctionType::get(llvmPtrTy, {int64Ty});
createLLVMFuncOpIfNotExist(rewriter, symbolTables, op, fnName, fnTy);
auto exceptionSize = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(),
adaptor.getSizeAttr());
auto allocaExceptionCall = mlir::LLVM::CallOp::create(
rewriter, op.getLoc(), mlir::TypeRange{llvmPtrTy}, fnName,
mlir::ValueRange{exceptionSize});
rewriter.replaceOp(op, allocaExceptionCall);
return mlir::success();
}
static mlir::LLVM::LLVMStructType
getLLVMLandingPadStructTy(mlir::ConversionPatternRewriter &rewriter) {
// Create the landing pad type: struct { ptr, i32 }
mlir::MLIRContext *ctx = rewriter.getContext();
auto llvmPtr = mlir::LLVM::LLVMPointerType::get(ctx);
llvm::SmallVector<mlir::Type> structFields = {llvmPtr, rewriter.getI32Type()};
return mlir::LLVM::LLVMStructType::getLiteral(ctx, structFields);
}
mlir::LogicalResult CIRToLLVMEhInflightOpLowering::matchAndRewrite(
cir::EhInflightOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto llvmFn = op->getParentOfType<mlir::LLVM::LLVMFuncOp>();
assert(llvmFn && "expected LLVM function parent");
mlir::Block *entryBlock = &llvmFn.getRegion().front();
assert(entryBlock->isEntryBlock());
mlir::ArrayAttr catchListAttr = op.getCatchTypeListAttr();
mlir::SmallVector<mlir::Value> catchSymAddrs;
auto llvmPtrTy = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
mlir::Location loc = op.getLoc();
// %landingpad = landingpad { ptr, i32 }
// Note that since llvm.landingpad has to be the first operation on the
// block, any needed value for its operands has to be added somewhere else.
if (catchListAttr) {
// catch ptr @_ZTIi
// catch ptr @_ZTIPKc
for (mlir::Attribute catchAttr : catchListAttr) {
auto symAttr = cast<mlir::FlatSymbolRefAttr>(catchAttr);
// Generate `llvm.mlir.addressof` for each symbol, and place those
// operations in the LLVM function entry basic block.
mlir::OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPointToStart(entryBlock);
mlir::Value addrOp = mlir::LLVM::AddressOfOp::create(
rewriter, loc, llvmPtrTy, symAttr.getValue());
catchSymAddrs.push_back(addrOp);
}
}
// Emit a catch-all clause (catch ptr null) when:
// - The catch_all attribute is set (typed catches + catch-all), or
// - No typed catches and no cleanup (legacy pure catch-all form)
if (op.getCatchAll() || (!catchListAttr && !op.getCleanup())) {
mlir::OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPointToStart(entryBlock);
mlir::Value nullOp = mlir::LLVM::ZeroOp::create(rewriter, loc, llvmPtrTy);
catchSymAddrs.push_back(nullOp);
}
// %slot = extractvalue { ptr, i32 } %x, 0
// %selector = extractvalue { ptr, i32 } %x, 1
mlir::LLVM::LLVMStructType llvmLandingPadStructTy =
getLLVMLandingPadStructTy(rewriter);
auto landingPadOp = mlir::LLVM::LandingpadOp::create(
rewriter, loc, llvmLandingPadStructTy, catchSymAddrs);
// The LLVM cleanup flag is only needed when there is no catch-all handler,
// since catch-all (catch ptr null) already ensures the personality function
// enters the landing pad for all exception types.
if (op.getCleanup() && !op.getCatchAll())
landingPadOp.setCleanup(true);
mlir::Value slot =
mlir::LLVM::ExtractValueOp::create(rewriter, loc, landingPadOp, 0);
mlir::Value selector =
mlir::LLVM::ExtractValueOp::create(rewriter, loc, landingPadOp, 1);
rewriter.replaceOp(op, mlir::ValueRange{slot, selector});
return mlir::success();
}
mlir::LogicalResult CIRToLLVMResumeFlatOpLowering::matchAndRewrite(
cir::ResumeFlatOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// %lpad.val = insertvalue { ptr, i32 } poison, ptr %exception_ptr, 0
// %lpad.val2 = insertvalue { ptr, i32 } %lpad.val, i32 %selector, 1
// resume { ptr, i32 } %lpad.val2
mlir::Type llvmLandingPadStructTy = getLLVMLandingPadStructTy(rewriter);
mlir::Value poison = mlir::LLVM::PoisonOp::create(rewriter, op.getLoc(),
llvmLandingPadStructTy);
SmallVector<int64_t> slotIdx = {0};
mlir::Value slot = mlir::LLVM::InsertValueOp::create(
rewriter, op.getLoc(), poison, adaptor.getExceptionPtr(), slotIdx);
SmallVector<int64_t> selectorIdx = {1};
mlir::Value selector = mlir::LLVM::InsertValueOp::create(
rewriter, op.getLoc(), slot, adaptor.getTypeId(), selectorIdx);
rewriter.replaceOpWithNewOp<mlir::LLVM::ResumeOp>(op, selector);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMEhTypeIdOpLowering::matchAndRewrite(
cir::EhTypeIdOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value addrOp = mlir::LLVM::AddressOfOp::create(
rewriter, op.getLoc(),
mlir::LLVM::LLVMPointerType::get(rewriter.getContext()),
op.getTypeSymAttr());
rewriter.replaceOpWithNewOp<mlir::LLVM::EhTypeidForOp>(
op, rewriter.getI32Type(), addrOp);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMEhSetjmpOpLowering::matchAndRewrite(
cir::EhSetjmpOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type returnType = typeConverter->convertType(op.getType());
mlir::LLVM::CallIntrinsicOp newOp =
createCallLLVMIntrinsicOp(rewriter, op.getLoc(), "llvm.eh.sjlj.setjmp",
returnType, adaptor.getEnv());
rewriter.replaceOp(op, newOp);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMEhLongjmpOpLowering::matchAndRewrite(
cir::EhLongjmpOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
replaceOpWithCallLLVMIntrinsicOp(rewriter, op, "llvm.eh.sjlj.longjmp",
/*resultTy=*/{}, adaptor.getOperands());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMTrapOpLowering::matchAndRewrite(
cir::TrapOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Location loc = op->getLoc();
rewriter.eraseOp(op);
mlir::LLVM::Trap::create(rewriter, loc);
// Note that the call to llvm.trap is not a terminator in LLVM dialect.
// So we must emit an additional llvm.unreachable to terminate the current
// block.
mlir::LLVM::UnreachableOp::create(rewriter, loc);
return mlir::success();
}
static mlir::Value
getValueForVTableSymbol(mlir::Operation *op,
mlir::ConversionPatternRewriter &rewriter,
mlir::SymbolTableCollection &symbolTables,
const mlir::TypeConverter *converter,
mlir::FlatSymbolRefAttr nameAttr, mlir::Type &eltType) {
auto module = op->getParentOfType<mlir::ModuleOp>();
mlir::Operation *symbol = symbolTables.lookupSymbolIn(module, nameAttr);
if (auto llvmSymbol = mlir::dyn_cast<mlir::LLVM::GlobalOp>(symbol)) {
eltType = llvmSymbol.getType();
} else if (auto cirSymbol = mlir::dyn_cast<cir::GlobalOp>(symbol)) {
eltType = converter->convertType(cirSymbol.getSymType());
} else {
op->emitError() << "unexpected symbol type for " << symbol;
return {};
}
return mlir::LLVM::AddressOfOp::create(
rewriter, op->getLoc(),
mlir::LLVM::LLVMPointerType::get(op->getContext()), nameAttr.getValue());
}
mlir::LogicalResult CIRToLLVMVTableAddrPointOpLowering::matchAndRewrite(
cir::VTableAddrPointOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::TypeConverter *converter = getTypeConverter();
mlir::Type targetType = converter->convertType(op.getType());
llvm::SmallVector<mlir::LLVM::GEPArg> offsets;
mlir::Type eltType;
mlir::Value symAddr = getValueForVTableSymbol(
op, rewriter, symbolTables, converter, op.getNameAttr(), eltType);
if (!symAddr)
return op.emitError() << "Unable to get value for vtable symbol";
offsets = llvm::SmallVector<mlir::LLVM::GEPArg>{
0, op.getAddressPointAttr().getIndex(),
op.getAddressPointAttr().getOffset()};
assert(eltType && "Shouldn't ever be missing an eltType here");
mlir::LLVM::GEPNoWrapFlags inboundsNuw =
mlir::LLVM::GEPNoWrapFlags::inbounds | mlir::LLVM::GEPNoWrapFlags::nuw;
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(op, targetType, eltType,
symAddr, offsets, inboundsNuw);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVTableGetVPtrOpLowering::matchAndRewrite(
cir::VTableGetVPtrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// cir.vtable.get_vptr is equivalent to a bitcast from the source object
// pointer to the vptr type. Since the LLVM dialect uses opaque pointers
// we can just replace uses of this operation with the original pointer.
mlir::Value srcVal = adaptor.getSrc();
rewriter.replaceOp(op, srcVal);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVTableGetVirtualFnAddrOpLowering::matchAndRewrite(
cir::VTableGetVirtualFnAddrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type targetType = getTypeConverter()->convertType(op.getType());
auto eltType = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
llvm::SmallVector<mlir::LLVM::GEPArg> offsets =
llvm::SmallVector<mlir::LLVM::GEPArg>{op.getIndex()};
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
op, targetType, eltType, adaptor.getVptr(), offsets,
mlir::LLVM::GEPNoWrapFlags::inbounds);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVTTAddrPointOpLowering::matchAndRewrite(
cir::VTTAddrPointOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
const mlir::Type resultType = getTypeConverter()->convertType(op.getType());
llvm::SmallVector<mlir::LLVM::GEPArg> offsets;
mlir::Type eltType;
mlir::Value llvmAddr = adaptor.getSymAddr();
if (op.getSymAddr()) {
if (op.getOffset() == 0) {
rewriter.replaceOp(op, {llvmAddr});
return mlir::success();
}
offsets.push_back(adaptor.getOffset());
eltType = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
} else {
llvmAddr =
getValueForVTableSymbol(op, rewriter, symbolTables, getTypeConverter(),
op.getNameAttr(), eltType);
assert(eltType && "Shouldn't ever be missing an eltType here");
offsets.push_back(0);
offsets.push_back(adaptor.getOffset());
}
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
op, resultType, eltType, llvmAddr, offsets,
mlir::LLVM::GEPNoWrapFlags::inbounds);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVecCreateOpLowering::matchAndRewrite(
cir::VecCreateOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// Start with an 'undef' value for the vector. Then 'insertelement' for
// each of the vector elements.
const auto vecTy = mlir::cast<cir::VectorType>(op.getType());
const mlir::Type llvmTy = typeConverter->convertType(vecTy);
const mlir::Location loc = op.getLoc();
mlir::Value result = mlir::LLVM::PoisonOp::create(rewriter, loc, llvmTy);
assert(vecTy.getSize() == op.getElements().size() &&
"cir.vec.create op count doesn't match vector type elements count");
for (uint64_t i = 0; i < vecTy.getSize(); ++i) {
const mlir::Value indexValue =
mlir::LLVM::ConstantOp::create(rewriter, loc, rewriter.getI64Type(), i);
result = mlir::LLVM::InsertElementOp::create(
rewriter, loc, result, adaptor.getElements()[i], indexValue);
}
rewriter.replaceOp(op, result);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVecExtractOpLowering::matchAndRewrite(
cir::VecExtractOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
rewriter.replaceOpWithNewOp<mlir::LLVM::ExtractElementOp>(
op, adaptor.getVec(), adaptor.getIndex());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVecInsertOpLowering::matchAndRewrite(
cir::VecInsertOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
rewriter.replaceOpWithNewOp<mlir::LLVM::InsertElementOp>(
op, adaptor.getVec(), adaptor.getValue(), adaptor.getIndex());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVecCmpOpLowering::matchAndRewrite(
cir::VecCmpOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type elementType = elementTypeIfVector(op.getLhs().getType());
mlir::Value bitResult;
if (auto intType = mlir::dyn_cast<cir::IntType>(elementType)) {
bitResult = mlir::LLVM::ICmpOp::create(
rewriter, op.getLoc(),
convertCmpKindToICmpPredicate(op.getKind(), intType.isSigned()),
adaptor.getLhs(), adaptor.getRhs());
} else if (mlir::isa<cir::FPTypeInterface>(elementType)) {
if (cir::FenvAttr fenv = op.getFenvAttr()) {
auto i1VecTy = mlir::VectorType::get(
mlir::cast<cir::VectorType>(op.getLhs().getType()).getSize(),
rewriter.getI1Type());
bitResult = createConstrainedFCmpCall(rewriter, op.getLoc(),
adaptor.getLhs(), adaptor.getRhs(),
op.getKind(), fenv, i1VecTy)
.getResult(0);
} else {
bitResult = mlir::LLVM::FCmpOp::create(
rewriter, op.getLoc(), convertCmpKindToFCmpPredicate(op.getKind()),
adaptor.getLhs(), adaptor.getRhs());
}
} else {
return op.emitError() << "unsupported type for VecCmpOp: " << elementType;
}
// LLVM IR vector comparison returns a vector of i1. This one-bit vector
// must be sign-extended to the correct result type, unless a vector of i1 is
// the type we need.
if (cast<cir::IntType>(cast<cir::VectorType>(op.getType()).getElementType())
.getWidth() > 1)
rewriter.replaceOpWithNewOp<mlir::LLVM::SExtOp>(
op, typeConverter->convertType(op.getType()), bitResult);
else
rewriter.replaceOp(op, bitResult);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVecSplatOpLowering::matchAndRewrite(
cir::VecSplatOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// Vector splat can be implemented with an `insertelement` and a
// `shufflevector`, which is better than an `insertelement` for each
// element in the vector. Start with an undef vector. Insert the value into
// the first element. Then use a `shufflevector` with a mask of all 0 to
// fill out the entire vector with that value.
cir::VectorType vecTy = op.getType();
mlir::Type llvmTy = typeConverter->convertType(vecTy);
mlir::Location loc = op.getLoc();
mlir::Value poison = mlir::LLVM::PoisonOp::create(rewriter, loc, llvmTy);
mlir::Value elementValue = adaptor.getValue();
if (elementValue.getDefiningOp<mlir::LLVM::PoisonOp>()) {
// If the splat value is poison, then we can just use poison value
// for the entire vector.
rewriter.replaceOp(op, poison);
return mlir::success();
}
if (auto constValue = elementValue.getDefiningOp<mlir::LLVM::ConstantOp>()) {
if (auto intAttr = dyn_cast<mlir::IntegerAttr>(constValue.getValue())) {
mlir::DenseIntElementsAttr denseVec = mlir::DenseIntElementsAttr::get(
mlir::cast<mlir::ShapedType>(llvmTy), intAttr.getValue());
rewriter.replaceOpWithNewOp<mlir::LLVM::ConstantOp>(
op, denseVec.getType(), denseVec);
return mlir::success();
}
if (auto fpAttr = dyn_cast<mlir::FloatAttr>(constValue.getValue())) {
mlir::DenseFPElementsAttr denseVec = mlir::DenseFPElementsAttr::get(
mlir::cast<mlir::ShapedType>(llvmTy), fpAttr.getValue());
rewriter.replaceOpWithNewOp<mlir::LLVM::ConstantOp>(
op, denseVec.getType(), denseVec);
return mlir::success();
}
}
mlir::Value indexValue =
mlir::LLVM::ConstantOp::create(rewriter, loc, rewriter.getI64Type(), 0);
mlir::Value oneElement = mlir::LLVM::InsertElementOp::create(
rewriter, loc, poison, elementValue, indexValue);
SmallVector<int32_t> zeroValues(vecTy.getSize(), 0);
rewriter.replaceOpWithNewOp<mlir::LLVM::ShuffleVectorOp>(op, oneElement,
poison, zeroValues);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVecShuffleOpLowering::matchAndRewrite(
cir::VecShuffleOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// LLVM::ShuffleVectorOp takes an ArrayRef of int for the list of indices.
// Convert the ClangIR ArrayAttr of IntAttr constants into a
// SmallVector<int>.
SmallVector<int, 8> indices;
std::transform(
op.getIndices().begin(), op.getIndices().end(),
std::back_inserter(indices), [](mlir::Attribute intAttr) {
return mlir::cast<cir::IntAttr>(intAttr).getValue().getSExtValue();
});
rewriter.replaceOpWithNewOp<mlir::LLVM::ShuffleVectorOp>(
op, adaptor.getVec1(), adaptor.getVec2(), indices);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVecShuffleDynamicOpLowering::matchAndRewrite(
cir::VecShuffleDynamicOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// LLVM IR does not have an operation that corresponds to this form of
// the built-in.
// __builtin_shufflevector(V, I)
// is implemented as this pseudocode, where the for loop is unrolled
// and N is the number of elements:
//
// result = undef
// maskbits = NextPowerOf2(N - 1)
// masked = I & maskbits
// for (i in 0 <= i < N)
// result[i] = V[masked[i]]
mlir::Location loc = op.getLoc();
mlir::Value input = adaptor.getVec();
mlir::Type llvmIndexVecType =
getTypeConverter()->convertType(op.getIndices().getType());
mlir::Type llvmIndexType = getTypeConverter()->convertType(
elementTypeIfVector(op.getIndices().getType()));
uint64_t numElements =
mlir::cast<cir::VectorType>(op.getVec().getType()).getSize();
uint64_t maskBits = llvm::NextPowerOf2(numElements - 1) - 1;
mlir::Value maskValue = mlir::LLVM::ConstantOp::create(
rewriter, loc, llvmIndexType,
rewriter.getIntegerAttr(llvmIndexType, maskBits));
mlir::Value maskVector =
mlir::LLVM::UndefOp::create(rewriter, loc, llvmIndexVecType);
for (uint64_t i = 0; i < numElements; ++i) {
mlir::Value idxValue =
mlir::LLVM::ConstantOp::create(rewriter, loc, rewriter.getI64Type(), i);
maskVector = mlir::LLVM::InsertElementOp::create(rewriter, loc, maskVector,
maskValue, idxValue);
}
mlir::Value maskedIndices = mlir::LLVM::AndOp::create(
rewriter, loc, llvmIndexVecType, adaptor.getIndices(), maskVector);
mlir::Value result = mlir::LLVM::UndefOp::create(
rewriter, loc, getTypeConverter()->convertType(op.getVec().getType()));
for (uint64_t i = 0; i < numElements; ++i) {
mlir::Value iValue =
mlir::LLVM::ConstantOp::create(rewriter, loc, rewriter.getI64Type(), i);
mlir::Value indexValue = mlir::LLVM::ExtractElementOp::create(
rewriter, loc, maskedIndices, iValue);
mlir::Value valueAtIndex =
mlir::LLVM::ExtractElementOp::create(rewriter, loc, input, indexValue);
result = mlir::LLVM::InsertElementOp::create(rewriter, loc, result,
valueAtIndex, iValue);
}
rewriter.replaceOp(op, result);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVecTernaryOpLowering::matchAndRewrite(
cir::VecTernaryOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
// Convert `cond` into a vector of i1, then use that in a `select` op.
mlir::Value bitVec = mlir::LLVM::ICmpOp::create(
rewriter, op.getLoc(), mlir::LLVM::ICmpPredicate::ne, adaptor.getCond(),
mlir::LLVM::ZeroOp::create(
rewriter, op.getCond().getLoc(),
typeConverter->convertType(op.getCond().getType())));
rewriter.replaceOpWithNewOp<mlir::LLVM::SelectOp>(
op, bitVec, adaptor.getLhs(), adaptor.getRhs());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMComplexAddOpLowering::matchAndRewrite(
cir::ComplexAddOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value lhs = adaptor.getLhs();
mlir::Value rhs = adaptor.getRhs();
mlir::Location loc = op.getLoc();
auto complexType = mlir::cast<cir::ComplexType>(op.getLhs().getType());
mlir::Type complexElemTy =
getTypeConverter()->convertType(complexType.getElementType());
auto lhsReal = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, lhs, ArrayRef(int64_t{0}));
auto lhsImag = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, lhs, ArrayRef(int64_t{1}));
auto rhsReal = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, rhs, ArrayRef(int64_t{0}));
auto rhsImag = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, rhs, ArrayRef(int64_t{1}));
mlir::Value newReal;
mlir::Value newImag;
if (complexElemTy.isInteger()) {
newReal = mlir::LLVM::AddOp::create(rewriter, loc, complexElemTy, lhsReal,
rhsReal);
newImag = mlir::LLVM::AddOp::create(rewriter, loc, complexElemTy, lhsImag,
rhsImag);
} else {
assert(!cir::MissingFeatures::fastMathFlags());
assert(!cir::MissingFeatures::fpConstraints());
newReal = mlir::LLVM::FAddOp::create(rewriter, loc, complexElemTy, lhsReal,
rhsReal);
newImag = mlir::LLVM::FAddOp::create(rewriter, loc, complexElemTy, lhsImag,
rhsImag);
}
mlir::Type complexLLVMTy =
getTypeConverter()->convertType(op.getResult().getType());
auto initialComplex =
mlir::LLVM::PoisonOp::create(rewriter, op->getLoc(), complexLLVMTy);
auto realComplex = mlir::LLVM::InsertValueOp::create(
rewriter, op->getLoc(), initialComplex, newReal, ArrayRef(int64_t{0}));
rewriter.replaceOpWithNewOp<mlir::LLVM::InsertValueOp>(
op, realComplex, newImag, ArrayRef(int64_t{1}));
return mlir::success();
}
mlir::LogicalResult CIRToLLVMComplexCreateOpLowering::matchAndRewrite(
cir::ComplexCreateOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type complexLLVMTy =
getTypeConverter()->convertType(op.getResult().getType());
auto initialComplex =
mlir::LLVM::UndefOp::create(rewriter, op->getLoc(), complexLLVMTy);
auto realComplex = mlir::LLVM::InsertValueOp::create(
rewriter, op->getLoc(), initialComplex, adaptor.getReal(),
ArrayRef(int64_t{0}));
auto complex = mlir::LLVM::InsertValueOp::create(
rewriter, op->getLoc(), realComplex, adaptor.getImag(),
ArrayRef(int64_t{1}));
rewriter.replaceOp(op, complex);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMComplexRealOpLowering::matchAndRewrite(
cir::ComplexRealOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type resultLLVMTy = getTypeConverter()->convertType(op.getType());
mlir::Value operand = adaptor.getOperand();
if (mlir::isa<cir::ComplexType>(op.getOperand().getType())) {
operand = mlir::LLVM::ExtractValueOp::create(
rewriter, op.getLoc(), resultLLVMTy, operand,
llvm::ArrayRef<std::int64_t>{0});
}
rewriter.replaceOp(op, operand);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMComplexSubOpLowering::matchAndRewrite(
cir::ComplexSubOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value lhs = adaptor.getLhs();
mlir::Value rhs = adaptor.getRhs();
mlir::Location loc = op.getLoc();
auto complexType = mlir::cast<cir::ComplexType>(op.getLhs().getType());
mlir::Type complexElemTy =
getTypeConverter()->convertType(complexType.getElementType());
auto lhsReal = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, lhs, ArrayRef(int64_t{0}));
auto lhsImag = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, lhs, ArrayRef(int64_t{1}));
auto rhsReal = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, rhs, ArrayRef(int64_t{0}));
auto rhsImag = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, complexElemTy, rhs, ArrayRef(int64_t{1}));
mlir::Value newReal;
mlir::Value newImag;
if (complexElemTy.isInteger()) {
newReal = mlir::LLVM::SubOp::create(rewriter, loc, complexElemTy, lhsReal,
rhsReal);
newImag = mlir::LLVM::SubOp::create(rewriter, loc, complexElemTy, lhsImag,
rhsImag);
} else {
assert(!cir::MissingFeatures::fastMathFlags());
assert(!cir::MissingFeatures::fpConstraints());
newReal = mlir::LLVM::FSubOp::create(rewriter, loc, complexElemTy, lhsReal,
rhsReal);
newImag = mlir::LLVM::FSubOp::create(rewriter, loc, complexElemTy, lhsImag,
rhsImag);
}
mlir::Type complexLLVMTy =
getTypeConverter()->convertType(op.getResult().getType());
auto initialComplex =
mlir::LLVM::PoisonOp::create(rewriter, op->getLoc(), complexLLVMTy);
auto realComplex = mlir::LLVM::InsertValueOp::create(
rewriter, op->getLoc(), initialComplex, newReal, ArrayRef(int64_t{0}));
rewriter.replaceOpWithNewOp<mlir::LLVM::InsertValueOp>(
op, realComplex, newImag, ArrayRef(int64_t{1}));
return mlir::success();
}
mlir::LogicalResult CIRToLLVMComplexImagOpLowering::matchAndRewrite(
cir::ComplexImagOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type resultLLVMTy = getTypeConverter()->convertType(op.getType());
mlir::Value operand = adaptor.getOperand();
mlir::Location loc = op.getLoc();
if (mlir::isa<cir::ComplexType>(op.getOperand().getType())) {
operand = mlir::LLVM::ExtractValueOp::create(
rewriter, loc, resultLLVMTy, operand, llvm::ArrayRef<std::int64_t>{1});
} else {
mlir::TypedAttr zeroAttr = rewriter.getZeroAttr(resultLLVMTy);
operand =
mlir::LLVM::ConstantOp::create(rewriter, loc, resultLLVMTy, zeroAttr);
}
rewriter.replaceOp(op, operand);
return mlir::success();
}
mlir::IntegerType computeBitfieldIntType(mlir::Type storageType,
mlir::MLIRContext *context,
unsigned &storageSize) {
return TypeSwitch<mlir::Type, mlir::IntegerType>(storageType)
.Case<cir::ArrayType>([&](cir::ArrayType atTy) {
storageSize = atTy.getSize() * 8;
return mlir::IntegerType::get(context, storageSize);
})
.Case<cir::IntType>([&](cir::IntType intTy) {
storageSize = intTy.getWidth();
return mlir::IntegerType::get(context, storageSize);
})
.Default([](mlir::Type) -> mlir::IntegerType {
llvm_unreachable(
"Either ArrayType or IntType expected for bitfields storage");
});
}
mlir::LogicalResult CIRToLLVMSetBitfieldOpLowering::matchAndRewrite(
cir::SetBitfieldOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPoint(op);
cir::BitfieldInfoAttr info = op.getBitfieldInfo();
uint64_t size = info.getSize();
uint64_t offset = info.getOffset();
mlir::Type storageType = info.getStorageType();
mlir::MLIRContext *context = storageType.getContext();
unsigned storageSize = 0;
mlir::IntegerType intType =
computeBitfieldIntType(storageType, context, storageSize);
mlir::Value srcVal = createIntCast(rewriter, adaptor.getSrc(), intType);
unsigned srcWidth = storageSize;
mlir::Value resultVal = srcVal;
if (storageSize != size) {
assert(storageSize > size && "Invalid bitfield size.");
mlir::Value val = mlir::LLVM::LoadOp::create(
rewriter, op.getLoc(), intType, adaptor.getAddr(), op.getAlignment(),
op.getIsVolatile());
srcVal =
createAnd(rewriter, srcVal, llvm::APInt::getLowBitsSet(srcWidth, size));
resultVal = srcVal;
srcVal = createShL(rewriter, srcVal, offset);
// Mask out the original value.
val = createAnd(rewriter, val,
~llvm::APInt::getBitsSet(srcWidth, offset, offset + size));
// Or together the unchanged values and the source value.
srcVal = mlir::LLVM::OrOp::create(rewriter, op.getLoc(), val, srcVal);
}
mlir::LLVM::StoreOp::create(rewriter, op.getLoc(), srcVal, adaptor.getAddr(),
op.getAlignment(), op.getIsVolatile());
mlir::Type resultTy = getTypeConverter()->convertType(op.getType());
if (info.getIsSigned()) {
assert(size <= storageSize);
unsigned highBits = storageSize - size;
if (highBits) {
resultVal = createShL(rewriter, resultVal, highBits);
resultVal = createAShR(rewriter, resultVal, highBits);
}
}
resultVal = createIntCast(rewriter, resultVal,
mlir::cast<mlir::IntegerType>(resultTy),
info.getIsSigned());
rewriter.replaceOp(op, resultVal);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMComplexImagPtrOpLowering::matchAndRewrite(
cir::ComplexImagPtrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
cir::PointerType operandTy = op.getOperand().getType();
mlir::Type resultLLVMTy = getTypeConverter()->convertType(op.getType());
mlir::Type elementLLVMTy =
getTypeConverter()->convertType(operandTy.getPointee());
mlir::LLVM::GEPArg gepIndices[2] = {{0}, {1}};
mlir::LLVM::GEPNoWrapFlags inboundsNuw =
mlir::LLVM::GEPNoWrapFlags::inbounds | mlir::LLVM::GEPNoWrapFlags::nuw;
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
op, resultLLVMTy, elementLLVMTy, adaptor.getOperand(), gepIndices,
inboundsNuw);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMComplexRealPtrOpLowering::matchAndRewrite(
cir::ComplexRealPtrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
cir::PointerType operandTy = op.getOperand().getType();
mlir::Type resultLLVMTy = getTypeConverter()->convertType(op.getType());
mlir::Type elementLLVMTy =
getTypeConverter()->convertType(operandTy.getPointee());
mlir::LLVM::GEPArg gepIndices[2] = {0, 0};
mlir::LLVM::GEPNoWrapFlags inboundsNuw =
mlir::LLVM::GEPNoWrapFlags::inbounds | mlir::LLVM::GEPNoWrapFlags::nuw;
rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
op, resultLLVMTy, elementLLVMTy, adaptor.getOperand(), gepIndices,
inboundsNuw);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMGetBitfieldOpLowering::matchAndRewrite(
cir::GetBitfieldOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPoint(op);
cir::BitfieldInfoAttr info = op.getBitfieldInfo();
uint64_t size = info.getSize();
uint64_t offset = info.getOffset();
mlir::Type storageType = info.getStorageType();
mlir::MLIRContext *context = storageType.getContext();
unsigned storageSize = 0;
mlir::IntegerType intType =
computeBitfieldIntType(storageType, context, storageSize);
mlir::Value val = mlir::LLVM::LoadOp::create(
rewriter, op.getLoc(), intType, adaptor.getAddr(), op.getAlignment(),
op.getIsVolatile());
val = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), intType, val);
if (info.getIsSigned()) {
assert(static_cast<unsigned>(offset + size) <= storageSize);
unsigned highBits = storageSize - offset - size;
val = createShL(rewriter, val, highBits);
val = createAShR(rewriter, val, offset + highBits);
} else {
val = createLShR(rewriter, val, offset);
if (static_cast<unsigned>(offset) + size < storageSize)
val = createAnd(rewriter, val,
llvm::APInt::getLowBitsSet(storageSize, size));
}
mlir::Type resTy = getTypeConverter()->convertType(op.getType());
mlir::Value newOp = createIntCast(
rewriter, val, mlir::cast<mlir::IntegerType>(resTy), info.getIsSigned());
rewriter.replaceOp(op, newOp);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMInlineAsmOpLowering::matchAndRewrite(
cir::InlineAsmOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type llResTy;
if (op.getNumResults())
llResTy = getTypeConverter()->convertType(op.getType(0));
cir::AsmFlavor dialect = op.getAsmFlavor();
mlir::LLVM::AsmDialect llDialect = dialect == cir::AsmFlavor::x86_att
? mlir::LLVM::AsmDialect::AD_ATT
: mlir::LLVM::AsmDialect::AD_Intel;
SmallVector<mlir::Attribute> opAttrs;
StringRef llvmAttrName = mlir::LLVM::InlineAsmOp::getElementTypeAttrName();
// this is for the lowering to LLVM from LLVM dialect. Otherwise, if we
// don't have the result (i.e. void type as a result of operation), the
// element type attribute will be attached to the whole instruction, but not
// to the operand
if (!op.getNumResults())
opAttrs.push_back(mlir::Attribute());
SmallVector<mlir::Value> llvmOperands;
SmallVector<mlir::Value> cirOperands;
for (auto const &[llvmOp, cirOp] :
zip(adaptor.getAsmOperands(), op.getAsmOperands())) {
append_range(llvmOperands, llvmOp);
append_range(cirOperands, cirOp);
}
// so far we infer the llvm dialect element type attr from
// CIR operand type.
for (auto const &[cirOpAttr, cirOp] :
zip(op.getOperandAttrs(), cirOperands)) {
if (!mlir::isa<mlir::UnitAttr>(cirOpAttr)) {
opAttrs.push_back(mlir::Attribute());
continue;
}
llvm::SmallVector<mlir::NamedAttribute, 1> attrs;
cir::PointerType typ = mlir::cast<cir::PointerType>(cirOp.getType());
mlir::TypeAttr typAttr = mlir::TypeAttr::get(convertTypeForMemory(
*getTypeConverter(), dataLayout, typ.getPointee()));
attrs.push_back(rewriter.getNamedAttr(llvmAttrName, typAttr));
mlir::DictionaryAttr newDict = rewriter.getDictionaryAttr(attrs);
opAttrs.push_back(newDict);
}
rewriter.replaceOpWithNewOp<mlir::LLVM::InlineAsmOp>(
op, llResTy, llvmOperands, op.getAsmStringAttr(), op.getConstraintsAttr(),
op.getSideEffectsAttr(),
/*is_align_stack*/ mlir::UnitAttr(),
/*tail_call_kind*/
mlir::LLVM::TailCallKindAttr::get(
getContext(), mlir::LLVM::tailcallkind::TailCallKind::None),
mlir::LLVM::AsmDialectAttr::get(getContext(), llDialect),
rewriter.getArrayAttr(opAttrs));
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVAStartOpLowering::matchAndRewrite(
cir::VAStartOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto opaquePtr = mlir::LLVM::LLVMPointerType::get(getContext());
auto vaList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
adaptor.getArgList());
rewriter.replaceOpWithNewOp<mlir::LLVM::VaStartOp>(op, vaList);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVAEndOpLowering::matchAndRewrite(
cir::VAEndOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto opaquePtr = mlir::LLVM::LLVMPointerType::get(getContext());
auto vaList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
adaptor.getArgList());
rewriter.replaceOpWithNewOp<mlir::LLVM::VaEndOp>(op, vaList);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVACopyOpLowering::matchAndRewrite(
cir::VACopyOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto opaquePtr = mlir::LLVM::LLVMPointerType::get(getContext());
auto dstList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
adaptor.getDstList());
auto srcList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
adaptor.getSrcList());
rewriter.replaceOpWithNewOp<mlir::LLVM::VaCopyOp>(op, dstList, srcList);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMVAArgOpLowering::matchAndRewrite(
cir::VAArgOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
assert(!cir::MissingFeatures::vaArgABILowering());
auto opaquePtr = mlir::LLVM::LLVMPointerType::get(getContext());
auto vaList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
adaptor.getArgList());
mlir::Type llvmType =
getTypeConverter()->convertType(op->getResultTypes().front());
if (!llvmType)
return mlir::failure();
rewriter.replaceOpWithNewOp<mlir::LLVM::VaArgOp>(op, llvmType, vaList);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMLabelOpLowering::matchAndRewrite(
cir::LabelOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::MLIRContext *ctx = rewriter.getContext();
mlir::Block *block = op->getBlock();
// A BlockTagOp cannot reside in the entry block. The address of the entry
// block cannot be taken
if (block->isEntryBlock()) {
mlir::Block *newBlock =
rewriter.splitBlock(op->getBlock(), mlir::Block::iterator(op));
rewriter.setInsertionPointToEnd(block);
mlir::LLVM::BrOp::create(rewriter, op.getLoc(), newBlock);
}
auto tagAttr =
mlir::LLVM::BlockTagAttr::get(ctx, blockInfoAddr.getTagIndex());
rewriter.setInsertionPoint(op);
auto blockTagOp =
mlir::LLVM::BlockTagOp::create(rewriter, op->getLoc(), tagAttr);
mlir::LLVM::LLVMFuncOp func = op->getParentOfType<mlir::LLVM::LLVMFuncOp>();
auto blockInfoAttr =
cir::BlockAddrInfoAttr::get(ctx, func.getSymName(), op.getLabel());
blockInfoAddr.mapBlockTag(blockInfoAttr, blockTagOp);
rewriter.eraseOp(op);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMBlockAddressOpLowering::matchAndRewrite(
cir::BlockAddressOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::MLIRContext *ctx = rewriter.getContext();
mlir::LLVM::BlockTagOp matchLabel =
blockInfoAddr.lookupBlockTag(op.getBlockAddrInfoAttr());
mlir::LLVM::BlockTagAttr tagAttr;
if (!matchLabel)
// If the BlockTagOp has not been emitted yet, use a placeholder.
// This will later be replaced with the correct tag index during
// `resolveBlockAddressOp`.
tagAttr = {};
else
tagAttr = matchLabel.getTag();
auto blkAddr = mlir::LLVM::BlockAddressAttr::get(
rewriter.getContext(), op.getBlockAddrInfoAttr().getFunc(), tagAttr);
rewriter.setInsertionPoint(op);
auto newOp = mlir::LLVM::BlockAddressOp::create(
rewriter, op.getLoc(), mlir::LLVM::LLVMPointerType::get(ctx), blkAddr);
if (!matchLabel)
blockInfoAddr.addUnresolvedBlockAddress(newOp, op.getBlockAddrInfoAttr());
rewriter.replaceOp(op, newOp);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMIndirectBrOpLowering::matchAndRewrite(
cir::IndirectBrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value targetAddr = adaptor.getAddr();
// If the poison attribute is set, use llvm.mlir.poison as the address.
// This happens when the block has no predecessors and is essentially
// unreachable. Do NOT erase the block argument directly, as that violates
// the MLIR dialect conversion framework contract (the framework tracks block
// arguments and will clean them up). A block with no predecessors simply
// produces no PHI node.
if (op.getPoison()) {
auto llvmPtrType = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
targetAddr =
mlir::LLVM::PoisonOp::create(rewriter, op->getLoc(), llvmPtrType);
}
rewriter.replaceOpWithNewOp<mlir::LLVM::IndirectBrOp>(
op, targetAddr, adaptor.getSuccOperands(), op.getSuccessors());
return mlir::success();
}
mlir::LogicalResult CIRToLLVMTokenNoneOpLowering::matchAndRewrite(
cir::TokenNoneOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return mlir::failure();
}
mlir::LogicalResult CIRToLLVMCoroFreeOpLowering::matchAndRewrite(
cir::CoroFreeOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return mlir::failure();
}
mlir::LogicalResult CIRToLLVMCoroEndOpLowering::matchAndRewrite(
cir::CoroEndOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return mlir::failure();
}
mlir::LogicalResult CIRToLLVMCoroAllocOpLowering::matchAndRewrite(
cir::CoroAllocOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return mlir::failure();
}
mlir::LogicalResult CIRToLLVMCoroBeginOpLowering::matchAndRewrite(
cir::CoroBeginOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return mlir::failure();
}
mlir::LogicalResult CIRToLLVMCoroIdOpLowering::matchAndRewrite(
cir::CoroIdOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return mlir::failure();
}
mlir::LogicalResult CIRToLLVMCoroSizeOpLowering::matchAndRewrite(
cir::CoroSizeOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
return mlir::failure();
}
mlir::LogicalResult CIRToLLVMCpuIdOpLowering::matchAndRewrite(
cir::CpuIdOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Type i32Ty = rewriter.getI32Type();
mlir::Type i64Ty = rewriter.getI64Type();
mlir::Type i32PtrTy = mlir::LLVM::LLVMPointerType::get(i32Ty.getContext(), 0);
mlir::Type cpuidRetTy = mlir::LLVM::LLVMStructType::getLiteral(
rewriter.getContext(), {i32Ty, i32Ty, i32Ty, i32Ty});
mlir::Value functionId = adaptor.getFunctionId();
mlir::Value subFunctionId = adaptor.getSubFunctionId();
StringRef asmString, constraints;
mlir::ModuleOp moduleOp = op->getParentOfType<mlir::ModuleOp>();
llvm::Triple triple(
mlir::cast<mlir::StringAttr>(
moduleOp->getAttr(cir::CIRDialect::getTripleAttrName()))
.getValue());
if (triple.getArch() == llvm::Triple::x86) {
asmString = "cpuid";
constraints = "={ax},={bx},={cx},={dx},{ax},{cx}";
} else {
// x86-64 uses %rbx as the base register, so preserve it.
asmString = "xchgq %rbx, ${1:q}\n"
"cpuid\n"
"xchgq %rbx, ${1:q}";
constraints = "={ax},=r,={cx},={dx},0,2";
}
mlir::Value inlineAsm =
mlir::LLVM::InlineAsmOp::create(
rewriter, op.getLoc(), cpuidRetTy, {functionId, subFunctionId},
rewriter.getStringAttr(asmString),
rewriter.getStringAttr(constraints),
/*has_side_effects=*/mlir::UnitAttr{},
/*is_align_stack=*/mlir::UnitAttr{},
/*tail_call_kind=*/mlir::LLVM::TailCallKindAttr{},
/*asm_dialect=*/mlir::LLVM::AsmDialectAttr{},
/*operand_attrs=*/mlir::ArrayAttr{})
.getResult(0);
mlir::Value basePtr = adaptor.getCpuInfo();
mlir::DataLayout layout(op->getParentOfType<mlir::ModuleOp>());
unsigned alignment = layout.getTypeABIAlignment(i32Ty);
for (unsigned i = 0; i < 4; i++) {
mlir::Value extracted =
mlir::LLVM::ExtractValueOp::create(rewriter, op.getLoc(), inlineAsm, i)
.getResult();
mlir::Value index = mlir::LLVM::ConstantOp::create(
rewriter, op.getLoc(), i64Ty, rewriter.getI64IntegerAttr(i));
llvm::SmallVector<mlir::Value, 1> gepIndices = {index};
mlir::Value storePtr = mlir::LLVM::GEPOp::create(
rewriter, op.getLoc(), i32PtrTy, i32Ty, basePtr,
gepIndices, mlir::LLVM::GEPNoWrapFlags::none)
.getResult();
mlir::LLVM::StoreOp::create(rewriter, op.getLoc(), extracted, storePtr,
alignment);
}
rewriter.eraseOp(op);
return mlir::success();
}
mlir::LogicalResult CIRToLLVMMemChrOpLowering::matchAndRewrite(
cir::MemChrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
auto llvmPtrTy = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
mlir::Type srcTy = getTypeConverter()->convertType(op.getSrc().getType());
mlir::Type patternTy =
getTypeConverter()->convertType(op.getPattern().getType());
mlir::Type lenTy = getTypeConverter()->convertType(op.getLen().getType());
auto fnTy =
mlir::LLVM::LLVMFunctionType::get(llvmPtrTy, {srcTy, patternTy, lenTy},
/*isVarArg=*/false);
llvm::StringRef fnName = "memchr";
mlir::Builder b(rewriter.getContext());
mlir::NamedAttribute noundefAttr =
b.getNamedAttr("llvm.noundef", b.getUnitAttr());
mlir::DictionaryAttr noundefDict = mlir::DictionaryAttr::get(
rewriter.getContext(), llvm::ArrayRef(noundefAttr));
SmallVector<mlir::Attribute> argAttrVec(3, noundefDict);
mlir::ArrayAttr argAttrs =
mlir::ArrayAttr::get(rewriter.getContext(), argAttrVec);
createLLVMFuncOpIfNotExist(rewriter, symbolTables, op, fnName, fnTy,
argAttrs);
mlir::LLVM::CallOp newCall = rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(
op, mlir::TypeRange{llvmPtrTy}, fnName,
mlir::ValueRange{adaptor.getSrc(), adaptor.getPattern(),
adaptor.getLen()});
newCall.setArgAttrsAttr(argAttrs);
return mlir::success();
}
// Function to do the clear-padding operation. This is a faithful translation of
// CGBuiltin.cpp's ClearPadding function.
static void clearPadding(mlir::ConversionPatternRewriter &rewriter,
mlir::Location loc, mlir::Value inputPtr,
uint64_t baseAlignment,
cir::OffsetPairAttr paddingAttr) {
// FIXME(cir): Classic-codegen pulls this from the data layout, but MLIR
// DataLayout just assumes it is 8 everywhere (as does all our lowering).
// Clang doesn't support any non-8-bit-CHAR_BIT architectures, but keeping
// this separate so we can use it next time.
uint64_t charWidth = 8;
mlir::Type i8Ty = rewriter.getI8Type();
mlir::Type ptrTy = mlir::LLVM::LLVMPointerType::get(i8Ty.getContext());
auto startByte = paddingAttr.getStart() / charWidth;
auto startBit = paddingAttr.getStart() % charWidth;
auto endByte = paddingAttr.getEnd() / charWidth;
auto endBit = paddingAttr.getEnd() % charWidth;
if (startByte == endByte) {
// Interval is within a single byte
auto index = mlir::LLVM::ConstantOp::create(
rewriter, loc, rewriter.getI32Type(), startByte);
auto element = mlir::LLVM::GEPOp::create(rewriter, loc, ptrTy, i8Ty,
inputPtr, {index});
uint64_t adjustedAlignment = llvm::MinAlign(baseAlignment, startByte);
auto value = mlir::LLVM::LoadOp::create(rewriter, loc, i8Ty, element,
adjustedAlignment);
// Create mask to clear bits within the byte
// We want to clear bits from StartBit to EndBit-1
uint8_t bitsToClear = ((1 << endBit) - 1) & ~((1 << startBit) - 1);
uint8_t bitsToKeep = ~bitsToClear;
auto maskValue =
mlir::LLVM::ConstantOp::create(rewriter, loc, i8Ty, bitsToKeep);
auto newValue = mlir::LLVM::AndOp::create(rewriter, loc, value, maskValue);
mlir::LLVM::StoreOp::create(rewriter, loc, newValue, element,
adjustedAlignment);
} else {
// Handle the start byte
if (startBit != 0) {
auto index = mlir::LLVM::ConstantOp::create(
rewriter, loc, rewriter.getI32Type(), startByte);
auto element = mlir::LLVM::GEPOp::create(rewriter, loc, ptrTy, i8Ty,
inputPtr, {index});
uint64_t adjustedAlignment = llvm::MinAlign(baseAlignment, startByte);
auto value = mlir::LLVM::LoadOp::create(rewriter, loc, i8Ty, element,
adjustedAlignment);
uint8_t bitsToClear = ((1 << (charWidth - startBit)) - 1) << startBit;
uint8_t bitsToKeep = ~bitsToClear;
auto maskValue =
mlir::LLVM::ConstantOp::create(rewriter, loc, i8Ty, bitsToKeep);
auto newValue =
mlir::LLVM::AndOp::create(rewriter, loc, value, maskValue);
mlir::LLVM::StoreOp::create(rewriter, loc, newValue, element,
adjustedAlignment);
++startByte;
}
// Handle full bytes in the middle
for (auto offset = startByte; offset < endByte; ++offset) {
auto index = mlir::LLVM::ConstantOp::create(
rewriter, loc, rewriter.getI32Type(), offset);
auto element = mlir::LLVM::GEPOp::create(rewriter, loc, ptrTy, i8Ty,
inputPtr, {index});
uint64_t adjustedAlignment = llvm::MinAlign(baseAlignment, offset);
auto zero = mlir::LLVM::ConstantOp::create(rewriter, loc, i8Ty, 0);
mlir::LLVM::StoreOp::create(rewriter, loc, zero, element,
adjustedAlignment);
}
// Handle the end byte
if (endBit != 0) {
auto index = mlir::LLVM::ConstantOp::create(
rewriter, loc, rewriter.getI32Type(), endByte);
auto element = mlir::LLVM::GEPOp::create(rewriter, loc, ptrTy, i8Ty,
inputPtr, {index});
uint64_t adjustedAlignment = llvm::MinAlign(baseAlignment, endByte);
auto value = mlir::LLVM::LoadOp::create(rewriter, loc, i8Ty, element,
adjustedAlignment);
uint8_t bitsToClear = (1 << endBit) - 1;
uint8_t bitsToKeep = ~bitsToClear;
auto maskValue =
mlir::LLVM::ConstantOp::create(rewriter, loc, i8Ty, bitsToKeep);
auto newValue =
mlir::LLVM::AndOp::create(rewriter, loc, value, maskValue);
mlir::LLVM::StoreOp::create(rewriter, loc, newValue, element,
adjustedAlignment);
}
}
}
mlir::LogicalResult CIRToLLVMClearPaddingOpLowering::matchAndRewrite(
cir::ClearPaddingOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value inputPtr = adaptor.getArg();
for (mlir::Attribute attr : op.getPadding())
clearPadding(rewriter, op.getLoc(), inputPtr, op.getAlignment(),
cast<cir::OffsetPairAttr>(attr));
rewriter.eraseOp(op);
return mlir::success();
}
std::unique_ptr<mlir::Pass> createConvertCIRToLLVMPass() {
return std::make_unique<ConvertCIRToLLVMPass>();
}
void populateCIRToLLVMPasses(mlir::OpPassManager &pm, bool enableOpenMP) {
mlir::populateCIRPreLoweringPasses(pm);
if (enableOpenMP)
pm.addPass(mlir::omp::createMarkDeclareTargetPass());
pm.addPass(createConvertCIRToLLVMPass());
if (enableOpenMP)
pm.addPass(mlir::omp::createHostOpFilteringPass());
}
std::unique_ptr<llvm::Module>
lowerDirectlyFromCIRToLLVMIR(mlir::ModuleOp mlirModule, LLVMContext &llvmCtx,
bool enableOpenMP, StringRef mlirSaveTempsOutFile,
llvm::vfs::FileSystem *fs) {
llvm::TimeTraceScope scope("lower from CIR to LLVM directly");
mlir::MLIRContext *mlirCtx = mlirModule.getContext();
mlir::PassManager pm(mlirCtx);
populateCIRToLLVMPasses(pm, enableOpenMP);
(void)mlir::applyPassManagerCLOptions(pm);
if (mlir::failed(pm.run(mlirModule))) {
// FIXME: Handle any errors where they occurs and return a nullptr here.
report_fatal_error(
"The pass manager failed to lower CIR to LLVMIR dialect!");
}
if (!mlirSaveTempsOutFile.empty()) {
std::error_code ec;
llvm::raw_fd_ostream out(mlirSaveTempsOutFile, ec);
if (!ec)
mlirModule->print(out);
}
mlir::registerBuiltinDialectTranslation(*mlirCtx);
mlir::registerLLVMDialectTranslation(*mlirCtx);
mlir::registerOpenMPDialectTranslation(*mlirCtx);
mlir::registerCIRDialectTranslation(*mlirCtx);
llvm::TimeTraceScope translateScope("translateModuleToLLVMIR");
StringRef moduleName = mlirModule.getName().value_or("CIRToLLVMModule");
std::unique_ptr<llvm::Module> llvmModule = mlir::translateModuleToLLVMIR(
mlirModule, llvmCtx, moduleName, /*disableVerification=*/false, fs);
if (!llvmModule) {
// FIXME: Handle any errors where they occurs and return a nullptr here.
report_fatal_error("Lowering from LLVMIR dialect to llvm IR failed!");
}
return llvmModule;
}
} // namespace direct
} // namespace cir