//===- LoweringPrepare.cpp - pareparation work for LLVM lowering ----------===// // // 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 // //===----------------------------------------------------------------------===// #include "LoweringPrepareCXXABI.h" #include "PassDetail.h" #include "mlir/IR/Attributes.h" #include "clang/AST/ASTContext.h" #include "clang/Basic/Module.h" #include "clang/Basic/TargetInfo.h" #include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h" #include "clang/CIR/Dialect/IR/CIRAttrs.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" #include "clang/CIR/Dialect/IR/CIROpsEnums.h" #include "clang/CIR/Dialect/Passes.h" #include "clang/CIR/MissingFeatures.h" #include "llvm/Support/Path.h" #include using namespace mlir; using namespace cir; namespace mlir { #define GEN_PASS_DEF_LOWERINGPREPARE #include "clang/CIR/Dialect/Passes.h.inc" } // namespace mlir static SmallString<128> getTransformedFileName(mlir::ModuleOp mlirModule) { SmallString<128> fileName; if (mlirModule.getSymName()) fileName = llvm::sys::path::filename(mlirModule.getSymName()->str()); if (fileName.empty()) fileName = ""; for (size_t i = 0; i < fileName.size(); ++i) { // Replace everything that's not [a-zA-Z0-9._] with a _. This set happens // to be the set of C preprocessing numbers. if (!clang::isPreprocessingNumberBody(fileName[i])) fileName[i] = '_'; } return fileName; } /// Return the FuncOp called by `callOp`. static cir::FuncOp getCalledFunction(cir::CallOp callOp) { mlir::SymbolRefAttr sym = llvm::dyn_cast_if_present( callOp.getCallableForCallee()); if (!sym) return nullptr; return dyn_cast_or_null( mlir::SymbolTable::lookupNearestSymbolFrom(callOp, sym)); } namespace { struct LoweringPreparePass : public impl::LoweringPrepareBase { LoweringPreparePass() = default; void runOnOperation() override; void runOnOp(mlir::Operation *op); void lowerCastOp(cir::CastOp op); void lowerComplexDivOp(cir::ComplexDivOp op); void lowerComplexMulOp(cir::ComplexMulOp op); void lowerUnaryOp(cir::UnaryOp op); void lowerGlobalOp(cir::GlobalOp op); void lowerDynamicCastOp(cir::DynamicCastOp op); void lowerArrayDtor(cir::ArrayDtor op); void lowerArrayCtor(cir::ArrayCtor op); /// Build the function that initializes the specified global cir::FuncOp buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op); /// Handle the dtor region by registering destructor with __cxa_atexit cir::FuncOp getOrCreateDtorFunc(CIRBaseBuilderTy &builder, cir::GlobalOp op, mlir::Region &dtorRegion, cir::CallOp &dtorCall); /// Build a module init function that calls all the dynamic initializers. void buildCXXGlobalInitFunc(); /// Materialize global ctor/dtor list void buildGlobalCtorDtorList(); cir::FuncOp buildRuntimeFunction( mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, cir::FuncType type, cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage); cir::GlobalOp buildRuntimeVariable( mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, mlir::Type type, cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind visibility = cir::VisibilityKind::Default); /// /// AST related /// ----------- clang::ASTContext *astCtx; // Helper for lowering C++ ABI specific operations. std::shared_ptr cxxABI; /// Tracks current module. mlir::ModuleOp mlirModule; /// Tracks existing dynamic initializers. llvm::StringMap dynamicInitializerNames; llvm::SmallVector dynamicInitializers; /// List of ctors and their priorities to be called before main() llvm::SmallVector, 4> globalCtorList; /// List of dtors and their priorities to be called when unloading module. llvm::SmallVector, 4> globalDtorList; void setASTContext(clang::ASTContext *c) { astCtx = c; switch (c->getCXXABIKind()) { case clang::TargetCXXABI::GenericItanium: // We'll need X86-specific support for handling vaargs lowering, but for // now the Itanium ABI will work. assert(!cir::MissingFeatures::loweringPrepareX86CXXABI()); cxxABI.reset(cir::LoweringPrepareCXXABI::createItaniumABI()); break; case clang::TargetCXXABI::GenericAArch64: case clang::TargetCXXABI::AppleARM64: assert(!cir::MissingFeatures::loweringPrepareAArch64XXABI()); cxxABI.reset(cir::LoweringPrepareCXXABI::createItaniumABI()); break; default: llvm_unreachable("NYI"); } } }; } // namespace cir::GlobalOp LoweringPreparePass::buildRuntimeVariable( mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, mlir::Type type, cir::GlobalLinkageKind linkage, cir::VisibilityKind visibility) { cir::GlobalOp g = dyn_cast_or_null( mlir::SymbolTable::lookupNearestSymbolFrom( mlirModule, mlir::StringAttr::get(mlirModule->getContext(), name))); if (!g) { g = cir::GlobalOp::create(builder, loc, name, type); g.setLinkageAttr( cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage)); mlir::SymbolTable::setSymbolVisibility( g, mlir::SymbolTable::Visibility::Private); g.setGlobalVisibilityAttr( cir::VisibilityAttr::get(builder.getContext(), visibility)); } return g; } cir::FuncOp LoweringPreparePass::buildRuntimeFunction( mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, cir::FuncType type, cir::GlobalLinkageKind linkage) { cir::FuncOp f = dyn_cast_or_null(SymbolTable::lookupNearestSymbolFrom( mlirModule, StringAttr::get(mlirModule->getContext(), name))); if (!f) { f = cir::FuncOp::create(builder, loc, name, type); f.setLinkageAttr( cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage)); mlir::SymbolTable::setSymbolVisibility( f, mlir::SymbolTable::Visibility::Private); assert(!cir::MissingFeatures::opFuncExtraAttrs()); } return f; } static mlir::Value lowerScalarToComplexCast(mlir::MLIRContext &ctx, cir::CastOp op) { cir::CIRBaseBuilderTy builder(ctx); builder.setInsertionPoint(op); mlir::Value src = op.getSrc(); mlir::Value imag = builder.getNullValue(src.getType(), op.getLoc()); return builder.createComplexCreate(op.getLoc(), src, imag); } static mlir::Value lowerComplexToScalarCast(mlir::MLIRContext &ctx, cir::CastOp op, cir::CastKind elemToBoolKind) { cir::CIRBaseBuilderTy builder(ctx); builder.setInsertionPoint(op); mlir::Value src = op.getSrc(); if (!mlir::isa(op.getType())) return builder.createComplexReal(op.getLoc(), src); // Complex cast to bool: (bool)(a+bi) => (bool)a || (bool)b mlir::Value srcReal = builder.createComplexReal(op.getLoc(), src); mlir::Value srcImag = builder.createComplexImag(op.getLoc(), src); cir::BoolType boolTy = builder.getBoolTy(); mlir::Value srcRealToBool = builder.createCast(op.getLoc(), elemToBoolKind, srcReal, boolTy); mlir::Value srcImagToBool = builder.createCast(op.getLoc(), elemToBoolKind, srcImag, boolTy); return builder.createLogicalOr(op.getLoc(), srcRealToBool, srcImagToBool); } static mlir::Value lowerComplexToComplexCast(mlir::MLIRContext &ctx, cir::CastOp op, cir::CastKind scalarCastKind) { CIRBaseBuilderTy builder(ctx); builder.setInsertionPoint(op); mlir::Value src = op.getSrc(); auto dstComplexElemTy = mlir::cast(op.getType()).getElementType(); mlir::Value srcReal = builder.createComplexReal(op.getLoc(), src); mlir::Value srcImag = builder.createComplexImag(op.getLoc(), src); mlir::Value dstReal = builder.createCast(op.getLoc(), scalarCastKind, srcReal, dstComplexElemTy); mlir::Value dstImag = builder.createCast(op.getLoc(), scalarCastKind, srcImag, dstComplexElemTy); return builder.createComplexCreate(op.getLoc(), dstReal, dstImag); } void LoweringPreparePass::lowerCastOp(cir::CastOp op) { mlir::MLIRContext &ctx = getContext(); mlir::Value loweredValue = [&]() -> mlir::Value { switch (op.getKind()) { case cir::CastKind::float_to_complex: case cir::CastKind::int_to_complex: return lowerScalarToComplexCast(ctx, op); case cir::CastKind::float_complex_to_real: case cir::CastKind::int_complex_to_real: return lowerComplexToScalarCast(ctx, op, op.getKind()); case cir::CastKind::float_complex_to_bool: return lowerComplexToScalarCast(ctx, op, cir::CastKind::float_to_bool); case cir::CastKind::int_complex_to_bool: return lowerComplexToScalarCast(ctx, op, cir::CastKind::int_to_bool); case cir::CastKind::float_complex: return lowerComplexToComplexCast(ctx, op, cir::CastKind::floating); case cir::CastKind::float_complex_to_int_complex: return lowerComplexToComplexCast(ctx, op, cir::CastKind::float_to_int); case cir::CastKind::int_complex: return lowerComplexToComplexCast(ctx, op, cir::CastKind::integral); case cir::CastKind::int_complex_to_float_complex: return lowerComplexToComplexCast(ctx, op, cir::CastKind::int_to_float); default: return nullptr; } }(); if (loweredValue) { op.replaceAllUsesWith(loweredValue); op.erase(); } } static mlir::Value buildComplexBinOpLibCall( LoweringPreparePass &pass, CIRBaseBuilderTy &builder, llvm::StringRef (*libFuncNameGetter)(llvm::APFloat::Semantics), mlir::Location loc, cir::ComplexType ty, mlir::Value lhsReal, mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag) { cir::FPTypeInterface elementTy = mlir::cast(ty.getElementType()); llvm::StringRef libFuncName = libFuncNameGetter( llvm::APFloat::SemanticsToEnum(elementTy.getFloatSemantics())); llvm::SmallVector libFuncInputTypes(4, elementTy); cir::FuncType libFuncTy = cir::FuncType::get(libFuncInputTypes, ty); // Insert a declaration for the runtime function to be used in Complex // multiplication and division when needed cir::FuncOp libFunc; { mlir::OpBuilder::InsertionGuard ipGuard{builder}; builder.setInsertionPointToStart(pass.mlirModule.getBody()); libFunc = pass.buildRuntimeFunction(builder, libFuncName, loc, libFuncTy); } cir::CallOp call = builder.createCallOp(loc, libFunc, {lhsReal, lhsImag, rhsReal, rhsImag}); return call.getResult(); } static llvm::StringRef getComplexDivLibCallName(llvm::APFloat::Semantics semantics) { switch (semantics) { case llvm::APFloat::S_IEEEhalf: return "__divhc3"; case llvm::APFloat::S_IEEEsingle: return "__divsc3"; case llvm::APFloat::S_IEEEdouble: return "__divdc3"; case llvm::APFloat::S_PPCDoubleDouble: return "__divtc3"; case llvm::APFloat::S_x87DoubleExtended: return "__divxc3"; case llvm::APFloat::S_IEEEquad: return "__divtc3"; default: llvm_unreachable("unsupported floating point type"); } } static mlir::Value buildAlgebraicComplexDiv(CIRBaseBuilderTy &builder, mlir::Location loc, mlir::Value lhsReal, mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag) { // (a+bi) / (c+di) = ((ac+bd)/(cc+dd)) + ((bc-ad)/(cc+dd))i mlir::Value &a = lhsReal; mlir::Value &b = lhsImag; mlir::Value &c = rhsReal; mlir::Value &d = rhsImag; mlir::Value ac = builder.createBinop(loc, a, cir::BinOpKind::Mul, c); // a*c mlir::Value bd = builder.createBinop(loc, b, cir::BinOpKind::Mul, d); // b*d mlir::Value cc = builder.createBinop(loc, c, cir::BinOpKind::Mul, c); // c*c mlir::Value dd = builder.createBinop(loc, d, cir::BinOpKind::Mul, d); // d*d mlir::Value acbd = builder.createBinop(loc, ac, cir::BinOpKind::Add, bd); // ac+bd mlir::Value ccdd = builder.createBinop(loc, cc, cir::BinOpKind::Add, dd); // cc+dd mlir::Value resultReal = builder.createBinop(loc, acbd, cir::BinOpKind::Div, ccdd); mlir::Value bc = builder.createBinop(loc, b, cir::BinOpKind::Mul, c); // b*c mlir::Value ad = builder.createBinop(loc, a, cir::BinOpKind::Mul, d); // a*d mlir::Value bcad = builder.createBinop(loc, bc, cir::BinOpKind::Sub, ad); // bc-ad mlir::Value resultImag = builder.createBinop(loc, bcad, cir::BinOpKind::Div, ccdd); return builder.createComplexCreate(loc, resultReal, resultImag); } static mlir::Value buildRangeReductionComplexDiv(CIRBaseBuilderTy &builder, mlir::Location loc, mlir::Value lhsReal, mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag) { // Implements Smith's algorithm for complex division. // SMITH, R. L. Algorithm 116: Complex division. Commun. ACM 5, 8 (1962). // Let: // - lhs := a+bi // - rhs := c+di // - result := lhs / rhs = e+fi // // The algorithm pseudocode looks like follows: // if fabs(c) >= fabs(d): // r := d / c // tmp := c + r*d // e = (a + b*r) / tmp // f = (b - a*r) / tmp // else: // r := c / d // tmp := d + r*c // e = (a*r + b) / tmp // f = (b*r - a) / tmp mlir::Value &a = lhsReal; mlir::Value &b = lhsImag; mlir::Value &c = rhsReal; mlir::Value &d = rhsImag; auto trueBranchBuilder = [&](mlir::OpBuilder &, mlir::Location) { mlir::Value r = builder.createBinop(loc, d, cir::BinOpKind::Div, c); // r := d / c mlir::Value rd = builder.createBinop(loc, r, cir::BinOpKind::Mul, d); // r*d mlir::Value tmp = builder.createBinop(loc, c, cir::BinOpKind::Add, rd); // tmp := c + r*d mlir::Value br = builder.createBinop(loc, b, cir::BinOpKind::Mul, r); // b*r mlir::Value abr = builder.createBinop(loc, a, cir::BinOpKind::Add, br); // a + b*r mlir::Value e = builder.createBinop(loc, abr, cir::BinOpKind::Div, tmp); mlir::Value ar = builder.createBinop(loc, a, cir::BinOpKind::Mul, r); // a*r mlir::Value bar = builder.createBinop(loc, b, cir::BinOpKind::Sub, ar); // b - a*r mlir::Value f = builder.createBinop(loc, bar, cir::BinOpKind::Div, tmp); mlir::Value result = builder.createComplexCreate(loc, e, f); builder.createYield(loc, result); }; auto falseBranchBuilder = [&](mlir::OpBuilder &, mlir::Location) { mlir::Value r = builder.createBinop(loc, c, cir::BinOpKind::Div, d); // r := c / d mlir::Value rc = builder.createBinop(loc, r, cir::BinOpKind::Mul, c); // r*c mlir::Value tmp = builder.createBinop(loc, d, cir::BinOpKind::Add, rc); // tmp := d + r*c mlir::Value ar = builder.createBinop(loc, a, cir::BinOpKind::Mul, r); // a*r mlir::Value arb = builder.createBinop(loc, ar, cir::BinOpKind::Add, b); // a*r + b mlir::Value e = builder.createBinop(loc, arb, cir::BinOpKind::Div, tmp); mlir::Value br = builder.createBinop(loc, b, cir::BinOpKind::Mul, r); // b*r mlir::Value bra = builder.createBinop(loc, br, cir::BinOpKind::Sub, a); // b*r - a mlir::Value f = builder.createBinop(loc, bra, cir::BinOpKind::Div, tmp); mlir::Value result = builder.createComplexCreate(loc, e, f); builder.createYield(loc, result); }; auto cFabs = cir::FAbsOp::create(builder, loc, c); auto dFabs = cir::FAbsOp::create(builder, loc, d); cir::CmpOp cmpResult = builder.createCompare(loc, cir::CmpOpKind::ge, cFabs, dFabs); auto ternary = cir::TernaryOp::create(builder, loc, cmpResult, trueBranchBuilder, falseBranchBuilder); return ternary.getResult(); } static mlir::Type higherPrecisionElementTypeForComplexArithmetic( mlir::MLIRContext &context, clang::ASTContext &cc, CIRBaseBuilderTy &builder, mlir::Type elementType) { auto getHigherPrecisionFPType = [&context](mlir::Type type) -> mlir::Type { if (mlir::isa(type)) return cir::SingleType::get(&context); if (mlir::isa(type) || mlir::isa(type)) return cir::DoubleType::get(&context); if (mlir::isa(type)) return cir::LongDoubleType::get(&context, type); return type; }; auto getFloatTypeSemantics = [&cc](mlir::Type type) -> const llvm::fltSemantics & { const clang::TargetInfo &info = cc.getTargetInfo(); if (mlir::isa(type)) return info.getHalfFormat(); if (mlir::isa(type)) return info.getBFloat16Format(); if (mlir::isa(type)) return info.getFloatFormat(); if (mlir::isa(type)) return info.getDoubleFormat(); if (mlir::isa(type)) { if (cc.getLangOpts().OpenMP && cc.getLangOpts().OpenMPIsTargetDevice) llvm_unreachable("NYI Float type semantics with OpenMP"); return info.getLongDoubleFormat(); } if (mlir::isa(type)) { if (cc.getLangOpts().OpenMP && cc.getLangOpts().OpenMPIsTargetDevice) llvm_unreachable("NYI Float type semantics with OpenMP"); return info.getFloat128Format(); } llvm_unreachable("Unsupported float type semantics"); }; const mlir::Type higherElementType = getHigherPrecisionFPType(elementType); const llvm::fltSemantics &elementTypeSemantics = getFloatTypeSemantics(elementType); const llvm::fltSemantics &higherElementTypeSemantics = getFloatTypeSemantics(higherElementType); // Check that the promoted type can handle the intermediate values without // overflowing. This can be interpreted as: // (SmallerType.LargestFiniteVal * SmallerType.LargestFiniteVal) * 2 <= // LargerType.LargestFiniteVal. // In terms of exponent it gives this formula: // (SmallerType.LargestFiniteVal * SmallerType.LargestFiniteVal // doubles the exponent of SmallerType.LargestFiniteVal) if (llvm::APFloat::semanticsMaxExponent(elementTypeSemantics) * 2 + 1 <= llvm::APFloat::semanticsMaxExponent(higherElementTypeSemantics)) { return higherElementType; } // The intermediate values can't be represented in the promoted type // without overflowing. return {}; } static mlir::Value lowerComplexDiv(LoweringPreparePass &pass, CIRBaseBuilderTy &builder, mlir::Location loc, cir::ComplexDivOp op, mlir::Value lhsReal, mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag, mlir::MLIRContext &mlirCx, clang::ASTContext &cc) { cir::ComplexType complexTy = op.getType(); if (mlir::isa(complexTy.getElementType())) { cir::ComplexRangeKind range = op.getRange(); if (range == cir::ComplexRangeKind::Improved) return buildRangeReductionComplexDiv(builder, loc, lhsReal, lhsImag, rhsReal, rhsImag); if (range == cir::ComplexRangeKind::Full) return buildComplexBinOpLibCall(pass, builder, &getComplexDivLibCallName, loc, complexTy, lhsReal, lhsImag, rhsReal, rhsImag); if (range == cir::ComplexRangeKind::Promoted) { mlir::Type originalElementType = complexTy.getElementType(); mlir::Type higherPrecisionElementType = higherPrecisionElementTypeForComplexArithmetic(mlirCx, cc, builder, originalElementType); if (!higherPrecisionElementType) return buildRangeReductionComplexDiv(builder, loc, lhsReal, lhsImag, rhsReal, rhsImag); cir::CastKind floatingCastKind = cir::CastKind::floating; lhsReal = builder.createCast(floatingCastKind, lhsReal, higherPrecisionElementType); lhsImag = builder.createCast(floatingCastKind, lhsImag, higherPrecisionElementType); rhsReal = builder.createCast(floatingCastKind, rhsReal, higherPrecisionElementType); rhsImag = builder.createCast(floatingCastKind, rhsImag, higherPrecisionElementType); mlir::Value algebraicResult = buildAlgebraicComplexDiv( builder, loc, lhsReal, lhsImag, rhsReal, rhsImag); mlir::Value resultReal = builder.createComplexReal(loc, algebraicResult); mlir::Value resultImag = builder.createComplexImag(loc, algebraicResult); mlir::Value finalReal = builder.createCast(floatingCastKind, resultReal, originalElementType); mlir::Value finalImag = builder.createCast(floatingCastKind, resultImag, originalElementType); return builder.createComplexCreate(loc, finalReal, finalImag); } } return buildAlgebraicComplexDiv(builder, loc, lhsReal, lhsImag, rhsReal, rhsImag); } void LoweringPreparePass::lowerComplexDivOp(cir::ComplexDivOp op) { cir::CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointAfter(op); mlir::Location loc = op.getLoc(); mlir::TypedValue lhs = op.getLhs(); mlir::TypedValue rhs = op.getRhs(); mlir::Value lhsReal = builder.createComplexReal(loc, lhs); mlir::Value lhsImag = builder.createComplexImag(loc, lhs); mlir::Value rhsReal = builder.createComplexReal(loc, rhs); mlir::Value rhsImag = builder.createComplexImag(loc, rhs); mlir::Value loweredResult = lowerComplexDiv(*this, builder, loc, op, lhsReal, lhsImag, rhsReal, rhsImag, getContext(), *astCtx); op.replaceAllUsesWith(loweredResult); op.erase(); } static llvm::StringRef getComplexMulLibCallName(llvm::APFloat::Semantics semantics) { switch (semantics) { case llvm::APFloat::S_IEEEhalf: return "__mulhc3"; case llvm::APFloat::S_IEEEsingle: return "__mulsc3"; case llvm::APFloat::S_IEEEdouble: return "__muldc3"; case llvm::APFloat::S_PPCDoubleDouble: return "__multc3"; case llvm::APFloat::S_x87DoubleExtended: return "__mulxc3"; case llvm::APFloat::S_IEEEquad: return "__multc3"; default: llvm_unreachable("unsupported floating point type"); } } static mlir::Value lowerComplexMul(LoweringPreparePass &pass, CIRBaseBuilderTy &builder, mlir::Location loc, cir::ComplexMulOp op, mlir::Value lhsReal, mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag) { // (a+bi) * (c+di) = (ac-bd) + (ad+bc)i mlir::Value resultRealLhs = builder.createBinop(loc, lhsReal, cir::BinOpKind::Mul, rhsReal); mlir::Value resultRealRhs = builder.createBinop(loc, lhsImag, cir::BinOpKind::Mul, rhsImag); mlir::Value resultImagLhs = builder.createBinop(loc, lhsReal, cir::BinOpKind::Mul, rhsImag); mlir::Value resultImagRhs = builder.createBinop(loc, lhsImag, cir::BinOpKind::Mul, rhsReal); mlir::Value resultReal = builder.createBinop( loc, resultRealLhs, cir::BinOpKind::Sub, resultRealRhs); mlir::Value resultImag = builder.createBinop( loc, resultImagLhs, cir::BinOpKind::Add, resultImagRhs); mlir::Value algebraicResult = builder.createComplexCreate(loc, resultReal, resultImag); cir::ComplexType complexTy = op.getType(); cir::ComplexRangeKind rangeKind = op.getRange(); if (mlir::isa(complexTy.getElementType()) || rangeKind == cir::ComplexRangeKind::Basic || rangeKind == cir::ComplexRangeKind::Improved || rangeKind == cir::ComplexRangeKind::Promoted) return algebraicResult; assert(!cir::MissingFeatures::fastMathFlags()); // Check whether the real part and the imaginary part of the result are both // NaN. If so, emit a library call to compute the multiplication instead. // We check a value against NaN by comparing the value against itself. mlir::Value resultRealIsNaN = builder.createIsNaN(loc, resultReal); mlir::Value resultImagIsNaN = builder.createIsNaN(loc, resultImag); mlir::Value resultRealAndImagAreNaN = builder.createLogicalAnd(loc, resultRealIsNaN, resultImagIsNaN); return cir::TernaryOp::create( builder, loc, resultRealAndImagAreNaN, [&](mlir::OpBuilder &, mlir::Location) { mlir::Value libCallResult = buildComplexBinOpLibCall( pass, builder, &getComplexMulLibCallName, loc, complexTy, lhsReal, lhsImag, rhsReal, rhsImag); builder.createYield(loc, libCallResult); }, [&](mlir::OpBuilder &, mlir::Location) { builder.createYield(loc, algebraicResult); }) .getResult(); } void LoweringPreparePass::lowerComplexMulOp(cir::ComplexMulOp op) { cir::CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointAfter(op); mlir::Location loc = op.getLoc(); mlir::TypedValue lhs = op.getLhs(); mlir::TypedValue rhs = op.getRhs(); mlir::Value lhsReal = builder.createComplexReal(loc, lhs); mlir::Value lhsImag = builder.createComplexImag(loc, lhs); mlir::Value rhsReal = builder.createComplexReal(loc, rhs); mlir::Value rhsImag = builder.createComplexImag(loc, rhs); mlir::Value loweredResult = lowerComplexMul(*this, builder, loc, op, lhsReal, lhsImag, rhsReal, rhsImag); op.replaceAllUsesWith(loweredResult); op.erase(); } void LoweringPreparePass::lowerUnaryOp(cir::UnaryOp op) { mlir::Type ty = op.getType(); if (!mlir::isa(ty)) return; mlir::Location loc = op.getLoc(); cir::UnaryOpKind opKind = op.getKind(); CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointAfter(op); mlir::Value operand = op.getInput(); mlir::Value operandReal = builder.createComplexReal(loc, operand); mlir::Value operandImag = builder.createComplexImag(loc, operand); mlir::Value resultReal; mlir::Value resultImag; switch (opKind) { case cir::UnaryOpKind::Inc: case cir::UnaryOpKind::Dec: resultReal = builder.createUnaryOp(loc, opKind, operandReal); resultImag = operandImag; break; case cir::UnaryOpKind::Plus: case cir::UnaryOpKind::Minus: resultReal = builder.createUnaryOp(loc, opKind, operandReal); resultImag = builder.createUnaryOp(loc, opKind, operandImag); break; case cir::UnaryOpKind::Not: resultReal = operandReal; resultImag = builder.createUnaryOp(loc, cir::UnaryOpKind::Minus, operandImag); break; } mlir::Value result = builder.createComplexCreate(loc, resultReal, resultImag); op.replaceAllUsesWith(result); op.erase(); } cir::FuncOp LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder, cir::GlobalOp op, mlir::Region &dtorRegion, cir::CallOp &dtorCall) { mlir::OpBuilder::InsertionGuard guard(builder); assert(!cir::MissingFeatures::astVarDeclInterface()); assert(!cir::MissingFeatures::opGlobalThreadLocal()); cir::VoidType voidTy = builder.getVoidTy(); auto voidPtrTy = cir::PointerType::get(voidTy); // Look for operations in dtorBlock mlir::Block &dtorBlock = dtorRegion.front(); // The first operation should be a get_global to retrieve the address // of the global variable we're destroying. auto opIt = dtorBlock.getOperations().begin(); cir::GetGlobalOp ggop = mlir::cast(*opIt); // The simple case is just a call to a destructor, like this: // // %0 = cir.get_global %globalS : !cir.ptr // cir.call %_ZN1SD1Ev(%0) : (!cir.ptr) -> () // (implicit cir.yield) // // That is, if the second operation is a call that takes the get_global result // as its only operand, and the only other operation is a yield, then we can // just return the called function. if (dtorBlock.getOperations().size() == 3) { auto callOp = mlir::dyn_cast(&*(++opIt)); auto yieldOp = mlir::dyn_cast(&*(++opIt)); if (yieldOp && callOp && callOp.getNumOperands() == 1 && callOp.getArgOperand(0) == ggop) { dtorCall = callOp; return getCalledFunction(callOp); } } // Otherwise, we need to create a helper function to replace the dtor region. // This name is kind of arbitrary, but it matches the name that classic // codegen uses, based on the expected case that gets us here. builder.setInsertionPointAfter(op); SmallString<256> fnName("__cxx_global_array_dtor"); uint32_t cnt = dynamicInitializerNames[fnName]++; if (cnt) fnName += "." + std::to_string(cnt); // Create the helper function. auto fnType = cir::FuncType::get({voidPtrTy}, voidTy); cir::FuncOp dtorFunc = buildRuntimeFunction(builder, fnName, op.getLoc(), fnType, cir::GlobalLinkageKind::InternalLinkage); mlir::Block *entryBB = dtorFunc.addEntryBlock(); // Move everything from the dtor region into the helper function. entryBB->getOperations().splice(entryBB->begin(), dtorBlock.getOperations(), dtorBlock.begin(), dtorBlock.end()); // Before erasing this, clone it back into the dtor region cir::GetGlobalOp dtorGGop = mlir::cast(entryBB->getOperations().front()); builder.setInsertionPointToStart(&dtorBlock); builder.clone(*dtorGGop.getOperation()); // Replace all uses of the help function's get_global with the function // argument. mlir::Value dtorArg = entryBB->getArgument(0); dtorGGop.replaceAllUsesWith(dtorArg); dtorGGop.erase(); // Replace the yield in the final block with a return mlir::Block &finalBlock = dtorFunc.getBody().back(); auto yieldOp = cast(finalBlock.getTerminator()); builder.setInsertionPoint(yieldOp); cir::ReturnOp::create(builder, yieldOp->getLoc()); yieldOp->erase(); // Create a call to the helper function, passing the original get_global op // as the argument. cir::GetGlobalOp origGGop = mlir::cast(dtorBlock.getOperations().front()); builder.setInsertionPointAfter(origGGop); mlir::Value ggopResult = origGGop.getResult(); dtorCall = builder.createCallOp(op.getLoc(), dtorFunc, ggopResult); // Add a yield after the call. auto finalYield = cir::YieldOp::create(builder, op.getLoc()); // Erase everything after the yield. dtorBlock.getOperations().erase(std::next(mlir::Block::iterator(finalYield)), dtorBlock.end()); dtorRegion.getBlocks().erase(std::next(dtorRegion.begin()), dtorRegion.end()); return dtorFunc; } cir::FuncOp LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { // TODO(cir): Store this in the GlobalOp. // This should come from the MangleContext, but for now I'm hardcoding it. SmallString<256> fnName("__cxx_global_var_init"); // Get a unique name uint32_t cnt = dynamicInitializerNames[fnName]++; if (cnt) fnName += "." + std::to_string(cnt); // Create a variable initialization function. CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointAfter(op); cir::VoidType voidTy = builder.getVoidTy(); auto fnType = cir::FuncType::get({}, voidTy); FuncOp f = buildRuntimeFunction(builder, fnName, op.getLoc(), fnType, cir::GlobalLinkageKind::InternalLinkage); // Move over the initialzation code of the ctor region. mlir::Block *entryBB = f.addEntryBlock(); if (!op.getCtorRegion().empty()) { mlir::Block &block = op.getCtorRegion().front(); entryBB->getOperations().splice(entryBB->begin(), block.getOperations(), block.begin(), std::prev(block.end())); } // Register the destructor call with __cxa_atexit mlir::Region &dtorRegion = op.getDtorRegion(); if (!dtorRegion.empty()) { assert(!cir::MissingFeatures::astVarDeclInterface()); assert(!cir::MissingFeatures::opGlobalThreadLocal()); // Create a variable that binds the atexit to this shared object. builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front()); cir::GlobalOp handle = buildRuntimeVariable( builder, "__dso_handle", op.getLoc(), builder.getI8Type(), cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind::Hidden); // If this is a simple call to a destructor, get the called function. // Otherwise, create a helper function for the entire dtor region, // replacing the current dtor region body with a call to the helper // function. cir::CallOp dtorCall; cir::FuncOp dtorFunc = getOrCreateDtorFunc(builder, op, dtorRegion, dtorCall); // Create a runtime helper function: // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d); auto voidPtrTy = cir::PointerType::get(voidTy); auto voidFnTy = cir::FuncType::get({voidPtrTy}, voidTy); auto voidFnPtrTy = cir::PointerType::get(voidFnTy); auto handlePtrTy = cir::PointerType::get(handle.getSymType()); auto fnAtExitType = cir::FuncType::get({voidFnPtrTy, voidPtrTy, handlePtrTy}, voidTy); const char *nameAtExit = "__cxa_atexit"; cir::FuncOp fnAtExit = buildRuntimeFunction(builder, nameAtExit, op.getLoc(), fnAtExitType); // Replace the dtor (or helper) call with a call to // __cxa_atexit(&dtor, &var, &__dso_handle) builder.setInsertionPointAfter(dtorCall); mlir::Value args[3]; auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType()); // dtorPtrTy args[0] = cir::GetGlobalOp::create(builder, dtorCall.getLoc(), dtorPtrTy, dtorFunc.getSymName()); args[0] = cir::CastOp::create(builder, dtorCall.getLoc(), voidFnPtrTy, cir::CastKind::bitcast, args[0]); args[1] = cir::CastOp::create(builder, dtorCall.getLoc(), voidPtrTy, cir::CastKind::bitcast, dtorCall.getArgOperand(0)); args[2] = cir::GetGlobalOp::create(builder, handle.getLoc(), handlePtrTy, handle.getSymName()); builder.createCallOp(dtorCall.getLoc(), fnAtExit, args); dtorCall->erase(); mlir::Block &dtorBlock = dtorRegion.front(); entryBB->getOperations().splice(entryBB->end(), dtorBlock.getOperations(), dtorBlock.begin(), std::prev(dtorBlock.end())); } // Replace cir.yield with cir.return builder.setInsertionPointToEnd(entryBB); mlir::Operation *yieldOp = nullptr; if (!op.getCtorRegion().empty()) { mlir::Block &block = op.getCtorRegion().front(); yieldOp = &block.getOperations().back(); } else { assert(!dtorRegion.empty()); mlir::Block &block = dtorRegion.front(); yieldOp = &block.getOperations().back(); } assert(isa(*yieldOp)); cir::ReturnOp::create(builder, yieldOp->getLoc()); return f; } void LoweringPreparePass::lowerGlobalOp(GlobalOp op) { mlir::Region &ctorRegion = op.getCtorRegion(); mlir::Region &dtorRegion = op.getDtorRegion(); if (!ctorRegion.empty() || !dtorRegion.empty()) { // Build a variable initialization function and move the initialzation code // in the ctor region over. cir::FuncOp f = buildCXXGlobalVarDeclInitFunc(op); // Clear the ctor and dtor region ctorRegion.getBlocks().clear(); dtorRegion.getBlocks().clear(); assert(!cir::MissingFeatures::astVarDeclInterface()); dynamicInitializers.push_back(f); } assert(!cir::MissingFeatures::opGlobalAnnotations()); } template static llvm::SmallVector prepareCtorDtorAttrList(mlir::MLIRContext *context, llvm::ArrayRef> list) { llvm::SmallVector attrs; for (const auto &[name, priority] : list) attrs.push_back(AttributeTy::get(context, name, priority)); return attrs; } void LoweringPreparePass::buildGlobalCtorDtorList() { if (!globalCtorList.empty()) { llvm::SmallVector globalCtors = prepareCtorDtorAttrList(&getContext(), globalCtorList); mlirModule->setAttr(cir::CIRDialect::getGlobalCtorsAttrName(), mlir::ArrayAttr::get(&getContext(), globalCtors)); } if (!globalDtorList.empty()) { llvm::SmallVector globalDtors = prepareCtorDtorAttrList(&getContext(), globalDtorList); mlirModule->setAttr(cir::CIRDialect::getGlobalDtorsAttrName(), mlir::ArrayAttr::get(&getContext(), globalDtors)); } } void LoweringPreparePass::buildCXXGlobalInitFunc() { if (dynamicInitializers.empty()) return; // TODO: handle globals with a user-specified initialzation priority. // TODO: handle default priority more nicely. assert(!cir::MissingFeatures::opGlobalCtorPriority()); SmallString<256> fnName; // Include the filename in the symbol name. Including "sub_" matches gcc // and makes sure these symbols appear lexicographically behind the symbols // with priority (TBD). Module implementation units behave the same // way as a non-modular TU with imports. // TODO: check CXX20ModuleInits if (astCtx->getCurrentNamedModule() && !astCtx->getCurrentNamedModule()->isModuleImplementation()) { llvm::raw_svector_ostream out(fnName); std::unique_ptr mangleCtx( astCtx->createMangleContext()); cast(*mangleCtx) .mangleModuleInitializer(astCtx->getCurrentNamedModule(), out); } else { fnName += "_GLOBAL__sub_I_"; fnName += getTransformedFileName(mlirModule); } CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointToEnd(&mlirModule.getBodyRegion().back()); auto fnType = cir::FuncType::get({}, builder.getVoidTy()); cir::FuncOp f = buildRuntimeFunction(builder, fnName, mlirModule.getLoc(), fnType, cir::GlobalLinkageKind::ExternalLinkage); builder.setInsertionPointToStart(f.addEntryBlock()); for (cir::FuncOp &f : dynamicInitializers) builder.createCallOp(f.getLoc(), f, {}); // Add the global init function (not the individual ctor functions) to the // global ctor list. globalCtorList.emplace_back(fnName, cir::GlobalCtorAttr::getDefaultPriority()); cir::ReturnOp::create(builder, f.getLoc()); } void LoweringPreparePass::lowerDynamicCastOp(DynamicCastOp op) { CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointAfter(op); assert(astCtx && "AST context is not available during lowering prepare"); auto loweredValue = cxxABI->lowerDynamicCast(builder, *astCtx, op); op.replaceAllUsesWith(loweredValue); op.erase(); } static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder, clang::ASTContext *astCtx, mlir::Operation *op, mlir::Type eltTy, mlir::Value arrayAddr, uint64_t arrayLen, bool isCtor) { // Generate loop to call into ctor/dtor for every element. mlir::Location loc = op->getLoc(); // TODO: instead of getting the size from the AST context, create alias for // PtrDiffTy and unify with CIRGen stuff. const unsigned sizeTypeSize = astCtx->getTypeSize(astCtx->getSignedSizeType()); uint64_t endOffset = isCtor ? arrayLen : arrayLen - 1; mlir::Value endOffsetVal = builder.getUnsignedInt(loc, endOffset, sizeTypeSize); auto begin = cir::CastOp::create(builder, loc, eltTy, cir::CastKind::array_to_ptrdecay, arrayAddr); mlir::Value end = cir::PtrStrideOp::create(builder, loc, eltTy, begin, endOffsetVal); mlir::Value start = isCtor ? begin : end; mlir::Value stop = isCtor ? end : begin; mlir::Value tmpAddr = builder.createAlloca( loc, /*addr type*/ builder.getPointerTo(eltTy), /*var type*/ eltTy, "__array_idx", builder.getAlignmentAttr(1)); builder.createStore(loc, start, tmpAddr); cir::DoWhileOp loop = builder.createDoWhile( loc, /*condBuilder=*/ [&](mlir::OpBuilder &b, mlir::Location loc) { auto currentElement = cir::LoadOp::create(b, loc, eltTy, tmpAddr); mlir::Type boolTy = cir::BoolType::get(b.getContext()); auto cmp = cir::CmpOp::create(builder, loc, boolTy, cir::CmpOpKind::ne, currentElement, stop); builder.createCondition(cmp); }, /*bodyBuilder=*/ [&](mlir::OpBuilder &b, mlir::Location loc) { auto currentElement = cir::LoadOp::create(b, loc, eltTy, tmpAddr); cir::CallOp ctorCall; op->walk([&](cir::CallOp c) { ctorCall = c; }); assert(ctorCall && "expected ctor call"); // Array elements get constructed in order but destructed in reverse. mlir::Value stride; if (isCtor) stride = builder.getUnsignedInt(loc, 1, sizeTypeSize); else stride = builder.getSignedInt(loc, -1, sizeTypeSize); ctorCall->moveBefore(stride.getDefiningOp()); ctorCall->setOperand(0, currentElement); auto nextElement = cir::PtrStrideOp::create(builder, loc, eltTy, currentElement, stride); // Store the element pointer to the temporary variable builder.createStore(loc, nextElement, tmpAddr); builder.createYield(loc); }); op->replaceAllUsesWith(loop); op->erase(); } void LoweringPreparePass::lowerArrayDtor(cir::ArrayDtor op) { CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointAfter(op.getOperation()); mlir::Type eltTy = op->getRegion(0).getArgument(0).getType(); assert(!cir::MissingFeatures::vlas()); auto arrayLen = mlir::cast(op.getAddr().getType().getPointee()).getSize(); lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(), arrayLen, false); } void LoweringPreparePass::lowerArrayCtor(cir::ArrayCtor op) { cir::CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointAfter(op.getOperation()); mlir::Type eltTy = op->getRegion(0).getArgument(0).getType(); assert(!cir::MissingFeatures::vlas()); auto arrayLen = mlir::cast(op.getAddr().getType().getPointee()).getSize(); lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(), arrayLen, true); } void LoweringPreparePass::runOnOp(mlir::Operation *op) { if (auto arrayCtor = dyn_cast(op)) { lowerArrayCtor(arrayCtor); } else if (auto arrayDtor = dyn_cast(op)) { lowerArrayDtor(arrayDtor); } else if (auto cast = mlir::dyn_cast(op)) { lowerCastOp(cast); } else if (auto complexDiv = mlir::dyn_cast(op)) { lowerComplexDivOp(complexDiv); } else if (auto complexMul = mlir::dyn_cast(op)) { lowerComplexMulOp(complexMul); } else if (auto glob = mlir::dyn_cast(op)) { lowerGlobalOp(glob); } else if (auto dynamicCast = mlir::dyn_cast(op)) { lowerDynamicCastOp(dynamicCast); } else if (auto unary = mlir::dyn_cast(op)) { lowerUnaryOp(unary); } else if (auto fnOp = dyn_cast(op)) { if (auto globalCtor = fnOp.getGlobalCtorPriority()) globalCtorList.emplace_back(fnOp.getName(), globalCtor.value()); else if (auto globalDtor = fnOp.getGlobalDtorPriority()) globalDtorList.emplace_back(fnOp.getName(), globalDtor.value()); } } void LoweringPreparePass::runOnOperation() { mlir::Operation *op = getOperation(); if (isa<::mlir::ModuleOp>(op)) mlirModule = cast<::mlir::ModuleOp>(op); llvm::SmallVector opsToTransform; op->walk([&](mlir::Operation *op) { if (mlir::isa(op)) opsToTransform.push_back(op); }); for (mlir::Operation *o : opsToTransform) runOnOp(o); buildCXXGlobalInitFunc(); buildGlobalCtorDtorList(); } std::unique_ptr mlir::createLoweringPreparePass() { return std::make_unique(); } std::unique_ptr mlir::createLoweringPreparePass(clang::ASTContext *astCtx) { auto pass = std::make_unique(); pass->setASTContext(astCtx); return std::move(pass); }