//====- 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 "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/IR/BuiltinDialect.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/Pass/Pass.h" #include "mlir/Pass/PassManager.h" #include "mlir/Target/LLVMIR/Dialect/Builtin/BuiltinToLLVMIRTranslation.h" #include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h" #include "mlir/Target/LLVMIR/Export.h" #include "mlir/Transforms/DialectConversion.h" #include "clang/CIR/Dialect/IR/CIRAttrVisitor.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" #include "clang/CIR/MissingFeatures.h" #include "llvm/IR/Module.h" #include "llvm/Support/TimeProfiler.h" using namespace cir; using namespace llvm; namespace cir { namespace direct { class CIRAttrToValue : public CirAttrVisitor { public: CIRAttrToValue(mlir::Operation *parentOp, mlir::ConversionPatternRewriter &rewriter, const mlir::TypeConverter *converter) : parentOp(parentOp), rewriter(rewriter), converter(converter) {} mlir::Value lowerCirAttrAsValue(mlir::Attribute attr) { return visit(attr); } mlir::Value visitCirIntAttr(cir::IntAttr intAttr) { mlir::Location loc = parentOp->getLoc(); return rewriter.create( loc, converter->convertType(intAttr.getType()), intAttr.getValue()); } mlir::Value visitCirFPAttr(cir::FPAttr fltAttr) { mlir::Location loc = parentOp->getLoc(); return rewriter.create( loc, converter->convertType(fltAttr.getType()), fltAttr.getValue()); } mlir::Value visitCirConstPtrAttr(cir::ConstPtrAttr ptrAttr) { mlir::Location loc = parentOp->getLoc(); if (ptrAttr.isNullValue()) { return rewriter.create( loc, converter->convertType(ptrAttr.getType())); } mlir::DataLayout layout(parentOp->getParentOfType()); mlir::Value ptrVal = rewriter.create( loc, rewriter.getIntegerType(layout.getTypeSizeInBits(ptrAttr.getType())), ptrAttr.getValue().getInt()); return rewriter.create( loc, converter->convertType(ptrAttr.getType()), ptrVal); } private: mlir::Operation *parentOp; mlir::ConversionPatternRewriter &rewriter; const mlir::TypeConverter *converter; }; // This class handles rewriting initializer attributes for types that do not // require region initialization. class GlobalInitAttrRewriter : public CirAttrVisitor { public: GlobalInitAttrRewriter(mlir::Type type, mlir::ConversionPatternRewriter &rewriter) : llvmType(type), rewriter(rewriter) {} mlir::Attribute rewriteInitAttr(mlir::Attribute attr) { return visit(attr); } mlir::Attribute visitCirIntAttr(cir::IntAttr attr) { return rewriter.getIntegerAttr(llvmType, attr.getValue()); } mlir::Attribute visitCirFPAttr(cir::FPAttr attr) { return rewriter.getFloatAttr(llvmType, 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; StringRef getDescription() const override { return "Convert the prepared CIR dialect module to LLVM dialect"; } StringRef getArgument() const override { return "cir-flat-to-llvm"; } }; bool CIRToLLVMGlobalOpLowering::attrRequiresRegionInitialization( mlir::Attribute attr) const { // There will be more cases added later. return isa(attr); } /// 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 { assert(!cir::MissingFeatures::convertTypeForMemory()); const mlir::Type llvmType = getTypeConverter()->convertType(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. assert(!cir::MissingFeatures::opGlobalConstant()); const bool isConst = false; assert(!cir::MissingFeatures::addressSpace()); const unsigned addrSpace = 0; assert(!cir::MissingFeatures::opGlobalDSOLocal()); const bool isDsoLocal = true; assert(!cir::MissingFeatures::opGlobalThreadLocal()); const bool isThreadLocal = false; assert(!cir::MissingFeatures::opGlobalAlignment()); const uint64_t alignment = 0; assert(!cir::MissingFeatures::opGlobalLinkage()); const mlir::LLVM::Linkage linkage = mlir::LLVM::Linkage::External; const StringRef symbol = op.getSymName(); SmallVector attributes; auto newGlobalOp = rewriter.replaceOpWithNewOp( op, llvmType, isConst, linkage, symbol, nullptr, alignment, addrSpace, isDsoLocal, isThreadLocal, /*comdat=*/mlir::SymbolRefAttr(), attributes); newGlobalOp.getRegion().push_back(new mlir::Block()); 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 attrVisitor(op, rewriter, typeConverter); mlir::Value value = attrVisitor.lowerCirAttrAsValue(init); rewriter.create(loc, value); return mlir::success(); } mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite( cir::GlobalOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { std::optional init = op.getInitialValue(); // If we have an initializer and it requires region initialization, handle // that separately if (init.has_value() && attrRequiresRegionInitialization(init.value())) { return matchAndRewriteRegionInitializedGlobal(op, init.value(), rewriter); } // Fetch required values to create LLVM op. const mlir::Type cirSymType = op.getSymType(); // This is the LLVM dialect type. assert(!cir::MissingFeatures::convertTypeForMemory()); const mlir::Type llvmType = getTypeConverter()->convertType(cirSymType); // FIXME: These default values are placeholders until the the equivalent // attributes are available on cir.global ops. assert(!cir::MissingFeatures::opGlobalConstant()); const bool isConst = false; assert(!cir::MissingFeatures::addressSpace()); const unsigned addrSpace = 0; assert(!cir::MissingFeatures::opGlobalDSOLocal()); const bool isDsoLocal = true; assert(!cir::MissingFeatures::opGlobalThreadLocal()); const bool isThreadLocal = false; assert(!cir::MissingFeatures::opGlobalAlignment()); const uint64_t alignment = 0; assert(!cir::MissingFeatures::opGlobalLinkage()); const mlir::LLVM::Linkage linkage = mlir::LLVM::Linkage::External; const StringRef symbol = op.getSymName(); SmallVector attributes; if (init.has_value()) { GlobalInitAttrRewriter initRewriter(llvmType, rewriter); init = initRewriter.rewriteInitAttr(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(); } } // Rewrite op. rewriter.replaceOpWithNewOp( op, llvmType, isConst, linkage, symbol, init.value_or(mlir::Attribute()), alignment, addrSpace, isDsoLocal, isThreadLocal, /*comdat=*/mlir::SymbolRefAttr(), attributes); return mlir::success(); } static void prepareTypeConverter(mlir::LLVMTypeConverter &converter, mlir::DataLayout &dataLayout) { converter.addConversion([&](cir::PointerType type) -> mlir::Type { // Drop pointee type since LLVM dialect only allows opaque pointers. assert(!cir::MissingFeatures::addressSpace()); unsigned targetAS = 0; return mlir::LLVM::LLVMPointerType::get(type.getContext(), targetAS); }); 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()); }); } 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); mlir::RewritePatternSet patterns(&getContext()); patterns.add(converter, patterns.getContext(), dl); mlir::ConversionTarget target(getContext()); target.addLegalOp(); target.addLegalDialect(); target.addIllegalDialect(); if (failed(applyPartialConversion(module, target, std::move(patterns)))) signalPassFailure(); } static std::unique_ptr createConvertCIRToLLVMPass() { return std::make_unique(); } static void populateCIRToLLVMPasses(mlir::OpPassManager &pm) { pm.addPass(createConvertCIRToLLVMPass()); } std::unique_ptr lowerDirectlyFromCIRToLLVMIR(mlir::ModuleOp mlirModule, LLVMContext &llvmCtx) { llvm::TimeTraceScope scope("lower from CIR to LLVM directly"); mlir::MLIRContext *mlirCtx = mlirModule.getContext(); mlir::PassManager pm(mlirCtx); populateCIRToLLVMPasses(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!"); } mlir::registerBuiltinDialectTranslation(*mlirCtx); mlir::registerLLVMDialectTranslation(*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