//====- 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 #include #include #include "mlir/Conversion/LLVMCommon/TypeConverter.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/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/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/LoweringHelpers.h" #include "clang/CIR/MissingFeatures.h" #include "clang/CIR/Passes.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/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(type) .Case( [](auto p) { return p.getElementType(); }) .Default([](mlir::Type p) { return p; }); } } // namespace /// 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(type)) { return mlir::IntegerType::get(type.getContext(), dataLayout.getTypeSizeInBits(type)); } return converter.convertType(type); } static mlir::Value createIntCast(mlir::OpBuilder &bld, mlir::Value src, mlir::IntegerType dstTy, bool isSigned = false) { mlir::Type srcTy = src.getType(); assert(mlir::isa(srcTy)); unsigned srcWidth = mlir::cast(srcTy).getWidth(); unsigned dstWidth = mlir::cast(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); } 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; } } /// 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, 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(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()); } 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(origType)) { // Create zext of value from i1 to i8 mlir::IntegerType memType = rewriter.getIntegerType(dataLayout.getTypeSizeInBits(boolTy)); return createIntCast(rewriter, value, memType); } 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"); } mlir::LogicalResult CIRToLLVMCopyOpLowering::matchAndRewrite( cir::CopyOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { mlir::DataLayout layout(op->getParentOfType()); const mlir::Value length = mlir::LLVM::ConstantOp::create( rewriter, op.getLoc(), rewriter.getI64Type(), op.getCopySizeInBytes(layout)); assert(!cir::MissingFeatures::aggValueSlotVolatile()); rewriter.replaceOpWithNewOp( op, adaptor.getDst(), adaptor.getSrc(), length, op.getIsVolatile()); return mlir::success(); } mlir::LogicalResult CIRToLLVMMemCpyOpLowering::matchAndRewrite( cir::MemCpyOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp( 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( 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( op, adaptor.getDst(), adaptor.getVal(), adaptor.getLen(), /*isVolatile=*/false); if (op.getAlignmentAttr()) { // Construct a list full of empty attributes. llvm::SmallVector attrs{memset.getNumOperands(), rewriter.getDictionaryAttr({})}; llvm::SmallVector 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, const mlir::TypeConverter *converter) : parentOp(parentOp), rewriter(rewriter), converter(converter) {} #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; const mlir::TypeConverter *converter; }; /// Switches on the type of attribute and calls the appropriate conversion. mlir::Value lowerCirAttrAsValue(mlir::Operation *parentOp, const mlir::Attribute attr, mlir::ConversionPatternRewriter &rewriter, const mlir::TypeConverter *converter) { CIRAttrToValue valueConverter(parentOp, rewriter, converter); 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) { auto intrinsicNameAttr = mlir::StringAttr::get(rewriter.getContext(), intrinsicName); return mlir::LLVM::CallIntrinsicOp::create(rewriter, loc, resultTy, intrinsicNameAttr, operands); } static mlir::LLVM::CallIntrinsicOp replaceOpWithCallLLVMIntrinsicOp( mlir::ConversionPatternRewriter &rewriter, mlir::Operation *op, const llvm::Twine &intrinsicName, mlir::Type resultTy, mlir::ValueRange operands) { mlir::LLVM::CallIntrinsicOp callIntrinOp = createCallLLVMIntrinsicOp( rewriter, op->getLoc(), intrinsicName, resultTy, operands); rewriter.replaceOp(op, callIntrinOp.getOperation()); return callIntrinOp; } mlir::LogicalResult CIRToLLVMLLVMIntrinsicCallOpLowering::matchAndRewrite( cir::LLVMIntrinsicCallOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { mlir::Type 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::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(); return mlir::LLVM::ConstantOp::create( rewriter, loc, converter->convertType(intAttr.getType()), intAttr.getValue()); } /// 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(complexAttr.getType()); mlir::Type complexElemTy = complexType.getElementType(); mlir::Type complexElemLLVMTy = converter->convertType(complexElemTy); mlir::Attribute components[2]; if (const auto intType = mlir::dyn_cast(complexElemTy)) { components[0] = rewriter.getIntegerAttr( complexElemLLVMTy, mlir::cast(complexAttr.getReal()).getValue()); components[1] = rewriter.getIntegerAttr( complexElemLLVMTy, mlir::cast(complexAttr.getImag()).getValue()); } else { components[0] = rewriter.getFloatAttr( complexElemLLVMTy, mlir::cast(complexAttr.getReal()).getValue()); components[1] = rewriter.getFloatAttr( complexElemLLVMTy, mlir::cast(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::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); } // ConstArrayAttr visitor mlir::Value CIRAttrToValue::visitCirAttr(cir::ConstArrayAttr attr) { mlir::Type llvmTy = converter->convertType(attr.getType()); mlir::Location loc = parentOp->getLoc(); mlir::Value result; 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(attr.getElts())) { for (auto [idx, elt] : llvm::enumerate(arrayAttr)) { mlir::DataLayout dataLayout(parentOp->getParentOfType()); mlir::Value init = visit(elt); result = mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init, idx); } } else if (auto strAttr = mlir::dyn_cast(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(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) { const mlir::Type llvmTy = converter->convertType(constRecord.getType()); 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 mlirValues; for (const mlir::Attribute elementAttr : attr.getElts()) { mlir::Attribute mlirAttr; if (auto intAttr = mlir::dyn_cast(elementAttr)) { mlirAttr = rewriter.getIntegerAttr( converter->convertType(intAttr.getType()), intAttr.getValue()); } else if (auto floatAttr = mlir::dyn_cast(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(llvmTy), mlirValues)); } // GlobalViewAttr visitor. mlir::Value CIRAttrToValue::visitCirAttr(cir::GlobalViewAttr globalAttr) { auto moduleOp = parentOp->getParentOfType(); mlir::DataLayout dataLayout(moduleOp); mlir::Type sourceType; assert(!cir::MissingFeatures::addressSpace()); llvm::StringRef symName; mlir::Operation *sourceSymbol = mlir::SymbolTable::lookupSymbolIn(moduleOp, globalAttr.getSymbol()); if (auto llvmSymbol = dyn_cast(sourceSymbol)) { sourceType = llvmSymbol.getType(); symName = llvmSymbol.getSymName(); } else if (auto cirSymbol = dyn_cast(sourceSymbol)) { sourceType = convertTypeForMemory(*converter, dataLayout, cirSymbol.getSymType()); symName = cirSymbol.getSymName(); } else if (auto llvmFun = dyn_cast(sourceSymbol)) { sourceType = llvmFun.getFunctionType(); symName = llvmFun.getSymName(); } else if (auto fun = dyn_cast(sourceSymbol)) { sourceType = converter->convertType(fun.getFunctionType()); symName = fun.getSymName(); } else if (auto alias = dyn_cast(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()), symName); if (globalAttr.getIndices()) { llvm::SmallVector indices; if (mlir::isa( sourceType)) indices.push_back(0); for (mlir::Attribute idx : globalAttr.getIndices()) { auto intAttr = mlir::cast(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(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(globalAttr.getType())) { mlir::Type llvmEltTy = convertTypeForMemory(*converter, dataLayout, ptrTy.getPointee()); if (llvmEltTy == sourceType) return addrOp; mlir::Type llvmDstTy = converter->convertType(globalAttr.getType()); return mlir::LLVM::BitcastOp::create(rewriter, parentOp->getLoc(), llvmDstTy, 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(); return mlir::LLVM::UndefOp::create( rewriter, loc, converter->convertType(undefAttr.getType())); } /// PoisonAttr visitor. mlir::Value CIRAttrToValue::visitCirAttr(cir::PoisonAttr poisonAttr) { mlir::Location loc = parentOp->getLoc(); return mlir::LLVM::PoisonOp::create( rewriter, loc, converter->convertType(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(); return mlir::LLVM::ZeroOp::create(rewriter, loc, converter->convertType(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(attr) .Case( [&](auto attrT) { return visitCirAttr(attrT); }) .Default([&](auto attrT) { return mlir::Attribute(); }); } mlir::Attribute visitCirAttr(cir::IntAttr attr) { return rewriter.getIntegerAttr(llvmType, attr.getValue()); } 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> { void getDependentDialects(mlir::DialectRegistry ®istry) const override { registry.insert(); } void runOnOperation() final; void processCIRAttrs(mlir::ModuleOp module); void resolveBlockAddressOp(LLVMBlockAddressInfo &blockInfoAddr); StringRef getDescription() const override { return "Convert the prepared CIR dialect module to LLVM dialect"; } StringRef getArgument() const override { return "cir-flat-to-llvm"; } }; 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( op, retTy, src, static_cast(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()); int width = layout.getTypeSizeInBits(op.getInput().getType()); if (auto longDoubleType = mlir::dyn_cast(op.getInput().getType())) { if (mlir::isa(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 { auto cond = adaptor.getPredicate(); rewriter.replaceOpWithNewOp(op, cond); return mlir::success(); } mlir::LogicalResult CIRToLLVMAssumeAlignedOpLowering::matchAndRewrite( cir::AssumeAlignedOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { SmallVector opBundleArgs{adaptor.getPointer()}; auto alignment = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(), adaptor.getAlignmentAttr()); opBundleArgs.push_back(alignment); if (mlir::Value offset = adaptor.getOffset()) opBundleArgs.push_back(offset); auto cond = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(), rewriter.getI1Type(), 1); mlir::LLVM::AssumeOp::create(rewriter, op.getLoc(), cond, "align", opBundleArgs); // The llvm.assume operation does not have a result, so we need to replace // all uses of this cir.assume_aligned operation with the input ptr itself. rewriter.replaceOp(op, adaptor.getPointer()); return mlir::success(); } mlir::LogicalResult CIRToLLVMAssumeSepStorageOpLowering::matchAndRewrite( cir::AssumeSepStorageOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { auto cond = mlir::LLVM::ConstantOp::create(rewriter, op.getLoc(), rewriter.getI1Type(), 1); rewriter.replaceOpWithNewOp( op, cond, mlir::LLVM::AssumeSeparateStorageTag{}, adaptor.getPtr1(), adaptor.getPtr2()); return mlir::success(); } static mlir::LLVM::AtomicOrdering getLLVMMemOrder(std::optional 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) { return syncScope == cir::SyncScopeKind::SingleThread ? "singlethread" : ""; } static std::optional getLLVMSyncScope(std::optional 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( 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; } 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: llvm_unreachable("uinc_wrap and udec_wrap 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 atomicOperands = {rmwVal, adaptor.getVal()}; SmallVector 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(op.getVal().getType())) { isInt = true; isSignedInt = intTy.isSigned(); } else if (mlir::isa( 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 CIRToLLVMBitReverseOpLowering::matchAndRewrite( cir::BitReverseOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp(op, adaptor.getInput()); return mlir::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( brOp, i1Condition, brOp.getDestTrue(), adaptor.getDestOperandsTrue(), brOp.getDestFalse(), adaptor.getDestOperandsFalse()); return mlir::success(); } mlir::LogicalResult CIRToLLVMByteSwapOpLowering::matchAndRewrite( cir::ByteSwapOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp(op, adaptor.getInput()); return mlir::LogicalResult::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(castOp.getType()); mlir::Value sourceValue = adaptor.getSrc(); mlir::Type targetType = convertTy(ptrTy); mlir::Type elementTy = convertTypeForMemory(*getTypeConverter(), dataLayout, ptrTy.getPointee()); llvm::SmallVector offset{0}; rewriter.replaceOpWithNewOp( castOp, targetType, elementTy, sourceValue, offset); break; } case cir::CastKind::int_to_bool: { mlir::Value llvmSrcVal = adaptor.getSrc(); mlir::Value zeroInt = mlir::LLVM::ConstantOp::create( rewriter, castOp.getLoc(), llvmSrcVal.getType(), 0); rewriter.replaceOpWithNewOp( 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(elementTypeIfVector(srcType)); cir::IntType dstIntType = mlir::cast(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(dstTy) || !mlir::isa(srcTy)) return castOp.emitError() << "NYI cast from " << srcTy << " to " << dstTy; auto getFloatWidth = [](mlir::Type ty) -> unsigned { return mlir::cast(ty).getWidth(); }; if (getFloatWidth(srcTy) > getFloatWidth(dstTy)) rewriter.replaceOpWithNewOp(castOp, llvmDstTy, llvmSrcVal); else rewriter.replaceOpWithNewOp(castOp, llvmDstTy, llvmSrcVal); return mlir::success(); } case cir::CastKind::int_to_ptr: { auto dstTy = mlir::cast(castOp.getType()); mlir::Value llvmSrcVal = adaptor.getSrc(); mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy); rewriter.replaceOpWithNewOp(castOp, llvmDstTy, llvmSrcVal); return mlir::success(); } case cir::CastKind::ptr_to_int: { auto dstTy = mlir::cast(castOp.getType()); mlir::Value llvmSrcVal = adaptor.getSrc(); mlir::Type llvmDstTy = getTypeConverter()->convertType(dstTy); rewriter.replaceOpWithNewOp(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. auto zeroFloat = mlir::LLVM::ConstantOp::create( rewriter, castOp.getLoc(), llvmSrcVal.getType(), mlir::FloatAttr::get(llvmSrcVal.getType(), 0.0)); // Extend comparison result to either bool (C++) or int (C). rewriter.replaceOpWithNewOp(castOp, kind, llvmSrcVal, zeroFloat); return mlir::success(); } case cir::CastKind::bool_to_int: { auto dstTy = mlir::cast(castOp.getType()); mlir::Value llvmSrcVal = adaptor.getSrc(); auto llvmSrcTy = mlir::cast(llvmSrcVal.getType()); auto llvmDstTy = mlir::cast(getTypeConverter()->convertType(dstTy)); if (llvmSrcTy.getWidth() == llvmDstTy.getWidth()) rewriter.replaceOpWithNewOp(castOp, llvmDstTy, llvmSrcVal); else rewriter.replaceOpWithNewOp(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(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); if (mlir::cast(elementTypeIfVector(castOp.getSrc().getType())) .isSigned()) rewriter.replaceOpWithNewOp(castOp, llvmDstTy, llvmSrcVal); else rewriter.replaceOpWithNewOp(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); if (mlir::cast(elementTypeIfVector(castOp.getType())) .isSigned()) rewriter.replaceOpWithNewOp(castOp, llvmDstTy, llvmSrcVal); else rewriter.replaceOpWithNewOp(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(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( 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(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(); } 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(index.getType()); mlir::DataLayout llvmLayout(mod); std::optional 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(indexOp); bool rewriteSub = false; if (sub) { if (auto lhsConst = dyn_cast(sub.getLhs().getDefiningOp())) { auto lhsConstInt = mlir::dyn_cast(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(elementTy) || mlir::isa(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(), index, adaptor.getBase().getType(), dyn_cast(ptrStrideOp.getOperand(1).getType())); rewriter.replaceOpWithNewOp( 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(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(elementTy) || mlir::isa(elementTy)) elementTy = rewriter.getIntegerType(8); mlir::Value index = adaptor.getIndex(); index = convertToIndexTy(rewriter, op->getParentOfType(), index, adaptor.getBase().getType(), dyn_cast(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 offset{0, index}; rewriter.replaceOpWithNewOp(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 offset = { adaptor.getOffset().getZExtValue()}; mlir::Type byteType = mlir::IntegerType::get(resultType.getContext(), 8, mlir::IntegerType::Signless); if (adaptor.getOffset().getZExtValue() == 0) { rewriter.replaceOpWithNewOp( baseClassOp, resultType, adaptor.getDerivedAddr()); return mlir::success(); } if (baseClassOp.getAssumeNotNull()) { rewriter.replaceOpWithNewOp( 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(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 offset = {offsetVal}; mlir::Type byteType = mlir::IntegerType::get(resultType.getContext(), 8, mlir::IntegerType::Signless); if (derivedClassOp.getAssumeNotNull()) { rewriter.replaceOpWithNewOp( 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(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()); rewriter.replaceOpWithNewOp( 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()); rewriter.replaceOpWithNewOp( 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.getIndexType()), 1); mlir::Type elementTy = convertTypeForMemory(*getTypeConverter(), dataLayout, op.getAllocaType()); mlir::Type resultTy = convertTypeForMemory(*getTypeConverter(), dataLayout, op.getType()); assert(!cir::MissingFeatures::addressSpace()); assert(!cir::MissingFeatures::opAllocaAnnotations()); rewriter.replaceOpWithNewOp(op, resultTy, elementTy, size, op.getAlignment()); return mlir::success(); } mlir::LogicalResult CIRToLLVMReturnOpLowering::matchAndRewrite( cir::ReturnOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp(op, adaptor.getOperands()); return mlir::LogicalResult::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(op, input, input, adaptor.getAmount()); else rewriter.replaceOpWithNewOp(op, input, input, adaptor.getAmount()); return mlir::LogicalResult::success(); } static void lowerCallAttributes(cir::CIRCallOpInterface op, SmallVectorImpl &result) { for (mlir::NamedAttribute attr : op->getAttrs()) { assert(!cir::MissingFeatures::opFuncCallingConv()); if (attr.getName() == CIRDialect::getCalleeAttrName() || attr.getName() == CIRDialect::getSideEffectAttrName() || attr.getName() == CIRDialect::getNoThrowAttrName() || attr.getName() == CIRDialect::getNoUnwindAttrName() || attr.getName() == CIRDialect::getNoReturnAttrName()) continue; assert(!cir::MissingFeatures::opFuncExtraAttrs()); result.push_back(attr); } } static mlir::LogicalResult rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands, mlir::ConversionPatternRewriter &rewriter, const mlir::TypeConverter *converter, mlir::FlatSymbolRefAttr calleeAttr, mlir::Block *continueBlock = nullptr, mlir::Block *landingPadBlock = nullptr) { llvm::SmallVector llvmResults; mlir::ValueTypeRange cirResults = op->getResultTypes(); auto call = cast(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 attributes; lowerCallAttributes(call, attributes); mlir::LLVM::LLVMFunctionType llvmFnTy; // Temporary to handle the case where we need to prepend an operand if the // callee is an alias. SmallVector adjustedCallOperands; if (calleeAttr) { // direct call mlir::Operation *callee = mlir::SymbolTable::lookupNearestSymbolFrom(op, calleeAttr); if (auto fn = mlir::dyn_cast(callee)) { llvmFnTy = converter->convertType( fn.getFunctionType()); assert(llvmFnTy && "Failed to convert function type"); } else if (auto alias = mlir::cast(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(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(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(calleeTy); auto calleeFuncTy = cast(calleePtrTy.getPointee()); llvm::append_range(adjustedCallOperands, callOperands); llvmFnTy = cast( converter->convertType(calleeFuncTy)); } assert(!cir::MissingFeatures::opCallCallConv()); if (landingPadBlock) { auto newOp = rewriter.replaceOpWithNewOp( op, llvmFnTy, calleeAttr, callOperands, continueBlock, mlir::ValueRange{}, landingPadBlock, mlir::ValueRange{}); newOp->setAttrs(attributes); } else { auto newOp = rewriter.replaceOpWithNewOp( op, llvmFnTy, calleeAttr, callOperands); newOp->setAttrs(attributes); if (memoryEffects) newOp.setMemoryEffectsAttr(memoryEffects); newOp.setNoUnwind(noUnwind); newOp.setWillReturn(willReturn); newOp.setNoreturn(noReturn); } return mlir::success(); } mlir::LogicalResult CIRToLLVMCallOpLowering::matchAndRewrite( cir::CallOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { return rewriteCallOrInvoke(op.getOperation(), adaptor.getOperands(), rewriter, getTypeConverter(), 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(), 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( 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()); mlir::LLVM::AtomicOrdering ordering = getLLVMMemOrder(op.getMemOrder()); std::optional opAlign = op.getAlignment(); unsigned alignment = (unsigned)opAlign.value_or(dataLayout.getTypeABIAlignment(llvmTy)); assert(!cir::MissingFeatures::lowerModeOptLevel()); // TODO: nontemporal. assert(!cir::MissingFeatures::opLoadStoreNontemporal()); std::optional llvmSyncScope = getLLVMSyncScope(op.getSyncScope()); mlir::LLVM::LoadOp newLoad = mlir::LLVM::LoadOp::create( rewriter, op->getLoc(), llvmTy, adaptor.getAddr(), alignment, op.getIsVolatile(), /*isNonTemporal=*/false, /*isInvariant=*/false, /*isInvariantGroup=*/false, ordering, llvmSyncScope.value_or(std::string())); // Convert adapted result to its original type if needed. mlir::Value result = emitFromMemory(rewriter, 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()); std::optional 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()); const mlir::Type llvmTy = getTypeConverter()->convertType(op.getValue().getType()); std::optional opAlign = op.getAlignment(); unsigned alignment = (unsigned)opAlign.value_or(dataLayout.getTypeABIAlignment(llvmTy)); assert(!cir::MissingFeatures::lowerModeOptLevel()); // Convert adapted value to its memory type if needed. mlir::Value value = emitToMemory(rewriter, dataLayout, op.getValue().getType(), adaptor.getValue()); // TODO: nontemporal. assert(!cir::MissingFeatures::opLoadStoreNontemporal()); assert(!cir::MissingFeatures::opLoadStoreTbaa()); std::optional llvmSyncScope = getLLVMSyncScope(op.getSyncScope()); mlir::LLVM::StoreOp storeOp = mlir::LLVM::StoreOp::create( rewriter, op->getLoc(), value, adaptor.getAddr(), alignment, op.getIsVolatile(), /*isNonTemporal=*/false, /*isInvariantGroup=*/false, memorder, llvmSyncScope.value_or(std::string())); rewriter.replaceOp(op, storeOp); assert(!cir::MissingFeatures::opLoadStoreTbaa()); return mlir::LogicalResult::success(); } bool hasTrailingZeros(cir::ConstArrayAttr attr) { auto array = mlir::dyn_cast(attr.getElts()); return attr.hasTrailingZeros() || (array && std::count_if(array.begin(), array.end(), [](auto elt) { auto ar = dyn_cast(elt); return ar && hasTrailingZeros(ar); })); } mlir::LogicalResult CIRToLLVMConstantOpLowering::matchAndRewrite( cir::ConstantOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { mlir::Attribute attr = op.getValue(); if (mlir::isa(attr)) { rewriter.replaceOpWithNewOp( op, getTypeConverter()->convertType(op.getType())); return mlir::success(); } if (mlir::isa(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(op.getType())) { int value = mlir::cast(op.getValue()).getValue(); attr = rewriter.getIntegerAttr(typeConverter->convertType(op.getType()), value); } else if (mlir::isa(op.getType())) { // Lower GlobalViewAttr to llvm.mlir.addressof + llvm.mlir.ptrtoint if (auto ga = mlir::dyn_cast(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(op.getValue()).getValue()); } else if (mlir::isa(op.getType())) { attr = rewriter.getFloatAttr( typeConverter->convertType(op.getType()), mlir::cast(op.getValue()).getValue()); } else if (mlir::isa(op.getType())) { // Optimize with dedicated LLVM op for null pointers. if (mlir::isa(op.getValue())) { if (mlir::cast(op.getValue()).isNullValue()) { rewriter.replaceOpWithNewOp( op, typeConverter->convertType(op.getType())); return mlir::success(); } } // Lower GlobalViewAttr to llvm.mlir.addressof if (auto gv = mlir::dyn_cast(op.getValue())) { auto newOp = lowerCirAttrAsValue(op, gv, rewriter, getTypeConverter()); rewriter.replaceOp(op, newOp); return mlir::success(); } attr = op.getValue(); } else if (const auto arrTy = mlir::dyn_cast(op.getType())) { const auto constArr = mlir::dyn_cast(op.getValue()); if (!constArr && !isa(op.getValue())) return op.emitError() << "array does not have a constant initializer"; std::optional denseAttr; if (constArr && hasTrailingZeros(constArr)) { const mlir::Value newOp = lowerCirAttrAsValue(op, constArr, rewriter, getTypeConverter()); rewriter.replaceOp(op, newOp); return mlir::success(); } else if (constArr && (denseAttr = lowerConstArrayAttr(constArr, typeConverter))) { attr = denseAttr.value(); } else { const mlir::Value initVal = lowerCirAttrAsValue(op, op.getValue(), rewriter, typeConverter); rewriter.replaceOp(op, initVal); return mlir::success(); } } else if (const auto recordAttr = mlir::dyn_cast(op.getValue())) { auto initVal = lowerCirAttrAsValue(op, recordAttr, rewriter, typeConverter); rewriter.replaceOp(op, initVal); return mlir::success(); } else if (const auto vecTy = mlir::dyn_cast(op.getType())) { rewriter.replaceOp(op, lowerCirAttrAsValue(op, op.getValue(), rewriter, getTypeConverter())); return mlir::success(); } else if (auto recTy = mlir::dyn_cast(op.getType())) { if (mlir::isa(attr)) { mlir::Value initVal = lowerCirAttrAsValue(op, attr, rewriter, 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(op.getType())) { mlir::Type complexElemTy = complexTy.getElementType(); mlir::Type complexElemLLVMTy = typeConverter->convertType(complexElemTy); if (auto zeroInitAttr = mlir::dyn_cast(op.getValue())) { mlir::TypedAttr zeroAttr = rewriter.getZeroAttr(complexElemLLVMTy); mlir::ArrayAttr array = rewriter.getArrayAttr({zeroAttr, zeroAttr}); rewriter.replaceOpWithNewOp( op, getTypeConverter()->convertType(op.getType()), array); return mlir::success(); } auto complexAttr = mlir::cast(op.getValue()); mlir::Attribute components[2]; if (mlir::isa(complexElemTy)) { components[0] = rewriter.getIntegerAttr( complexElemLLVMTy, mlir::cast(complexAttr.getReal()).getValue()); components[1] = rewriter.getIntegerAttr( complexElemLLVMTy, mlir::cast(complexAttr.getImag()).getValue()); } else { components[0] = rewriter.getFloatAttr( complexElemLLVMTy, mlir::cast(complexAttr.getReal()).getValue()); components[1] = rewriter.getFloatAttr( complexElemLLVMTy, mlir::cast(complexAttr.getImag()).getValue()); } attr = rewriter.getArrayAttr(components); } else { return op.emitError() << "unsupported constant type " << op.getType(); } rewriter.replaceOpWithNewOp( op, getTypeConverter()->convertType(op.getType()), attr); return mlir::success(); } static uint64_t getTypeSize(mlir::Type type, mlir::Operation &op) { mlir::DataLayout layout(op.getParentOfType()); // For LLVM purposes we treat void as u8. if (isa(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( 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(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 prob = op.getProb(); if (prob) rewriter.replaceOpWithNewOp( op, adaptor.getVal(), adaptor.getExpected(), prob.value()); else rewriter.replaceOpWithNewOp(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 &result) const { assert(!cir::MissingFeatures::opFuncCallingConv()); for (mlir::NamedAttribute attr : func->getAttrs()) { assert(!cir::MissingFeatures::opFuncCallingConv()); if (attr.getName() == mlir::SymbolTable::getSymbolAttrName() || attr.getName() == func.getFunctionTypeAttrName() || attr.getName() == getLinkageAttrNameString() || attr.getName() == func.getDsoLocalAttrName() || attr.getName() == func.getInlineKindAttrName() || attr.getName() == func.getSideEffectAttrName() || attr.getName() == CIRDialect::getNoReturnAttrName() || (filterArgAndResAttrs && (attr.getName() == func.getArgAttrsAttrName() || attr.getName() == func.getResAttrsAttrName()))) continue; assert(!cir::MissingFeatures::opFuncExtraAttrs()); result.push_back(attr); } } mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewriteAlias( cir::FuncOp op, llvm::StringRef aliasee, mlir::Type ty, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { SmallVector attributes; lowerFuncAttributes(op, /*filterArgAndResAttrs=*/false, attributes); mlir::Location loc = op.getLoc(); auto aliasOp = rewriter.replaceOpWithNewOp( op, ty, convertLinkage(op.getLinkage()), op.getName(), op.getDsoLocal(), /*threadLocal=*/false, 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 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(loc)) loc = fusedLoc.getLocations()[0]; assert((mlir::isa(loc) || mlir::isa(loc)) && "expected single location or unknown location here"); mlir::LLVM::Linkage linkage = convertLinkage(op.getLinkage()); assert(!cir::MissingFeatures::opFuncCallingConv()); mlir::LLVM::CConv cconv = mlir::LLVM::CConv::C; SmallVector 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 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); if (std::optional inlineKind = op.getInlineKind()) { fn.setNoInline(*inlineKind == cir::InlineKind::NoInline); fn.setInlineHint(*inlineKind == cir::InlineKind::InlineHint); fn.setAlwaysInline(*inlineKind == cir::InlineKind::AlwaysInline); } if (std::optional 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 CIRToLLVMGlobalOpLowering::lowerGlobalAttributes( cir::GlobalOp op, mlir::ConversionPatternRewriter &rewriter) const { SmallVector 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; } /// 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 { const mlir::Type llvmType = convertTypeForMemory(*getTypeConverter(), dataLayout, op.getSymType()); // 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( op.getAddrSpaceAttr())) addrSpace = targetAS.getValue(); const bool isDsoLocal = op.getDsoLocal(); const bool isThreadLocal = (bool)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 attributes = lowerGlobalAttributes(op, rewriter); mlir::LLVM::GlobalOp newGlobalOp = rewriter.replaceOpWithNewOp( op, llvmType, isConst, linkage, symbol, nullptr, alignment, addrSpace, isDsoLocal, isThreadLocal, 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(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); CIRAttrToValue valueConverter(op, rewriter, typeConverter); 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 init = op.getInitialValue(); // Fetch required values to create LLVM op. const mlir::Type cirSymType = op.getSymType(); // This is the LLVM dialect type. const mlir::Type llvmType = convertTypeForMemory(*getTypeConverter(), dataLayout, cirSymType); // 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( op.getAddrSpaceAttr())) addrSpace = targetAS.getValue(); const bool isDsoLocal = op.getDsoLocal(); const bool isThreadLocal = (bool)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 attributes = lowerGlobalAttributes(op, rewriter); if (init.has_value()) { if (mlir::isa(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 (mlir::isa(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( op, llvmType, isConst, linkage, symbol, init.value_or(mlir::Attribute()), alignment, addrSpace, isDsoLocal, isThreadLocal, comdatAttr, attributes); return mlir::success(); } mlir::SymbolRefAttr CIRToLLVMGlobalOpLowering::getComdatAttr(cir::GlobalOp &op, mlir::OpBuilder &builder) const { if (!op.getComdat()) return mlir::SymbolRefAttr{}; mlir::ModuleOp module = op->getParentOfType(); mlir::OpBuilder::InsertionGuard guard(builder); StringRef comdatName("__llvm_comdat_globals"); if (!comdatOp) { builder.setInsertionPointToStart(module.getBody()); comdatOp = mlir::LLVM::ComdatOp::create(builder, module.getLoc(), comdatName); } if (auto comdatSelector = comdatOp.lookupSymbol( 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 caseValues; for (mlir::Attribute val : op.getCaseValues()) { auto intAttr = cast(val); caseValues.push_back(intAttr.getValue()); } llvm::SmallVector caseDestinations; llvm::SmallVector 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( 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 static mlir::LogicalResult lowerIncDecOp(CIROp op, typename CIROp::Adaptor adaptor, mlir::ConversionPatternRewriter &rewriter, double fpConstant) { mlir::Type elementType = elementTypeIfVector(op.getType()); mlir::Type llvmType = adaptor.getInput().getType(); mlir::Location loc = op.getLoc(); if (mlir::isa(elementType)) { auto maybeNSW = nswFlag(op.getNoSignedWrap()); auto one = mlir::LLVM::ConstantOp::create(rewriter, loc, llvmType, 1); rewriter.replaceOpWithNewOp(op, adaptor.getInput(), one, maybeNSW); return mlir::success(); } if (mlir::isa(elementType)) { auto fpConst = mlir::LLVM::ConstantOp::create( rewriter, loc, rewriter.getFloatAttr(llvmType, fpConstant)); rewriter.replaceOpWithNewOp(op, fpConst, adaptor.getInput()); return mlir::success(); } return op.emitError() << "Unsupported type for IncOp/DecOp"; } mlir::LogicalResult CIRToLLVMIncOpLowering::matchAndRewrite( cir::IncOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { return lowerIncDecOp(op, adaptor, rewriter, 1.0); } mlir::LogicalResult CIRToLLVMDecOpLowering::matchAndRewrite( cir::DecOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { return lowerIncDecOp(op, adaptor, rewriter, -1.0); } mlir::LogicalResult CIRToLLVMMinusOpLowering::matchAndRewrite( cir::MinusOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { mlir::Type elementType = elementTypeIfVector(op.getType()); bool isVector = mlir::isa(op.getType()); mlir::Type llvmType = adaptor.getInput().getType(); mlir::Location loc = op.getLoc(); if (mlir::isa(elementType)) { 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(op, zero, adaptor.getInput(), maybeNSW); return mlir::success(); } if (mlir::isa(elementType)) { rewriter.replaceOpWithNewOp(op, adaptor.getInput()); return mlir::success(); } return op.emitError() << "Unsupported type for unary minus"; } mlir::LogicalResult CIRToLLVMNotOpLowering::matchAndRewrite( cir::NotOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { mlir::Type elementType = elementTypeIfVector(op.getType()); bool isVector = mlir::isa(op.getType()); mlir::Type llvmType = adaptor.getInput().getType(); mlir::Location loc = op.getLoc(); if (mlir::isa(elementType)) { mlir::Value minusOne; if (isVector) { const uint64_t numElements = mlir::dyn_cast(op.getType()).getSize(); SmallVector 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(op, adaptor.getInput(), minusOne); return mlir::success(); } if (mlir::isa(elementType)) { auto one = mlir::LLVM::ConstantOp::create(rewriter, loc, llvmType, 1); rewriter.replaceOpWithNewOp(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(type) ? mlir::cast(type).isUnsigned() : mlir::cast(type).isUnsigned(); } //===----------------------------------------------------------------------===// // Binary Op Lowering //===----------------------------------------------------------------------===// template 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 /// variant. Used for Add and Sub which share identical dispatch logic. template static mlir::LogicalResult lowerSaturatableArithOp(CIROp op, mlir::Value lhs, mlir::Value rhs, mlir::ConversionPatternRewriter &rewriter) { const mlir::Type eltType = elementTypeIfVector(op.getRhs().getType()); if (cir::isIntOrBoolType(eltType)) { if (op.getSaturated()) { if (isIntTypeUnsigned(eltType)) rewriter.replaceOpWithNewOp(op, lhs, rhs); else rewriter.replaceOpWithNewOp(op, lhs, rhs); return mlir::success(); } rewriter.replaceOpWithNewOp(op, lhs, rhs, intOverflowFlag(op)); } else { rewriter.replaceOpWithNewOp(op, lhs, rhs); } return mlir::success(); } mlir::LogicalResult CIRToLLVMAddOpLowering::matchAndRewrite( cir::AddOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { return lowerSaturatableArithOp( op, adaptor.getLhs(), adaptor.getRhs(), rewriter); } mlir::LogicalResult CIRToLLVMSubOpLowering::matchAndRewrite( cir::SubOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { return lowerSaturatableArithOp( op, adaptor.getLhs(), adaptor.getRhs(), rewriter); } mlir::LogicalResult CIRToLLVMMulOpLowering::matchAndRewrite( cir::MulOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { const mlir::Value lhs = adaptor.getLhs(); const mlir::Value rhs = adaptor.getRhs(); if (cir::isIntOrBoolType(elementTypeIfVector(op.getRhs().getType()))) { rewriter.replaceOpWithNewOp(op, lhs, rhs, intOverflowFlag(op)); } else { rewriter.replaceOpWithNewOp(op, lhs, rhs); } return mlir::success(); } /// Lower a binary op that maps to unsigned/signed/FP LLVM ops depending on /// operand type. Used for Div and Rem which share identical dispatch logic. template static mlir::LogicalResult lowerIntFPBinaryOp(CIROp op, mlir::Value lhs, mlir::Value rhs, mlir::ConversionPatternRewriter &rewriter) { const mlir::Type eltType = elementTypeIfVector(op.getRhs().getType()); if (cir::isIntOrBoolType(eltType)) { if (isIntTypeUnsigned(eltType)) rewriter.replaceOpWithNewOp(op, lhs, rhs); else rewriter.replaceOpWithNewOp(op, lhs, rhs); } else { rewriter.replaceOpWithNewOp(op, lhs, rhs); } return mlir::success(); } mlir::LogicalResult CIRToLLVMDivOpLowering::matchAndRewrite( cir::DivOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { return lowerIntFPBinaryOp(op, adaptor.getLhs(), adaptor.getRhs(), rewriter); } mlir::LogicalResult CIRToLLVMRemOpLowering::matchAndRewrite( cir::RemOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { return lowerIntFPBinaryOp(op, adaptor.getLhs(), adaptor.getRhs(), rewriter); } mlir::LogicalResult CIRToLLVMAndOpLowering::matchAndRewrite( cir::AndOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), adaptor.getRhs()); return mlir::success(); } mlir::LogicalResult CIRToLLVMOrOpLowering::matchAndRewrite( cir::OrOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), adaptor.getRhs()); return mlir::success(); } mlir::LogicalResult CIRToLLVMXorOpLowering::matchAndRewrite( cir::XorOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), adaptor.getRhs()); return mlir::success(); } template 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(op, lhs, rhs); else rewriter.replaceOpWithNewOp(op, lhs, rhs); return mlir::success(); } mlir::LogicalResult CIRToLLVMMaxOpLowering::matchAndRewrite( cir::MaxOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { return lowerMinMaxOp( op, adaptor, rewriter); } mlir::LogicalResult CIRToLLVMMinOpLowering::matchAndRewrite( cir::MinOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { return lowerMinMaxOp( 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); } 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; } llvm_unreachable("Unknown CmpOpKind"); } mlir::LogicalResult CIRToLLVMCmpOpLowering::matchAndRewrite( cir::CmpOp cmpOp, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { mlir::Type type = cmpOp.getLhs().getType(); if (mlir::isa(type)) { bool isSigned = mlir::isa(type) ? mlir::cast(type).isSigned() : mlir::cast(type).isSigned(); mlir::LLVM::ICmpPredicate kind = convertCmpKindToICmpPredicate(cmpOp.getKind(), isSigned); rewriter.replaceOpWithNewOp( cmpOp, kind, adaptor.getLhs(), adaptor.getRhs()); return mlir::success(); } if (auto ptrTy = mlir::dyn_cast(type)) { mlir::LLVM::ICmpPredicate kind = convertCmpKindToICmpPredicate(cmpOp.getKind(), /* isSigned=*/false); rewriter.replaceOpWithNewOp( cmpOp, kind, adaptor.getLhs(), adaptor.getRhs()); return mlir::success(); } if (auto vptrTy = mlir::dyn_cast(type)) { // !cir.vptr is a special case, but it's just a pointer to LLVM. auto kind = convertCmpKindToICmpPredicate(cmpOp.getKind(), /* isSigned=*/false); rewriter.replaceOpWithNewOp( cmpOp, kind, adaptor.getLhs(), adaptor.getRhs()); return mlir::success(); } if (mlir::isa(type)) { mlir::LLVM::FCmpPredicate kind = convertCmpKindToFCmpPredicate(cmpOp.getKind()); rewriter.replaceOpWithNewOp( cmpOp, kind, adaptor.getLhs(), adaptor.getRhs()); return mlir::success(); } if (mlir::isa(type)) { mlir::Value lhs = adaptor.getLhs(); mlir::Value rhs = adaptor.getRhs(); mlir::Location loc = cmpOp.getLoc(); auto complexType = mlir::cast(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(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(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(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(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 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(); cir::IntType resultTy = op.getResult().getType(); bool sign = operandTy.getIsSigned() || resultTy.getIsSigned(); unsigned width = std::max(operandTy.getWidth() + (sign && operandTy.isUnsigned()), resultTy.getWidth() + (sign && resultTy.isUnsigned())); 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{0}) .getResult(); mlir::Value overflow = mlir::LLVM::ExtractValueOp::create( rewriter, loc, intrinRet, ArrayRef{1}) .getResult(); if (resultTy.getWidth() < 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 (resultTy.isSigned()) 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 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(op.getAmount().getType()); bool isUnsigned; if (cirAmtTy) { auto cirValTy = mlir::cast(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(op.getValue().getType()); isUnsigned = mlir::cast(cirValVTy.getElementType()).isUnsigned(); } // Lower to the proper LLVM shift operation. if (op.getIsShiftleft()) { rewriter.replaceOpWithNewOp(op, llvmTy, val, amt); return mlir::success(); } if (isUnsigned) rewriter.replaceOpWithNewOp(op, llvmTy, val, amt); else rewriter.replaceOpWithNewOp(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(); if (!definingOp) return {}; auto constValue = definingOp.getValueAttr(); 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(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(op, adaptor.getCondition(), adaptor.getTrueValue()); return mlir::success(); } if (trueValue && trueValue.getValue()) { // select %0, true, %1 => or %0, %1 rewriter.replaceOpWithNewOp(op, adaptor.getCondition(), adaptor.getFalseValue()); return mlir::success(); } } mlir::Value llvmCondition = adaptor.getCondition(); rewriter.replaceOpWithNewOp( 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( 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()); return mlir::LLVM::LLVMArrayType::get(ty, type.getSize()); }); converter.addConversion([&](cir::VectorType type) -> mlir::Type { 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 { auto result = converter.convertType(type.getReturnType()); llvm::SmallVector 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::RecordType type) -> mlir::Type { // Convert struct members. llvm::SmallVector llvmMembers; switch (type.getKind()) { case cir::RecordType::Class: case cir::RecordType::Struct: for (mlir::Type ty : type.getMembers()) llvmMembers.push_back(convertTypeForMemory(converter, dataLayout, ty)); break; // Unions are lowered as only the largest member. case cir::RecordType::Union: if (type.getMembers().empty()) break; if (auto largestMember = type.getLargestMember(dataLayout)) llvmMembers.push_back( convertTypeForMemory(converter, dataLayout, largestMember)); if (type.getPadded()) { auto last = *type.getMembers().rbegin(); llvmMembers.push_back( convertTypeForMemory(converter, dataLayout, last)); } break; } // Record has a name: lower as an identified record. 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 { // Record has no name: lower as literal record. 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(mlir::Attribute)> createXtor) { llvm::SmallVector> globalXtors; for (const mlir::NamedAttribute namedAttr : module->getAttrs()) { if (namedAttr.getName() == globalXtorName) { for (auto attr : mlir::cast(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 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 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); } // The applyPartialConversion function traverses blocks in the dominance order, // so it does not lower and operations that are not reachachable from the // operations passed in as arguments. Since we do need to lower such code in // order to avoid verification errors occur, we cannot just pass the module op // to applyPartialConversion. We must build a set of unreachable ops and // explicitly add them, along with the module, to the vector we pass to // applyPartialConversion. // // For instance, this CIR code: // // cir.func @foo(%arg0: !s32i) -> !s32i { // %4 = cir.cast int_to_bool %arg0 : !s32i -> !cir.bool // cir.if %4 { // %5 = cir.const #cir.int<1> : !s32i // cir.return %5 : !s32i // } else { // %5 = cir.const #cir.int<0> : !s32i // cir.return %5 : !s32i // } // cir.return %arg0 : !s32i // } // // contains an unreachable return operation (the last one). After the flattening // pass it will be placed into the unreachable block. The possible error // after the lowering pass is: error: 'cir.return' op expects parent op to be // one of 'cir.func, cir.scope, cir.if ... The reason that this operation was // not lowered and the new parent is llvm.func. // // In the future we may want to get rid of this function and use a DCE pass or // something similar. But for now we need to guarantee the absence of the // dialect verification errors. static void collectUnreachable(mlir::Operation *parent, llvm::SmallVector &ops) { llvm::SmallVector unreachableBlocks; parent->walk([&](mlir::Block *blk) { // check if (blk->hasNoPredecessors() && !blk->isEntryBlock()) unreachableBlocks.push_back(blk); }); std::set visited; for (mlir::Block *root : unreachableBlocks) { // We create a work list for each unreachable block. // Thus we traverse operations in some order. std::deque workList; workList.push_back(root); while (!workList.empty()) { mlir::Block *blk = workList.back(); workList.pop_back(); if (visited.count(blk)) continue; visited.emplace(blk); for (mlir::Operation &op : *blk) ops.push_back(&op); for (mlir::Block *succ : blk->getSuccessors()) workList.push_back(succ); } } } 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(); } 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; mlir::RewritePatternSet patterns(&getContext()); patterns.add( converter, patterns.getContext(), dl, 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); processCIRAttrs(module); mlir::ConversionTarget target(getContext()); target.addLegalOp(); target.addLegalDialect(); target.addIllegalDialect(); llvm::SmallVector ops; ops.push_back(module); collectUnreachable(module, ops); if (failed(applyPartialConversion(ops, target, std::move(patterns)))) signalPassFailure(); // Emit the llvm.global_ctors array. buildCtorDtorList(module, cir::CIRDialect::getGlobalCtorsAttrName(), "llvm.global_ctors", [](mlir::Attribute attr) { auto ctorAttr = mlir::cast(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(attr); return std::make_pair(dtorAttr.getName(), dtorAttr.getPriority()); }); resolveBlockAddressOp(blockInfoAddr); } mlir::LogicalResult CIRToLLVMBrOpLowering::matchAndRewrite( cir::BrOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp(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()); const auto recordTy = mlir::cast(op.getAddrTy().getPointee()); assert(recordTy && "expected record type"); switch (recordTy.getKind()) { case cir::RecordType::Class: case cir::RecordType::Struct: { // 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. llvm::SmallVector offset{0, op.getIndex()}; const mlir::Type elementTy = getTypeConverter()->convertType(recordTy); rewriter.replaceOpWithNewOp(op, llResTy, elementTy, adaptor.getAddr(), offset); return mlir::success(); } case cir::RecordType::Union: // Union members share the address space, so we just need a bitcast to // conform to type-checking. rewriter.replaceOpWithNewOp(op, llResTy, adaptor.getAddr()); return mlir::success(); } } mlir::LogicalResult CIRToLLVMExtractMemberOpLowering::matchAndRewrite( cir::ExtractMemberOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { std::int64_t indices[1] = {static_cast(op.getIndex())}; mlir::Type recordTy = op.getRecord().getType(); auto cirRecordTy = mlir::cast(recordTy); switch (cirRecordTy.getKind()) { case cir::RecordType::Struct: case cir::RecordType::Class: rewriter.replaceOpWithNewOp( op, adaptor.getRecord(), indices); return mlir::success(); case cir::RecordType::Union: op.emitError("cir.extract_member cannot extract member from a union"); return mlir::failure(); } llvm_unreachable("Unexpected record kind"); } mlir::LogicalResult CIRToLLVMInsertMemberOpLowering::matchAndRewrite( cir::InsertMemberOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { std::int64_t indecies[1] = {static_cast(op.getIndex())}; mlir::Type recordTy = op.getRecord().getType(); if (auto cirRecordTy = mlir::dyn_cast(recordTy)) { if (cirRecordTy.getKind() == cir::RecordType::Union) { op.emitError("cir.update_member cannot update member of a union"); return mlir::failure(); } } rewriter.replaceOpWithNewOp( op, adaptor.getRecord(), adaptor.getValue(), indecies); return mlir::success(); } mlir::LogicalResult CIRToLLVMUnreachableOpLowering::matchAndRewrite( cir::UnreachableOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp(op); return mlir::success(); } void createLLVMFuncOpIfNotExist(mlir::ConversionPatternRewriter &rewriter, mlir::Operation *srcOp, llvm::StringRef fnName, mlir::Type fnTy) { auto modOp = srcOp->getParentOfType(); auto enclosingFnOp = srcOp->getParentOfType(); mlir::Operation *sourceSymbol = mlir::SymbolTable::lookupSymbolIn(modOp, fnName); if (!sourceSymbol) { mlir::OpBuilder::InsertionGuard guard(rewriter); rewriter.setInsertionPoint(enclosingFnOp); mlir::LLVM::LLVMFuncOp::create(rewriter, srcOp->getLoc(), fnName, fnTy); } } 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, 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, 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, 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 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(); assert(llvmFn && "expected LLVM function parent"); mlir::Block *entryBlock = &llvmFn.getRegion().front(); assert(entryBlock->isEntryBlock()); mlir::ArrayAttr catchListAttr = op.getCatchTypeListAttr(); mlir::SmallVector 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(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 slotIdx = {0}; mlir::Value slot = mlir::LLVM::InsertValueOp::create( rewriter, op.getLoc(), poison, adaptor.getExceptionPtr(), slotIdx); SmallVector selectorIdx = {1}; mlir::Value selector = mlir::LLVM::InsertValueOp::create( rewriter, op.getLoc(), slot, adaptor.getTypeId(), selectorIdx); rewriter.replaceOpWithNewOp(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( 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, const mlir::TypeConverter *converter, mlir::FlatSymbolRefAttr nameAttr, mlir::Type &eltType) { auto module = op->getParentOfType(); mlir::Operation *symbol = mlir::SymbolTable::lookupSymbolIn(module, nameAttr); if (auto llvmSymbol = mlir::dyn_cast(symbol)) { eltType = llvmSymbol.getType(); } else if (auto cirSymbol = mlir::dyn_cast(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 offsets; mlir::Type eltType; mlir::Value symAddr = getValueForVTableSymbol(op, rewriter, converter, op.getNameAttr(), eltType); if (!symAddr) return op.emitError() << "Unable to get value for vtable symbol"; offsets = llvm::SmallVector{ 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(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 offsets = llvm::SmallVector{op.getIndex()}; rewriter.replaceOpWithNewOp( 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 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, 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( op, resultType, eltType, llvmAddr, offsets, mlir::LLVM::GEPNoWrapFlags::inbounds); return mlir::success(); } mlir::LogicalResult CIRToLLVMStackSaveOpLowering::matchAndRewrite( cir::StackSaveOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { const mlir::Type ptrTy = getTypeConverter()->convertType(op.getType()); rewriter.replaceOpWithNewOp(op, ptrTy); return mlir::success(); } mlir::LogicalResult CIRToLLVMStackRestoreOpLowering::matchAndRewrite( cir::StackRestoreOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp(op, adaptor.getPtr()); 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(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( op, adaptor.getVec(), adaptor.getIndex()); return mlir::success(); } mlir::LogicalResult CIRToLLVMVecInsertOpLowering::matchAndRewrite( cir::VecInsertOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp( 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(elementType)) { bitResult = mlir::LLVM::ICmpOp::create( rewriter, op.getLoc(), convertCmpKindToICmpPredicate(op.getKind(), intType.isSigned()), adaptor.getLhs(), adaptor.getRhs()); } else if (mlir::isa(elementType)) { 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(cast(op.getType()).getElementType()) .getWidth() > 1) rewriter.replaceOpWithNewOp( 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()) { // 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()) { if (auto intAttr = dyn_cast(constValue.getValue())) { mlir::DenseIntElementsAttr denseVec = mlir::DenseIntElementsAttr::get( mlir::cast(llvmTy), intAttr.getValue()); rewriter.replaceOpWithNewOp( op, denseVec.getType(), denseVec); return mlir::success(); } if (auto fpAttr = dyn_cast(constValue.getValue())) { mlir::DenseFPElementsAttr denseVec = mlir::DenseFPElementsAttr::get( mlir::cast(llvmTy), fpAttr.getValue()); rewriter.replaceOpWithNewOp( 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 zeroValues(vecTy.getSize(), 0); rewriter.replaceOpWithNewOp(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. SmallVector indices; std::transform( op.getIndices().begin(), op.getIndices().end(), std::back_inserter(indices), [](mlir::Attribute intAttr) { return mlir::cast(intAttr).getValue().getSExtValue(); }); rewriter.replaceOpWithNewOp( 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(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( 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(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( 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(op.getOperand().getType())) { operand = mlir::LLVM::ExtractValueOp::create( rewriter, op.getLoc(), resultLLVMTy, operand, llvm::ArrayRef{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(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( 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(op.getOperand().getType())) { operand = mlir::LLVM::ExtractValueOp::create( rewriter, loc, resultLLVMTy, operand, llvm::ArrayRef{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(storageType) .Case([&](cir::ArrayType atTy) { storageSize = atTy.getSize() * 8; return mlir::IntegerType::get(context, storageSize); }) .Case([&](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(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( 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( 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(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(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(resTy), info.getIsSigned()); rewriter.replaceOp(op, newOp); return mlir::success(); } mlir::LogicalResult CIRToLLVMIsConstantOpLowering::matchAndRewrite( cir::IsConstantOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { rewriter.replaceOpWithNewOp(op, adaptor.getVal()); 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 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 llvmOperands; SmallVector 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 (!cirOpAttr) { opAttrs.push_back(mlir::Attribute()); continue; } llvm::SmallVector attrs; cir::PointerType typ = mlir::cast(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( 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(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(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(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(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(); 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 { llvm::SmallVector successors; llvm::SmallVector succOperands; bool poison = op.getPoison(); for (mlir::Block *succ : op->getSuccessors()) successors.push_back(succ); for (mlir::ValueRange operand : op.getSuccOperands()) { succOperands.push_back(operand); } auto llvmPtrType = mlir::LLVM::LLVMPointerType::get(rewriter.getContext()); mlir::Value targetAddr; if (!poison) { targetAddr = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), llvmPtrType, adaptor.getAddr()); } else { targetAddr = mlir::LLVM::PoisonOp::create(rewriter, op->getLoc(), llvmPtrType); // Remove the block argument to avoid generating an empty PHI during // lowering. op->getBlock()->eraseArgument(0); } auto newOp = mlir::LLVM::IndirectBrOp::create( rewriter, op.getLoc(), targetAddr, succOperands, successors); rewriter.replaceOp(op, newOp); return mlir::success(); } mlir::LogicalResult CIRToLLVMAwaitOpLowering::matchAndRewrite( cir::AwaitOp 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(); llvm::Triple triple( mlir::cast( 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()); 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 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"; createLLVMFuncOpIfNotExist(rewriter, op, fnName, fnTy); rewriter.replaceOpWithNewOp( op, mlir::TypeRange{llvmPtrTy}, fnName, mlir::ValueRange{adaptor.getSrc(), adaptor.getPattern(), adaptor.getLen()}); return mlir::success(); } std::unique_ptr createConvertCIRToLLVMPass() { return std::make_unique(); } void populateCIRToLLVMPasses(mlir::OpPassManager &pm) { mlir::populateCIRPreLoweringPasses(pm); pm.addPass(mlir::omp::createMarkDeclareTargetPass()); pm.addPass(createConvertCIRToLLVMPass()); } std::unique_ptr lowerDirectlyFromCIRToLLVMIR(mlir::ModuleOp mlirModule, LLVMContext &llvmCtx, StringRef mlirSaveTempsOutFile) { llvm::TimeTraceScope scope("lower from CIR to LLVM directly"); mlir::MLIRContext *mlirCtx = mlirModule.getContext(); mlir::PassManager pm(mlirCtx); populateCIRToLLVMPasses(pm); (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 llvmModule = mlir::translateModuleToLLVMIR(mlirModule, llvmCtx, moduleName); 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