summaryrefslogtreecommitdiff
path: root/clang/lib/CIR/CodeGen/CIRGenModule.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang/lib/CIR/CodeGen/CIRGenModule.cpp')
-rw-r--r--clang/lib/CIR/CodeGen/CIRGenModule.cpp286
1 files changed, 282 insertions, 4 deletions
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 0ec5dcec9928..e5eae31e937d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -202,6 +202,104 @@ 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)) {
+ const CIRGenFunctionInfo &fi =
+ getTypes().arrangeCXXMethodDeclaration(cast<CXXMethodDecl>(d));
+ cir::FuncType ty = getTypes().getFunctionType(fi);
+ return getAddrOfFunction(gd, ty, /*ForVTable=*/false, /*DontDefer=*/false,
+ isForDefinition);
+ }
+
+ if (isa<FunctionDecl>(d)) {
+ const CIRGenFunctionInfo &fi = getTypes().arrangeGlobalDeclaration(gd);
+ cir::FuncType ty = getTypes().getFunctionType(fi);
+ return getAddrOfFunction(gd, ty, /*ForVTable=*/false, /*DontDefer=*/false,
+ isForDefinition);
+ }
+
+ 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);
@@ -240,8 +338,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,
@@ -402,6 +525,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)
@@ -704,6 +838,11 @@ void CIRGenModule::maybeSetTrivialComdat(const Decl &d, mlir::Operation *op) {
assert(!cir::MissingFeatures::opFuncSetComdat());
}
+void CIRGenModule::updateCompletedType(const TagDecl *td) {
+ // Make sure that this type is translated.
+ genTypes.updateCompletedType(td);
+}
+
// TODO(CIR): this could be a common method between LLVM codegen.
static bool isVarDeclStrongDefinition(const ASTContext &astContext,
CIRGenModule &cgm, const VarDecl *vd,
@@ -1001,19 +1140,30 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) {
emitGlobalOpenACCDecl(cast<OpenACCDeclareDecl>(decl));
break;
case Decl::Enum:
+ case Decl::Using: // using X; [C++]
case Decl::UsingDirective: // using namespace X; [C++]
case Decl::Typedef:
case Decl::TypeAlias: // using foo = bar; [C++11]
case Decl::Record:
- case Decl::CXXRecord:
assert(!cir::MissingFeatures::generateDebugInfo());
break;
+ // No code generation needed.
+ case Decl::UsingShadow:
+ case Decl::Empty:
+ break;
+
// C++ Decls
case Decl::LinkageSpec:
case Decl::Namespace:
emitDeclContext(Decl::castToDeclContext(decl));
break;
+
+ case Decl::ClassTemplateSpecialization:
+ case Decl::CXXRecord:
+ assert(!cir::MissingFeatures::generateDebugInfo());
+ assert(!cir::MissingFeatures::cxxRecordStaticMembers());
+ break;
}
}
@@ -1121,12 +1271,83 @@ 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;
+
+ 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())
@@ -1318,6 +1539,56 @@ cir::FuncOp CIRGenModule::getOrCreateCIRFunction(
cir::FuncOp funcOp = createCIRFunction(
invalidLoc ? theModule->getLoc() : getLoc(funcDecl->getSourceRange()),
mangledName, mlir::cast<cir::FuncType>(funcType), funcDecl);
+
+ // 'dontDefer' actually means don't move this to the deferredDeclsToEmit list.
+ if (dontDefer) {
+ // TODO(cir): This assertion will need an additional condition when we
+ // support incomplete functions.
+ assert(funcOp.getFunctionType() == funcType);
+ return funcOp;
+ }
+
+ // All MSVC dtors other than the base dtor are linkonce_odr and delegate to
+ // each other bottoming out wiht the base dtor. Therefore we emit non-base
+ // dtors on usage, even if there is no dtor definition in the TU.
+ if (isa_and_nonnull<CXXDestructorDecl>(d))
+ errorNYI(d->getSourceRange(), "getOrCreateCIRFunction: dtor");
+
+ // 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);
+
+ // Otherwise, there are cases we have to worry about where we're using a
+ // declaration for which we must emit a definition but where we might not
+ // find a top-level definition.
+ // - member functions defined inline in their classes
+ // - friend functions defined inline in some class
+ // - special member functions with implicit definitions
+ // If we ever change our AST traversal to walk into class methods, this
+ // will be unnecessary.
+ //
+ // We also don't emit a definition for a function if it's going to be an
+ // entry in a vtable, unless it's already marked as used.
+ } else if (getLangOpts().CPlusPlus && d) {
+ // Look for a declaration that's lexically in a record.
+ for (const auto *fd = cast<FunctionDecl>(d)->getMostRecentDecl(); fd;
+ fd = fd->getPreviousDecl()) {
+ if (isa<CXXRecordDecl>(fd->getLexicalDeclContext())) {
+ if (fd->doesThisDeclarationHaveABody()) {
+ addDeferredDeclToEmit(gd.getWithDecl(fd));
+ break;
+ }
+ }
+ }
+ }
+
return funcOp;
}
@@ -1394,6 +1665,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);
}