diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 7e59989dc09f1..3e25101de40c6 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -246,7 +246,6 @@ struct MissingFeatures { static bool metaDataNode() { return false; } static bool moduleNameHash() { return false; } static bool msabi() { return false; } - static bool needsGlobalCtorDtor() { return false; } static bool nrvo() { return false; } static bool objCBlocks() { return false; } static bool objCGC() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index da507d6f28335..d5b35c25c83ba 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -15,10 +15,89 @@ #include "clang/AST/GlobalDecl.h" #include "clang/CIR/MissingFeatures.h" +#include "llvm/Support/SaveAndRestore.h" using namespace clang; using namespace clang::CIRGen; +static void emitDeclInit(CIRGenFunction &cgf, const VarDecl *varDecl, + cir::GlobalOp globalOp) { + assert((varDecl->hasGlobalStorage() || + (varDecl->hasLocalStorage() && + cgf.getContext().getLangOpts().OpenCLCPlusPlus)) && + "VarDecl must have global or local (in the case of OpenCL) storage!"); + assert(!varDecl->getType()->isReferenceType() && + "Should not call emitDeclInit on a reference!"); + + CIRGenBuilderTy &builder = cgf.getBuilder(); + + // Set up the ctor region. + mlir::OpBuilder::InsertionGuard guard(builder); + mlir::Block *block = builder.createBlock(&globalOp.getCtorRegion()); + CIRGenFunction::LexicalScope lexScope{cgf, globalOp.getLoc(), + builder.getInsertionBlock()}; + lexScope.setAsGlobalInit(); + builder.setInsertionPointToStart(block); + + Address declAddr(cgf.cgm.getAddrOfGlobalVar(varDecl), + cgf.cgm.getASTContext().getDeclAlign(varDecl)); + + QualType type = varDecl->getType(); + LValue lv = cgf.makeAddrLValue(declAddr, type); + + const Expr *init = varDecl->getInit(); + switch (CIRGenFunction::getEvaluationKind(type)) { + case cir::TEK_Scalar: + assert(!cir::MissingFeatures::objCGC()); + cgf.emitScalarInit(init, cgf.getLoc(varDecl->getLocation()), lv, false); + break; + case cir::TEK_Complex: + cgf.cgm.errorNYI(varDecl->getSourceRange(), "complex global initializer"); + break; + case cir::TEK_Aggregate: + assert(!cir::MissingFeatures::aggValueSlotGC()); + cgf.emitAggExpr(init, + AggValueSlot::forLValue(lv, AggValueSlot::IsDestructed, + AggValueSlot::IsNotAliased, + AggValueSlot::DoesNotOverlap)); + break; + } + + // Finish the ctor region. + builder.setInsertionPointToEnd(block); + cir::YieldOp::create(builder, globalOp.getLoc()); +} + +static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd, + cir::GlobalOp addr) { + // Honor __attribute__((no_destroy)) and bail instead of attempting + // to emit a reference to a possibly nonexistent destructor, which + // in turn can cause a crash. This will result in a global constructor + // that isn't balanced out by a destructor call as intended by the + // attribute. This also checks for -fno-c++-static-destructors and + // bails even if the attribute is not present. + QualType::DestructionKind dtorKind = vd->needsDestruction(cgf.getContext()); + + // FIXME: __attribute__((cleanup)) ? + + switch (dtorKind) { + case QualType::DK_none: + return; + + case QualType::DK_cxx_destructor: + break; + + case QualType::DK_objc_strong_lifetime: + case QualType::DK_objc_weak_lifetime: + case QualType::DK_nontrivial_c_struct: + // We don't care about releasing objects during process teardown. + assert(!vd->getTLSKind() && "should have rejected this"); + return; + } + + cgf.cgm.errorNYI(vd->getSourceRange(), "global with destructor"); +} + cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl gd) { const CIRGenFunctionInfo &fnInfo = getTypes().arrangeCXXStructorDeclaration(gd); @@ -38,3 +117,63 @@ cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl gd) { assert(!cir::MissingFeatures::opFuncAttributesForDefinition()); return fn; } + +// Global variables requiring non-trivial initialization are handled +// differently in CIR than in classic codegen. Classic codegen emits +// a global init function (__cxx_global_var_init) and inserts +// initialization for each global there. In CIR, we attach a ctor +// region to the global variable and insert the initialization code +// into the ctor region. This will be moved into the +// __cxx_global_var_init function during the LoweringPrepare pass. +void CIRGenModule::emitCXXGlobalVarDeclInit(const VarDecl *varDecl, + cir::GlobalOp addr, + bool performInit) { + QualType ty = varDecl->getType(); + + // TODO: handle address space + // The address space of a static local variable (addr) may be different + // from the address space of the "this" argument of the constructor. In that + // case, we need an addrspacecast before calling the constructor. + // + // struct StructWithCtor { + // __device__ StructWithCtor() {...} + // }; + // __device__ void foo() { + // __shared__ StructWithCtor s; + // ... + // } + // + // For example, in the above CUDA code, the static local variable s has a + // "shared" address space qualifier, but the constructor of StructWithCtor + // expects "this" in the "generic" address space. + assert(!cir::MissingFeatures::addressSpace()); + + // Create a CIRGenFunction to emit the initializer. While this isn't a true + // function, the handling works the same way. + CIRGenFunction cgf{*this, builder, true}; + llvm::SaveAndRestore savedCGF(curCGF, &cgf); + curCGF->curFn = addr; + + CIRGenFunction::SourceLocRAIIObject fnLoc{cgf, + getLoc(varDecl->getLocation())}; + + assert(!cir::MissingFeatures::astVarDeclInterface()); + + if (!ty->isReferenceType()) { + assert(!cir::MissingFeatures::openMP()); + + bool needsDtor = varDecl->needsDestruction(getASTContext()) == + QualType::DK_cxx_destructor; + // PerformInit, constant store invariant / destroy handled below. + if (performInit) + emitDeclInit(cgf, varDecl, addr); + + if (varDecl->getType().isConstantStorage(getASTContext(), true, !needsDtor)) + errorNYI(varDecl->getSourceRange(), "global with constant storage"); + else + emitDeclDestroy(cgf, varDecl, addr); + return; + } + + errorNYI(varDecl->getSourceRange(), "global with reference type"); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp new file mode 100644 index 0000000000000..d1efed80aaf0e --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// 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 contains code dealing with code generation of C++ declarations +// +//===----------------------------------------------------------------------===// + +#include "CIRGenModule.h" +#include "clang/AST/Attr.h" +#include "clang/Basic/LangOptions.h" + +using namespace clang; +using namespace clang::CIRGen; + +void CIRGenModule::emitCXXGlobalVarDeclInitFunc(const VarDecl *vd, + cir::GlobalOp addr, + bool performInit) { + assert(!cir::MissingFeatures::cudaSupport()); + + assert(!cir::MissingFeatures::deferredCXXGlobalInit()); + + emitCXXGlobalVarDeclInit(vd, addr, performInit); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index 178b276f19d41..e20a4fc3c63aa 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -775,7 +775,9 @@ class ConstExprEmitter } mlir::Attribute VisitCXXConstructExpr(CXXConstructExpr *e, QualType ty) { - cgm.errorNYI(e->getBeginLoc(), "ConstExprEmitter::VisitCXXConstructExpr"); + if (!e->getConstructor()->isTrivial()) + return nullptr; + cgm.errorNYI(e->getBeginLoc(), "trivial constructor const handling"); return {}; } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 0abb21a670719..e68ce99dbdc74 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -342,10 +342,12 @@ void CIRGenFunction::LexicalScope::cleanup() { cir::ReturnOp CIRGenFunction::LexicalScope::emitReturn(mlir::Location loc) { CIRGenBuilderTy &builder = cgf.getBuilder(); - if (!cgf.curFn.getFunctionType().hasVoidReturn()) { + auto fn = dyn_cast(cgf.curFn); + assert(fn && "emitReturn from non-function"); + if (!fn.getFunctionType().hasVoidReturn()) { // Load the value from `__retval` and return it via the `cir.return` op. auto value = builder.create( - loc, cgf.curFn.getFunctionType().getReturnType(), *cgf.fnRetAlloca); + loc, fn.getFunctionType().getReturnType(), *cgf.fnRetAlloca); return builder.create(loc, llvm::ArrayRef(value.getResult())); } @@ -459,7 +461,9 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType, const auto *md = cast(d); if (md->getParent()->isLambda() && md->getOverloadedOperator() == OO_Call) { // We're in a lambda. - curFn.setLambda(true); + auto fn = dyn_cast(curFn); + assert(fn && "lambda in non-function region"); + fn.setLambda(true); // Figure out the captures. md->getParent()->getCaptureFields(lambdaCaptureFields, diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index ef07db3d48ffc..c0ed8b4006ec5 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -98,8 +98,10 @@ class CIRGenFunction : public CIRGenTypeCache { /// This is the inner-most code context, which includes blocks. const clang::Decl *curCodeDecl = nullptr; - /// The function for which code is currently being generated. - cir::FuncOp curFn; + /// The current function or global initializer that is generated code for. + /// This is usually a cir::FuncOp, but it can also be a cir::GlobalOp for + /// global initializers. + mlir::Operation *curFn = nullptr; using DeclMapTy = llvm::DenseMap; /// This keeps track of the CIR allocas or globals for local C @@ -116,7 +118,11 @@ class CIRGenFunction : public CIRGenTypeCache { CIRGenModule &getCIRGenModule() { return cgm; } const CIRGenModule &getCIRGenModule() const { return cgm; } - mlir::Block *getCurFunctionEntryBlock() { return &curFn.getRegion().front(); } + mlir::Block *getCurFunctionEntryBlock() { + // We currently assume this isn't called for a global initializer. + auto fn = mlir::cast(curFn); + return &fn.getRegion().front(); + } /// Sanitizers enabled for this function. clang::SanitizerSet sanOpts; diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index c977ff9f06de6..5dc4335aeb6ad 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -730,7 +730,6 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd, // since this is the job for its original source. bool isDefinitionAvailableExternally = astContext.GetGVALinkageForVariable(vd) == GVA_AvailableExternally; - assert(!cir::MissingFeatures::needsGlobalCtorDtor()); // It is useless to emit the definition for an available_externally variable // which can't be marked as const. @@ -743,6 +742,10 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd, return; mlir::Attribute init; + bool needsGlobalCtor = false; + bool needsGlobalDtor = + !isDefinitionAvailableExternally && + vd->needsDestruction(astContext) == QualType::DK_cxx_destructor; const VarDecl *initDecl; const Expr *initExpr = vd->getAnyInitializer(initDecl); @@ -777,8 +780,8 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd, if (initDecl->hasFlexibleArrayInit(astContext)) errorNYI(vd->getSourceRange(), "flexible array initializer"); init = builder.getZeroInitAttr(convertType(qt)); - if (astContext.GetGVALinkageForVariable(vd) != GVA_AvailableExternally) - errorNYI(vd->getSourceRange(), "global constructor"); + if (!isDefinitionAvailableExternally) + needsGlobalCtor = true; } else { errorNYI(vd->getSourceRange(), "static initializer"); } @@ -787,8 +790,7 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd, // We don't need an initializer, so remove the entry for the delayed // initializer position (just in case this entry was delayed) if we // also don't need to register a destructor. - if (vd->needsDestruction(astContext) == QualType::DK_cxx_destructor) - errorNYI(vd->getSourceRange(), "delayed destructor"); + assert(!cir::MissingFeatures::deferredCXXGlobalInit()); } } @@ -827,6 +829,9 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd, if (emitter) emitter->finalize(gv); + assert(!cir::MissingFeatures::opGlobalConstant()); + assert(!cir::MissingFeatures::opGlobalSection()); + // Set CIR's linkage type as appropriate. cir::GlobalLinkageKind linkage = getCIRLinkageVarDefinition(vd, /*IsConstant=*/false); @@ -844,6 +849,10 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd, assert(!cir::MissingFeatures::opGlobalThreadLocal()); maybeSetTrivialComdat(*vd, gv); + + // Emit the initializer function if necessary. + if (needsGlobalCtor || needsGlobalDtor) + emitCXXGlobalVarDeclInitFunc(vd, gv, needsGlobalCtor); } void CIRGenModule::emitGlobalDefinition(clang::GlobalDecl gd, diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 073e8d96b773b..7630daabfb3a4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -408,6 +408,13 @@ class CIRGenModule : public CIRGenTypeCache { void emitGlobalVarDefinition(const clang::VarDecl *vd, bool isTentative = false); + /// Emit the function that initializes the specified global + void emitCXXGlobalVarDeclInit(const VarDecl *varDecl, cir::GlobalOp addr, + bool performInit); + + void emitCXXGlobalVarDeclInitFunc(const VarDecl *vd, cir::GlobalOp addr, + bool performInit); + void emitGlobalOpenACCDecl(const clang::OpenACCConstructDecl *cd); // C++ related functions. diff --git a/clang/lib/CIR/CodeGen/CMakeLists.txt b/clang/lib/CIR/CodeGen/CMakeLists.txt index c1f27ec8ba858..3ebf460f7d34c 100644 --- a/clang/lib/CIR/CodeGen/CMakeLists.txt +++ b/clang/lib/CIR/CodeGen/CMakeLists.txt @@ -18,6 +18,7 @@ add_clang_library(clangCIR CIRGenCXXABI.cpp CIRGenBuiltin.cpp CIRGenDecl.cpp + CIRGenDeclCXX.cpp CIRGenDeclOpenACC.cpp CIRGenException.cpp CIRGenExpr.cpp diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 876948d53010b..1edec057e6307 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -1710,6 +1710,11 @@ CIRToLLVMGlobalOpLowering::matchAndRewriteRegionInitializedGlobal( 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(); diff --git a/clang/test/CIR/CodeGen/global-init.cpp b/clang/test/CIR/CodeGen/global-init.cpp new file mode 100644 index 0000000000000..102affc5563ac --- /dev/null +++ b/clang/test/CIR/CodeGen/global-init.cpp @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR + +// Note: The CIR generated from this test isn't ready for lowering to LLVM yet. +// That will require changes to LoweringPrepare. + +struct NeedsCtor { + NeedsCtor(); +}; + +NeedsCtor needsCtor; + +// CIR: cir.func private @_ZN9NeedsCtorC1Ev(!cir.ptr) +// CIR: cir.global external @needsCtor = ctor : !rec_NeedsCtor { +// CIR: %[[THIS:.*]] = cir.get_global @needsCtor : !cir.ptr +// CIR: cir.call @_ZN9NeedsCtorC1Ev(%[[THIS]]) : (!cir.ptr) -> () +// CIR: }