-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[CIR] Defer definitions of global variables until they are used. #142496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-clangir Author: Andy Kaylor (andykaylor) ChangesThis change adds support for deferring global variable definitions until first use whenever it is possible to do so. Although deferring function definitions uses much of the same implementation, function deferral will be added in a follow-up change. Full diff: https://github.com/llvm/llvm-project/pull/142496.diff 6 Files Affected:
diff --git a/clang/include/clang/CIR/CIRGenerator.h b/clang/include/clang/CIR/CIRGenerator.h
index bb20fdf72693b..38e031c9a68d0 100644
--- a/clang/include/clang/CIR/CIRGenerator.h
+++ b/clang/include/clang/CIR/CIRGenerator.h
@@ -54,6 +54,7 @@ class CIRGenerator : public clang::ASTConsumer {
~CIRGenerator() override;
void Initialize(clang::ASTContext &astContext) override;
bool HandleTopLevelDecl(clang::DeclGroupRef group) override;
+ void HandleTranslationUnit(clang::ASTContext &astContext) override;
void CompleteTentativeDefinition(clang::VarDecl *d) override;
mlir::ModuleOp getModule() const;
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 3b82de882953c..5609cac46910d 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -137,6 +137,11 @@ struct MissingFeatures {
static bool recordZeroInit() { return false; }
static bool zeroSizeRecordMembers() { return false; }
+ // Various handling of deferred processing in CIRGenModule.
+ static bool cgmRelease() { return false; }
+ static bool deferredVtables() { return false; }
+ static bool deferredFuncDecls() { return false; }
+
// Misc
static bool cxxABI() { return false; }
static bool cirgenABIInfo() { return false; }
@@ -201,7 +206,7 @@ struct MissingFeatures {
static bool writebacks() { return false; }
static bool cleanupsToDeactivate() { return false; }
static bool stackBase() { return false; }
- static bool deferredDecls() { return false; }
+ static bool deferredCXXGlobalInit() { return false; }
static bool setTargetAttributes() { return false; }
// Missing types
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 9da7fcc0f89d5..11be07d36ab57 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -210,6 +210,99 @@ mlir::Location CIRGenModule::getLoc(SourceRange cRange) {
return mlir::FusedLoc::get({begin, end}, metadata, builder.getContext());
}
+mlir::Operation *
+CIRGenModule::getAddrOfGlobal(GlobalDecl gd, ForDefinition_t isForDefinition) {
+ const Decl *d = gd.getDecl();
+
+ if (isa<CXXConstructorDecl>(d) || isa<CXXDestructorDecl>(d)) {
+ errorNYI(d->getSourceRange(),
+ "getAddrOfGlobal: C++ constructor/destructor");
+ return nullptr;
+ }
+
+ if (isa<CXXMethodDecl>(d)) {
+ errorNYI(d->getSourceRange(), "getAddrOfGlobal: C++ method decl");
+ return nullptr;
+ }
+
+ if (isa<FunctionDecl>(d)) {
+ errorNYI(d->getSourceRange(), "getAddrOfGlobal: function decl");
+ return nullptr;
+ }
+
+ return getAddrOfGlobalVar(cast<VarDecl>(d), /*ty=*/nullptr, isForDefinition)
+ .getDefiningOp();
+}
+
+void CIRGenModule::emitGlobalDecl(const clang::GlobalDecl &d) {
+ // We call getAddrOfGlobal with isForDefinition set to ForDefinition in
+ // order to get a Value with exactly the type we need, not something that
+ // might have been created for another decl with the same mangled name but
+ // different type.
+ mlir::Operation *op = getAddrOfGlobal(d, ForDefinition);
+
+ // In case of different address spaces, we may still get a cast, even with
+ // IsForDefinition equal to ForDefinition. Query mangled names table to get
+ // GlobalValue.
+ if (!op)
+ op = getGlobalValue(getMangledName(d));
+
+ assert(op && "expected a valid global op");
+
+ // Check to see if we've already emitted this. This is necessary for a
+ // couple of reasons: first, decls can end up in deferred-decls queue
+ // multiple times, and second, decls can end up with definitions in unusual
+ // ways (e.g. by an extern inline function acquiring a strong function
+ // redefinition). Just ignore those cases.
+ // TODO: Not sure what to map this to for MLIR
+ mlir::Operation *globalValueOp = op;
+ if (auto gv = dyn_cast<cir::GetGlobalOp>(op))
+ globalValueOp =
+ mlir::SymbolTable::lookupSymbolIn(getModule(), gv.getNameAttr());
+
+ if (auto cirGlobalValue =
+ dyn_cast<cir::CIRGlobalValueInterface>(globalValueOp))
+ if (!cirGlobalValue.isDeclaration())
+ return;
+
+ // If this is OpenMP, check if it is legal to emit this global normally.
+ assert(!cir::MissingFeatures::openMP());
+
+ // Otherwise, emit the definition and move on to the next one.
+ emitGlobalDefinition(d, op);
+}
+
+void CIRGenModule::emitDeferred() {
+ // Emit code for any potentially referenced deferred decls. Since a previously
+ // unused static decl may become used during the generation of code for a
+ // static function, iterate until no changes are made.
+
+ assert(!cir::MissingFeatures::openMP());
+ assert(!cir::MissingFeatures::deferredVtables());
+ assert(!cir::MissingFeatures::cudaSupport());
+
+ // Stop if we're out of both deferred vtables and deferred declarations.
+ if (deferredDeclsToEmit.empty())
+ return;
+
+ // Grab the list of decls to emit. If emitGlobalDefinition schedules more
+ // work, it will not interfere with this.
+ std::vector<GlobalDecl> curDeclsToEmit;
+ curDeclsToEmit.swap(deferredDeclsToEmit);
+
+ for (const GlobalDecl &d : curDeclsToEmit) {
+ emitGlobalDecl(d);
+
+ // If we found out that we need to emit more decls, do that recursively.
+ // This has the advantage that the decls are emitted in a DFS and related
+ // ones are close together, which is convenient for testing.
+ if (!deferredDeclsToEmit.empty()) {
+ emitDeferred();
+ assert(deferredDeclsToEmit.empty());
+ }
+ }
+}
+
void CIRGenModule::emitGlobal(clang::GlobalDecl gd) {
if (const auto *cd = dyn_cast<clang::OpenACCConstructDecl>(gd.getDecl())) {
emitGlobalOpenACCDecl(cd);
@@ -248,8 +341,33 @@ void CIRGenModule::emitGlobal(clang::GlobalDecl gd) {
}
}
- // TODO(CIR): Defer emitting some global definitions until later
- emitGlobalDefinition(gd);
+ // Defer code generation to first use when possible, e.g. if this is an inline
+ // function. If the global must always be emitted, do it eagerly if possible
+ // to benefit from cache locality. Deferring code generation is necessary to
+ // avoid adding initializers to external declarations.
+ if (mustBeEmitted(global) && mayBeEmittedEagerly(global)) {
+ // Emit the definition if it can't be deferred.
+ emitGlobalDefinition(gd);
+ return;
+ }
+
+ // If we're deferring emission of a C++ variable with an initializer, remember
+ // the order in which it appeared on the file.
+ assert(!cir::MissingFeatures::deferredCXXGlobalInit());
+
+ llvm::StringRef mangledName = getMangledName(gd);
+ if (getGlobalValue(mangledName) != nullptr) {
+ // The value has already been used and should therefore be emitted.
+ addDeferredDeclToEmit(gd);
+ } else if (mustBeEmitted(global)) {
+ // The value must be emitted, but cannot be emitted eagerly.
+ assert(!mayBeEmittedEagerly(global));
+ addDeferredDeclToEmit(gd);
+ } else {
+ // Otherwise, remember that we saw a deferred decl with this name. The first
+ // use of the mangled name will cause it to move into deferredDeclsToEmit.
+ deferredDecls[mangledName] = gd;
+ }
}
void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
@@ -410,6 +528,17 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty,
CIRGenModule::createGlobalOp(*this, loc, mangledName, ty,
/*insertPoint=*/entry.getOperation());
+ // This is the first use or definition of a mangled name. If there is a
+ // deferred decl with this name, remember that we need to emit it at the end
+ // of the file.
+ auto ddi = deferredDecls.find(mangledName);
+ if (ddi != deferredDecls.end()) {
+ // Move the potentially referenced deferred decl to the DeferredDeclsToEmit
+ // list, and remove it from DeferredDecls (since we don't need it anymore).
+ addDeferredDeclToEmit(ddi->second);
+ deferredDecls.erase(ddi);
+ }
+
// Handle things which are present even on external declarations.
if (d) {
if (langOpts.OpenMP && !langOpts.OpenMPSimd)
@@ -1129,12 +1258,88 @@ void CIRGenModule::emitTentativeDefinition(const VarDecl *d) {
if (gv && !mlir::cast<cir::GlobalOp>(gv).isDeclaration())
return;
- assert(!cir::MissingFeatures::deferredDecls());
+ // If we have not seen a reference to this variable yet, place it into the
+ // deferred declarations table to be emitted if needed later.
+ if (!mustBeEmitted(d) && !gv) {
+ deferredDecls[mangledName] = d;
+ return;
+ }
// The tentative definition is the only definition.
emitGlobalVarDefinition(d);
}
+bool CIRGenModule::mustBeEmitted(const ValueDecl *global) {
+ // Never defer when EmitAllDecls is specified.
+ if (langOpts.EmitAllDecls)
+ return true;
+
+ const auto *vd = dyn_cast<VarDecl>(global);
+ if (vd &&
+ ((codeGenOpts.KeepPersistentStorageVariables &&
+ (vd->getStorageDuration() == SD_Static ||
+ vd->getStorageDuration() == SD_Thread)) ||
+ (codeGenOpts.KeepStaticConsts && vd->getStorageDuration() == SD_Static &&
+ vd->getType().isConstQualified())))
+ return true;
+
+ // TODO(cir): We do want to defer function decls, but it's not implemented.
+ assert(!cir::MissingFeatures::deferredFuncDecls());
+ if (isa<FunctionDecl>(global))
+ return true;
+
+ return getASTContext().DeclMustBeEmitted(global);
+}
+
+bool CIRGenModule::mayBeEmittedEagerly(const ValueDecl *global) {
+ // In OpenMP 5.0 variables and function may be marked as
+ // device_type(host/nohost) and we should not emit them eagerly unless we sure
+ // that they must be emitted on the host/device. To be sure we need to have
+ // seen a declare target with an explicit mentioning of the function, we know
+ // we have if the level of the declare target attribute is -1. Note that we
+ // check somewhere else if we should emit this at all.
+ if (langOpts.OpenMP >= 50 && !langOpts.OpenMPSimd) {
+ std::optional<OMPDeclareTargetDeclAttr *> activeAttr =
+ OMPDeclareTargetDeclAttr::getActiveAttr(global);
+ if (!activeAttr || (*activeAttr)->getLevel() != (unsigned)-1)
+ return false;
+ }
+
+ const auto *fd = dyn_cast<FunctionDecl>(global);
+ if (fd) {
+ // Implicit template instantiations may change linkage if they are later
+ // explicitly instantiated, so they should not be emitted eagerly.
+ if (fd->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
+ return false;
+ // Defer until all versions have been semantically checked.
+ if (fd->hasAttr<TargetVersionAttr>() && !fd->isMultiVersion())
+ return false;
+ if (langOpts.SYCLIsDevice) {
+ errorNYI(fd->getSourceRange(), "mayBeEmittedEagerly: SYCL");
+ return false;
+ }
+ }
+ const auto *vd = dyn_cast<VarDecl>(global);
+ if (vd)
+ if (astContext.getInlineVariableDefinitionKind(vd) ==
+ ASTContext::InlineVariableDefinitionKind::WeakUnknown)
+ // A definition of an inline constexpr static data member may change
+ // linkage later if it's redeclared outside the class.
+ return false;
+
+ // If OpenMP is enabled and threadprivates must be generated like TLS, delay
+ // codegen for global variables, because they may be marked as threadprivate.
+ if (langOpts.OpenMP && langOpts.OpenMPUseTLS &&
+ astContext.getTargetInfo().isTLSSupported() && isa<VarDecl>(global) &&
+ !global->getType().isConstantStorage(astContext, false, false) &&
+ !OMPDeclareTargetDeclAttr::isDeclareTargetDeclaration(global))
+ return false;
+
+ assert((fd || vd) &&
+ "Only FunctionDecl and VarDecl should hit this path so far.");
+ return true;
+}
+
static bool shouldAssumeDSOLocal(const CIRGenModule &cgm,
cir::CIRGlobalValueInterface gv) {
if (gv.hasLocalLinkage())
@@ -1402,6 +1607,13 @@ CIRGenModule::getGlobalVisibilityAttrFromDecl(const Decl *decl) {
return cirVisibility;
}
+void CIRGenModule::release() {
+ emitDeferred();
+
+ // There's a lot of code that is not implemented yet.
+ assert(!cir::MissingFeatures::cgmRelease());
+}
+
mlir::Type CIRGenModule::convertType(QualType type) {
return genTypes.convertType(type);
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 234c5f1710176..7055170ac7ac3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -167,8 +167,31 @@ class CIRGenModule : public CIRGenTypeCache {
clang::CharUnits getNaturalTypeAlignment(clang::QualType t,
LValueBaseInfo *baseInfo);
+ /// This contains all the decls which have definitions but which are deferred
+ /// for emission and therefore should only be output if they are actually
+ /// used. If a decl is in this, then it is known to have not been referenced
+ /// yet.
+ std::map<llvm::StringRef, clang::GlobalDecl> deferredDecls;
+
+ // This is a list of deferred decls which we have seen that *are* actually
+ // referenced. These get code generated when the module is done.
+ std::vector<clang::GlobalDecl> deferredDeclsToEmit;
+ void addDeferredDeclToEmit(clang::GlobalDecl GD) {
+ deferredDeclsToEmit.emplace_back(GD);
+ }
+
void emitTopLevelDecl(clang::Decl *decl);
+ /// Determine whether the definition must be emitted; if this returns \c
+ /// false, the definition can be emitted lazily if it's used.
+ bool mustBeEmitted(const clang::ValueDecl *d);
+
+ /// Determine whether the definition can be emitted eagerly, or should be
+ /// delayed until the end of the translation unit. This is relevant for
+ /// definitions whose linkage can change, e.g. implicit function
+ /// instantiations which may later be explicitly instantiated.
+ bool mayBeEmittedEagerly(const clang::ValueDecl *d);
+
bool verifyModule() const;
/// Return the address of the given function. If funcType is non-null, then
@@ -179,6 +202,10 @@ class CIRGenModule : public CIRGenTypeCache {
bool forVTable = false, bool dontDefer = false,
ForDefinition_t isForDefinition = NotForDefinition);
+ mlir::Operation *
+ getAddrOfGlobal(clang::GlobalDecl gd,
+ ForDefinition_t isForDefinition = NotForDefinition);
+
/// Emit code for a single global function or variable declaration. Forward
/// declarations are emitted lazily.
void emitGlobal(clang::GlobalDecl gd);
@@ -234,8 +261,17 @@ class CIRGenModule : public CIRGenTypeCache {
return builder.getSizeFromCharUnits(size);
}
+ /// Emit any needed decls for which code generation was deferred.
+ void emitDeferred();
+
+ /// Helper for `emitDeferred` to apply actual codegen.
+ void emitGlobalDecl(const clang::GlobalDecl &d);
+
const llvm::Triple &getTriple() const { return target.getTriple(); }
+ // Finalize CIR code generation.
+ void release();
+
/// -------
/// Visibility and Linkage
/// -------
diff --git a/clang/lib/CIR/CodeGen/CIRGenerator.cpp b/clang/lib/CIR/CodeGen/CIRGenerator.cpp
index 726da5b013264..23a25299a10d0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenerator.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenerator.cpp
@@ -63,6 +63,20 @@ bool CIRGenerator::HandleTopLevelDecl(DeclGroupRef group) {
return true;
}
+void CIRGenerator::HandleTranslationUnit(ASTContext &astContext) {
+ // Release the Builder when there is no error.
+ if (!diags.hasErrorOccurred() && cgm)
+ cgm->release();
+
+ // If there are errors before or when releasing the CGM, reset the module to
+ // stop here before invoking the backend.
+ if (diags.hasErrorOccurred()) {
+ if (cgm)
+ // TODO: cgm->clear();
+ return;
+ }
+}
+
void CIRGenerator::CompleteTentativeDefinition(VarDecl *d) {
if (diags.hasErrorOccurred())
return;
diff --git a/clang/test/CIR/CodeGen/deferred-defs.cpp b/clang/test/CIR/CodeGen/deferred-defs.cpp
new file mode 100644
index 0000000000000..4230a4ed3e24e
--- /dev/null
+++ b/clang/test/CIR/CodeGen/deferred-defs.cpp
@@ -0,0 +1,22 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR --implicit-check-not=unusedExternal \
+// RUN: --implicit-check-not=unusedInternal
+
+extern int usedExternal;
+extern int unusedExternal;
+
+int locallyDefined;
+
+namespace {
+ int unsedInternal;
+ int usedInternal;
+}
+
+void use() {
+ usedInternal;
+ usedExternal;
+}
+
+// CIR: cir.global external @locallyDefined = #cir.int<0> : !s32i
+// CIR: cir.global "private" internal dsolocal @_ZN12_GLOBAL__N_112usedInternalE = #cir.int<0> : !s32i
+// CIR: cir.global "private" external @usedExternal : !s32i
|
@llvm/pr-subscribers-clang Author: Andy Kaylor (andykaylor) ChangesThis change adds support for deferring global variable definitions until first use whenever it is possible to do so. Although deferring function definitions uses much of the same implementation, function deferral will be added in a follow-up change. Full diff: https://github.com/llvm/llvm-project/pull/142496.diff 6 Files Affected:
diff --git a/clang/include/clang/CIR/CIRGenerator.h b/clang/include/clang/CIR/CIRGenerator.h
index bb20fdf72693b..38e031c9a68d0 100644
--- a/clang/include/clang/CIR/CIRGenerator.h
+++ b/clang/include/clang/CIR/CIRGenerator.h
@@ -54,6 +54,7 @@ class CIRGenerator : public clang::ASTConsumer {
~CIRGenerator() override;
void Initialize(clang::ASTContext &astContext) override;
bool HandleTopLevelDecl(clang::DeclGroupRef group) override;
+ void HandleTranslationUnit(clang::ASTContext &astContext) override;
void CompleteTentativeDefinition(clang::VarDecl *d) override;
mlir::ModuleOp getModule() const;
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 3b82de882953c..5609cac46910d 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -137,6 +137,11 @@ struct MissingFeatures {
static bool recordZeroInit() { return false; }
static bool zeroSizeRecordMembers() { return false; }
+ // Various handling of deferred processing in CIRGenModule.
+ static bool cgmRelease() { return false; }
+ static bool deferredVtables() { return false; }
+ static bool deferredFuncDecls() { return false; }
+
// Misc
static bool cxxABI() { return false; }
static bool cirgenABIInfo() { return false; }
@@ -201,7 +206,7 @@ struct MissingFeatures {
static bool writebacks() { return false; }
static bool cleanupsToDeactivate() { return false; }
static bool stackBase() { return false; }
- static bool deferredDecls() { return false; }
+ static bool deferredCXXGlobalInit() { return false; }
static bool setTargetAttributes() { return false; }
// Missing types
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 9da7fcc0f89d5..11be07d36ab57 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -210,6 +210,99 @@ mlir::Location CIRGenModule::getLoc(SourceRange cRange) {
return mlir::FusedLoc::get({begin, end}, metadata, builder.getContext());
}
+mlir::Operation *
+CIRGenModule::getAddrOfGlobal(GlobalDecl gd, ForDefinition_t isForDefinition) {
+ const Decl *d = gd.getDecl();
+
+ if (isa<CXXConstructorDecl>(d) || isa<CXXDestructorDecl>(d)) {
+ errorNYI(d->getSourceRange(),
+ "getAddrOfGlobal: C++ constructor/destructor");
+ return nullptr;
+ }
+
+ if (isa<CXXMethodDecl>(d)) {
+ errorNYI(d->getSourceRange(), "getAddrOfGlobal: C++ method decl");
+ return nullptr;
+ }
+
+ if (isa<FunctionDecl>(d)) {
+ errorNYI(d->getSourceRange(), "getAddrOfGlobal: function decl");
+ return nullptr;
+ }
+
+ return getAddrOfGlobalVar(cast<VarDecl>(d), /*ty=*/nullptr, isForDefinition)
+ .getDefiningOp();
+}
+
+void CIRGenModule::emitGlobalDecl(const clang::GlobalDecl &d) {
+ // We call getAddrOfGlobal with isForDefinition set to ForDefinition in
+ // order to get a Value with exactly the type we need, not something that
+ // might have been created for another decl with the same mangled name but
+ // different type.
+ mlir::Operation *op = getAddrOfGlobal(d, ForDefinition);
+
+ // In case of different address spaces, we may still get a cast, even with
+ // IsForDefinition equal to ForDefinition. Query mangled names table to get
+ // GlobalValue.
+ if (!op)
+ op = getGlobalValue(getMangledName(d));
+
+ assert(op && "expected a valid global op");
+
+ // Check to see if we've already emitted this. This is necessary for a
+ // couple of reasons: first, decls can end up in deferred-decls queue
+ // multiple times, and second, decls can end up with definitions in unusual
+ // ways (e.g. by an extern inline function acquiring a strong function
+ // redefinition). Just ignore those cases.
+ // TODO: Not sure what to map this to for MLIR
+ mlir::Operation *globalValueOp = op;
+ if (auto gv = dyn_cast<cir::GetGlobalOp>(op))
+ globalValueOp =
+ mlir::SymbolTable::lookupSymbolIn(getModule(), gv.getNameAttr());
+
+ if (auto cirGlobalValue =
+ dyn_cast<cir::CIRGlobalValueInterface>(globalValueOp))
+ if (!cirGlobalValue.isDeclaration())
+ return;
+
+ // If this is OpenMP, check if it is legal to emit this global normally.
+ assert(!cir::MissingFeatures::openMP());
+
+ // Otherwise, emit the definition and move on to the next one.
+ emitGlobalDefinition(d, op);
+}
+
+void CIRGenModule::emitDeferred() {
+ // Emit code for any potentially referenced deferred decls. Since a previously
+ // unused static decl may become used during the generation of code for a
+ // static function, iterate until no changes are made.
+
+ assert(!cir::MissingFeatures::openMP());
+ assert(!cir::MissingFeatures::deferredVtables());
+ assert(!cir::MissingFeatures::cudaSupport());
+
+ // Stop if we're out of both deferred vtables and deferred declarations.
+ if (deferredDeclsToEmit.empty())
+ return;
+
+ // Grab the list of decls to emit. If emitGlobalDefinition schedules more
+ // work, it will not interfere with this.
+ std::vector<GlobalDecl> curDeclsToEmit;
+ curDeclsToEmit.swap(deferredDeclsToEmit);
+
+ for (const GlobalDecl &d : curDeclsToEmit) {
+ emitGlobalDecl(d);
+
+ // If we found out that we need to emit more decls, do that recursively.
+ // This has the advantage that the decls are emitted in a DFS and related
+ // ones are close together, which is convenient for testing.
+ if (!deferredDeclsToEmit.empty()) {
+ emitDeferred();
+ assert(deferredDeclsToEmit.empty());
+ }
+ }
+}
+
void CIRGenModule::emitGlobal(clang::GlobalDecl gd) {
if (const auto *cd = dyn_cast<clang::OpenACCConstructDecl>(gd.getDecl())) {
emitGlobalOpenACCDecl(cd);
@@ -248,8 +341,33 @@ void CIRGenModule::emitGlobal(clang::GlobalDecl gd) {
}
}
- // TODO(CIR): Defer emitting some global definitions until later
- emitGlobalDefinition(gd);
+ // Defer code generation to first use when possible, e.g. if this is an inline
+ // function. If the global must always be emitted, do it eagerly if possible
+ // to benefit from cache locality. Deferring code generation is necessary to
+ // avoid adding initializers to external declarations.
+ if (mustBeEmitted(global) && mayBeEmittedEagerly(global)) {
+ // Emit the definition if it can't be deferred.
+ emitGlobalDefinition(gd);
+ return;
+ }
+
+ // If we're deferring emission of a C++ variable with an initializer, remember
+ // the order in which it appeared on the file.
+ assert(!cir::MissingFeatures::deferredCXXGlobalInit());
+
+ llvm::StringRef mangledName = getMangledName(gd);
+ if (getGlobalValue(mangledName) != nullptr) {
+ // The value has already been used and should therefore be emitted.
+ addDeferredDeclToEmit(gd);
+ } else if (mustBeEmitted(global)) {
+ // The value must be emitted, but cannot be emitted eagerly.
+ assert(!mayBeEmittedEagerly(global));
+ addDeferredDeclToEmit(gd);
+ } else {
+ // Otherwise, remember that we saw a deferred decl with this name. The first
+ // use of the mangled name will cause it to move into deferredDeclsToEmit.
+ deferredDecls[mangledName] = gd;
+ }
}
void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
@@ -410,6 +528,17 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty,
CIRGenModule::createGlobalOp(*this, loc, mangledName, ty,
/*insertPoint=*/entry.getOperation());
+ // This is the first use or definition of a mangled name. If there is a
+ // deferred decl with this name, remember that we need to emit it at the end
+ // of the file.
+ auto ddi = deferredDecls.find(mangledName);
+ if (ddi != deferredDecls.end()) {
+ // Move the potentially referenced deferred decl to the DeferredDeclsToEmit
+ // list, and remove it from DeferredDecls (since we don't need it anymore).
+ addDeferredDeclToEmit(ddi->second);
+ deferredDecls.erase(ddi);
+ }
+
// Handle things which are present even on external declarations.
if (d) {
if (langOpts.OpenMP && !langOpts.OpenMPSimd)
@@ -1129,12 +1258,88 @@ void CIRGenModule::emitTentativeDefinition(const VarDecl *d) {
if (gv && !mlir::cast<cir::GlobalOp>(gv).isDeclaration())
return;
- assert(!cir::MissingFeatures::deferredDecls());
+ // If we have not seen a reference to this variable yet, place it into the
+ // deferred declarations table to be emitted if needed later.
+ if (!mustBeEmitted(d) && !gv) {
+ deferredDecls[mangledName] = d;
+ return;
+ }
// The tentative definition is the only definition.
emitGlobalVarDefinition(d);
}
+bool CIRGenModule::mustBeEmitted(const ValueDecl *global) {
+ // Never defer when EmitAllDecls is specified.
+ if (langOpts.EmitAllDecls)
+ return true;
+
+ const auto *vd = dyn_cast<VarDecl>(global);
+ if (vd &&
+ ((codeGenOpts.KeepPersistentStorageVariables &&
+ (vd->getStorageDuration() == SD_Static ||
+ vd->getStorageDuration() == SD_Thread)) ||
+ (codeGenOpts.KeepStaticConsts && vd->getStorageDuration() == SD_Static &&
+ vd->getType().isConstQualified())))
+ return true;
+
+ // TODO(cir): We do want to defer function decls, but it's not implemented.
+ assert(!cir::MissingFeatures::deferredFuncDecls());
+ if (isa<FunctionDecl>(global))
+ return true;
+
+ return getASTContext().DeclMustBeEmitted(global);
+}
+
+bool CIRGenModule::mayBeEmittedEagerly(const ValueDecl *global) {
+ // In OpenMP 5.0 variables and function may be marked as
+ // device_type(host/nohost) and we should not emit them eagerly unless we sure
+ // that they must be emitted on the host/device. To be sure we need to have
+ // seen a declare target with an explicit mentioning of the function, we know
+ // we have if the level of the declare target attribute is -1. Note that we
+ // check somewhere else if we should emit this at all.
+ if (langOpts.OpenMP >= 50 && !langOpts.OpenMPSimd) {
+ std::optional<OMPDeclareTargetDeclAttr *> activeAttr =
+ OMPDeclareTargetDeclAttr::getActiveAttr(global);
+ if (!activeAttr || (*activeAttr)->getLevel() != (unsigned)-1)
+ return false;
+ }
+
+ const auto *fd = dyn_cast<FunctionDecl>(global);
+ if (fd) {
+ // Implicit template instantiations may change linkage if they are later
+ // explicitly instantiated, so they should not be emitted eagerly.
+ if (fd->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
+ return false;
+ // Defer until all versions have been semantically checked.
+ if (fd->hasAttr<TargetVersionAttr>() && !fd->isMultiVersion())
+ return false;
+ if (langOpts.SYCLIsDevice) {
+ errorNYI(fd->getSourceRange(), "mayBeEmittedEagerly: SYCL");
+ return false;
+ }
+ }
+ const auto *vd = dyn_cast<VarDecl>(global);
+ if (vd)
+ if (astContext.getInlineVariableDefinitionKind(vd) ==
+ ASTContext::InlineVariableDefinitionKind::WeakUnknown)
+ // A definition of an inline constexpr static data member may change
+ // linkage later if it's redeclared outside the class.
+ return false;
+
+ // If OpenMP is enabled and threadprivates must be generated like TLS, delay
+ // codegen for global variables, because they may be marked as threadprivate.
+ if (langOpts.OpenMP && langOpts.OpenMPUseTLS &&
+ astContext.getTargetInfo().isTLSSupported() && isa<VarDecl>(global) &&
+ !global->getType().isConstantStorage(astContext, false, false) &&
+ !OMPDeclareTargetDeclAttr::isDeclareTargetDeclaration(global))
+ return false;
+
+ assert((fd || vd) &&
+ "Only FunctionDecl and VarDecl should hit this path so far.");
+ return true;
+}
+
static bool shouldAssumeDSOLocal(const CIRGenModule &cgm,
cir::CIRGlobalValueInterface gv) {
if (gv.hasLocalLinkage())
@@ -1402,6 +1607,13 @@ CIRGenModule::getGlobalVisibilityAttrFromDecl(const Decl *decl) {
return cirVisibility;
}
+void CIRGenModule::release() {
+ emitDeferred();
+
+ // There's a lot of code that is not implemented yet.
+ assert(!cir::MissingFeatures::cgmRelease());
+}
+
mlir::Type CIRGenModule::convertType(QualType type) {
return genTypes.convertType(type);
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 234c5f1710176..7055170ac7ac3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -167,8 +167,31 @@ class CIRGenModule : public CIRGenTypeCache {
clang::CharUnits getNaturalTypeAlignment(clang::QualType t,
LValueBaseInfo *baseInfo);
+ /// This contains all the decls which have definitions but which are deferred
+ /// for emission and therefore should only be output if they are actually
+ /// used. If a decl is in this, then it is known to have not been referenced
+ /// yet.
+ std::map<llvm::StringRef, clang::GlobalDecl> deferredDecls;
+
+ // This is a list of deferred decls which we have seen that *are* actually
+ // referenced. These get code generated when the module is done.
+ std::vector<clang::GlobalDecl> deferredDeclsToEmit;
+ void addDeferredDeclToEmit(clang::GlobalDecl GD) {
+ deferredDeclsToEmit.emplace_back(GD);
+ }
+
void emitTopLevelDecl(clang::Decl *decl);
+ /// Determine whether the definition must be emitted; if this returns \c
+ /// false, the definition can be emitted lazily if it's used.
+ bool mustBeEmitted(const clang::ValueDecl *d);
+
+ /// Determine whether the definition can be emitted eagerly, or should be
+ /// delayed until the end of the translation unit. This is relevant for
+ /// definitions whose linkage can change, e.g. implicit function
+ /// instantiations which may later be explicitly instantiated.
+ bool mayBeEmittedEagerly(const clang::ValueDecl *d);
+
bool verifyModule() const;
/// Return the address of the given function. If funcType is non-null, then
@@ -179,6 +202,10 @@ class CIRGenModule : public CIRGenTypeCache {
bool forVTable = false, bool dontDefer = false,
ForDefinition_t isForDefinition = NotForDefinition);
+ mlir::Operation *
+ getAddrOfGlobal(clang::GlobalDecl gd,
+ ForDefinition_t isForDefinition = NotForDefinition);
+
/// Emit code for a single global function or variable declaration. Forward
/// declarations are emitted lazily.
void emitGlobal(clang::GlobalDecl gd);
@@ -234,8 +261,17 @@ class CIRGenModule : public CIRGenTypeCache {
return builder.getSizeFromCharUnits(size);
}
+ /// Emit any needed decls for which code generation was deferred.
+ void emitDeferred();
+
+ /// Helper for `emitDeferred` to apply actual codegen.
+ void emitGlobalDecl(const clang::GlobalDecl &d);
+
const llvm::Triple &getTriple() const { return target.getTriple(); }
+ // Finalize CIR code generation.
+ void release();
+
/// -------
/// Visibility and Linkage
/// -------
diff --git a/clang/lib/CIR/CodeGen/CIRGenerator.cpp b/clang/lib/CIR/CodeGen/CIRGenerator.cpp
index 726da5b013264..23a25299a10d0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenerator.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenerator.cpp
@@ -63,6 +63,20 @@ bool CIRGenerator::HandleTopLevelDecl(DeclGroupRef group) {
return true;
}
+void CIRGenerator::HandleTranslationUnit(ASTContext &astContext) {
+ // Release the Builder when there is no error.
+ if (!diags.hasErrorOccurred() && cgm)
+ cgm->release();
+
+ // If there are errors before or when releasing the CGM, reset the module to
+ // stop here before invoking the backend.
+ if (diags.hasErrorOccurred()) {
+ if (cgm)
+ // TODO: cgm->clear();
+ return;
+ }
+}
+
void CIRGenerator::CompleteTentativeDefinition(VarDecl *d) {
if (diags.hasErrorOccurred())
return;
diff --git a/clang/test/CIR/CodeGen/deferred-defs.cpp b/clang/test/CIR/CodeGen/deferred-defs.cpp
new file mode 100644
index 0000000000000..4230a4ed3e24e
--- /dev/null
+++ b/clang/test/CIR/CodeGen/deferred-defs.cpp
@@ -0,0 +1,22 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR --implicit-check-not=unusedExternal \
+// RUN: --implicit-check-not=unusedInternal
+
+extern int usedExternal;
+extern int unusedExternal;
+
+int locallyDefined;
+
+namespace {
+ int unsedInternal;
+ int usedInternal;
+}
+
+void use() {
+ usedInternal;
+ usedExternal;
+}
+
+// CIR: cir.global external @locallyDefined = #cir.int<0> : !s32i
+// CIR: cir.global "private" internal dsolocal @_ZN12_GLOBAL__N_112usedInternalE = #cir.int<0> : !s32i
+// CIR: cir.global "private" external @usedExternal : !s32i
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a quick look, and this seems reasonable.
// stop here before invoking the backend. | ||
if (diags.hasErrorOccurred()) { | ||
if (cgm) | ||
// TODO: cgm->clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah... I'm not really sure what's needed here. This TODO comment is in the incubator code. I think the idea is that "something" probably needs to happen to clean up after an error, but it isn't clear what that something should be. I should have replaced this with a MissingFeature.
This change adds support for deferring global variable definitions until first use whenever it is possible to do so. Although deferring function definitions uses much of the same implementation, function deferral will be added in a follow-up change.
fa12d73
to
2987464
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This change adds support for deferring global variable definitions until first use whenever it is possible to do so. Although deferring function definitions uses much of the same implementation, function deferral will be added in a follow-up change.