Skip to content

[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

Merged
merged 2 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/include/clang/CIR/CIRGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,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 HandleInlineFunctionDefinition(clang::FunctionDecl *d) override;
void CompleteTentativeDefinition(clang::VarDecl *d) override;

Expand Down
8 changes: 7 additions & 1 deletion clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

// CXXABI
static bool cxxABI() { return false; }
static bool cxxabiThisAlignment() { return false; }
Expand Down Expand Up @@ -205,11 +210,12 @@ 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; }
static bool coverageMapping() { return false; }
static bool peepholeProtection() { return false; }
static bool instrumentation() { return false; }
static bool cleanupAfterErrorDiags() { return false; }

// Missing types
static bool dataMemberType() { return false; }
Expand Down
218 changes: 215 additions & 3 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,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);
Expand Down Expand Up @@ -240,8 +333,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,
Expand Down Expand Up @@ -402,6 +520,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)
Expand Down Expand Up @@ -1121,12 +1250,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())
Expand Down Expand Up @@ -1394,6 +1599,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);
}
Expand Down
36 changes: 36 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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
/// -------
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ 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.
assert(!cir::MissingFeatures::cleanupAfterErrorDiags());
}

void CIRGenerator::HandleInlineFunctionDefinition(FunctionDecl *d) {
if (diags.hasErrorOccurred())
return;
Expand Down
22 changes: 22 additions & 0 deletions clang/test/CIR/CodeGen/deferred-defs.cpp
Original file line number Diff line number Diff line change
@@ -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