Skip to content

Commit f327d6d

Browse files
authored
[CIR] Defer definitions of global variables until they are used. (llvm#142496)
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.
1 parent 31abf07 commit f327d6d

File tree

6 files changed

+291
-4
lines changed

6 files changed

+291
-4
lines changed

clang/include/clang/CIR/CIRGenerator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class CIRGenerator : public clang::ASTConsumer {
7676
~CIRGenerator() override;
7777
void Initialize(clang::ASTContext &astContext) override;
7878
bool HandleTopLevelDecl(clang::DeclGroupRef group) override;
79+
void HandleTranslationUnit(clang::ASTContext &astContext) override;
7980
void HandleInlineFunctionDefinition(clang::FunctionDecl *d) override;
8081
void CompleteTentativeDefinition(clang::VarDecl *d) override;
8182

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ struct MissingFeatures {
137137
static bool recordZeroInit() { return false; }
138138
static bool zeroSizeRecordMembers() { return false; }
139139

140+
// Various handling of deferred processing in CIRGenModule.
141+
static bool cgmRelease() { return false; }
142+
static bool deferredVtables() { return false; }
143+
static bool deferredFuncDecls() { return false; }
144+
140145
// CXXABI
141146
static bool cxxABI() { return false; }
142147
static bool cxxabiThisAlignment() { return false; }
@@ -205,11 +210,12 @@ struct MissingFeatures {
205210
static bool writebacks() { return false; }
206211
static bool cleanupsToDeactivate() { return false; }
207212
static bool stackBase() { return false; }
208-
static bool deferredDecls() { return false; }
213+
static bool deferredCXXGlobalInit() { return false; }
209214
static bool setTargetAttributes() { return false; }
210215
static bool coverageMapping() { return false; }
211216
static bool peepholeProtection() { return false; }
212217
static bool instrumentation() { return false; }
218+
static bool cleanupAfterErrorDiags() { return false; }
213219

214220
// Missing types
215221
static bool dataMemberType() { return false; }

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 215 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,99 @@ mlir::Location CIRGenModule::getLoc(SourceRange cRange) {
202202
return mlir::FusedLoc::get({begin, end}, metadata, builder.getContext());
203203
}
204204

205+
mlir::Operation *
206+
CIRGenModule::getAddrOfGlobal(GlobalDecl gd, ForDefinition_t isForDefinition) {
207+
const Decl *d = gd.getDecl();
208+
209+
if (isa<CXXConstructorDecl>(d) || isa<CXXDestructorDecl>(d)) {
210+
errorNYI(d->getSourceRange(),
211+
"getAddrOfGlobal: C++ constructor/destructor");
212+
return nullptr;
213+
}
214+
215+
if (isa<CXXMethodDecl>(d)) {
216+
errorNYI(d->getSourceRange(), "getAddrOfGlobal: C++ method decl");
217+
return nullptr;
218+
}
219+
220+
if (isa<FunctionDecl>(d)) {
221+
errorNYI(d->getSourceRange(), "getAddrOfGlobal: function decl");
222+
return nullptr;
223+
}
224+
225+
return getAddrOfGlobalVar(cast<VarDecl>(d), /*ty=*/nullptr, isForDefinition)
226+
.getDefiningOp();
227+
}
228+
229+
void CIRGenModule::emitGlobalDecl(const clang::GlobalDecl &d) {
230+
// We call getAddrOfGlobal with isForDefinition set to ForDefinition in
231+
// order to get a Value with exactly the type we need, not something that
232+
// might have been created for another decl with the same mangled name but
233+
// different type.
234+
mlir::Operation *op = getAddrOfGlobal(d, ForDefinition);
235+
236+
// In case of different address spaces, we may still get a cast, even with
237+
// IsForDefinition equal to ForDefinition. Query mangled names table to get
238+
// GlobalValue.
239+
if (!op)
240+
op = getGlobalValue(getMangledName(d));
241+
242+
assert(op && "expected a valid global op");
243+
244+
// Check to see if we've already emitted this. This is necessary for a
245+
// couple of reasons: first, decls can end up in deferred-decls queue
246+
// multiple times, and second, decls can end up with definitions in unusual
247+
// ways (e.g. by an extern inline function acquiring a strong function
248+
// redefinition). Just ignore those cases.
249+
// TODO: Not sure what to map this to for MLIR
250+
mlir::Operation *globalValueOp = op;
251+
if (auto gv = dyn_cast<cir::GetGlobalOp>(op))
252+
globalValueOp =
253+
mlir::SymbolTable::lookupSymbolIn(getModule(), gv.getNameAttr());
254+
255+
if (auto cirGlobalValue =
256+
dyn_cast<cir::CIRGlobalValueInterface>(globalValueOp))
257+
if (!cirGlobalValue.isDeclaration())
258+
return;
259+
260+
// If this is OpenMP, check if it is legal to emit this global normally.
261+
assert(!cir::MissingFeatures::openMP());
262+
263+
// Otherwise, emit the definition and move on to the next one.
264+
emitGlobalDefinition(d, op);
265+
}
266+
267+
void CIRGenModule::emitDeferred() {
268+
// Emit code for any potentially referenced deferred decls. Since a previously
269+
// unused static decl may become used during the generation of code for a
270+
// static function, iterate until no changes are made.
271+
272+
assert(!cir::MissingFeatures::openMP());
273+
assert(!cir::MissingFeatures::deferredVtables());
274+
assert(!cir::MissingFeatures::cudaSupport());
275+
276+
// Stop if we're out of both deferred vtables and deferred declarations.
277+
if (deferredDeclsToEmit.empty())
278+
return;
279+
280+
// Grab the list of decls to emit. If emitGlobalDefinition schedules more
281+
// work, it will not interfere with this.
282+
std::vector<GlobalDecl> curDeclsToEmit;
283+
curDeclsToEmit.swap(deferredDeclsToEmit);
284+
285+
for (const GlobalDecl &d : curDeclsToEmit) {
286+
emitGlobalDecl(d);
287+
288+
// If we found out that we need to emit more decls, do that recursively.
289+
// This has the advantage that the decls are emitted in a DFS and related
290+
// ones are close together, which is convenient for testing.
291+
if (!deferredDeclsToEmit.empty()) {
292+
emitDeferred();
293+
assert(deferredDeclsToEmit.empty());
294+
}
295+
}
296+
}
297+
205298
void CIRGenModule::emitGlobal(clang::GlobalDecl gd) {
206299
if (const auto *cd = dyn_cast<clang::OpenACCConstructDecl>(gd.getDecl())) {
207300
emitGlobalOpenACCDecl(cd);
@@ -240,8 +333,33 @@ void CIRGenModule::emitGlobal(clang::GlobalDecl gd) {
240333
}
241334
}
242335

243-
// TODO(CIR): Defer emitting some global definitions until later
244-
emitGlobalDefinition(gd);
336+
// Defer code generation to first use when possible, e.g. if this is an inline
337+
// function. If the global must always be emitted, do it eagerly if possible
338+
// to benefit from cache locality. Deferring code generation is necessary to
339+
// avoid adding initializers to external declarations.
340+
if (mustBeEmitted(global) && mayBeEmittedEagerly(global)) {
341+
// Emit the definition if it can't be deferred.
342+
emitGlobalDefinition(gd);
343+
return;
344+
}
345+
346+
// If we're deferring emission of a C++ variable with an initializer, remember
347+
// the order in which it appeared on the file.
348+
assert(!cir::MissingFeatures::deferredCXXGlobalInit());
349+
350+
llvm::StringRef mangledName = getMangledName(gd);
351+
if (getGlobalValue(mangledName) != nullptr) {
352+
// The value has already been used and should therefore be emitted.
353+
addDeferredDeclToEmit(gd);
354+
} else if (mustBeEmitted(global)) {
355+
// The value must be emitted, but cannot be emitted eagerly.
356+
assert(!mayBeEmittedEagerly(global));
357+
addDeferredDeclToEmit(gd);
358+
} else {
359+
// Otherwise, remember that we saw a deferred decl with this name. The first
360+
// use of the mangled name will cause it to move into deferredDeclsToEmit.
361+
deferredDecls[mangledName] = gd;
362+
}
245363
}
246364

247365
void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
@@ -402,6 +520,17 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty,
402520
CIRGenModule::createGlobalOp(*this, loc, mangledName, ty,
403521
/*insertPoint=*/entry.getOperation());
404522

523+
// This is the first use or definition of a mangled name. If there is a
524+
// deferred decl with this name, remember that we need to emit it at the end
525+
// of the file.
526+
auto ddi = deferredDecls.find(mangledName);
527+
if (ddi != deferredDecls.end()) {
528+
// Move the potentially referenced deferred decl to the DeferredDeclsToEmit
529+
// list, and remove it from DeferredDecls (since we don't need it anymore).
530+
addDeferredDeclToEmit(ddi->second);
531+
deferredDecls.erase(ddi);
532+
}
533+
405534
// Handle things which are present even on external declarations.
406535
if (d) {
407536
if (langOpts.OpenMP && !langOpts.OpenMPSimd)
@@ -1121,12 +1250,88 @@ void CIRGenModule::emitTentativeDefinition(const VarDecl *d) {
11211250
if (gv && !mlir::cast<cir::GlobalOp>(gv).isDeclaration())
11221251
return;
11231252

1124-
assert(!cir::MissingFeatures::deferredDecls());
1253+
// If we have not seen a reference to this variable yet, place it into the
1254+
// deferred declarations table to be emitted if needed later.
1255+
if (!mustBeEmitted(d) && !gv) {
1256+
deferredDecls[mangledName] = d;
1257+
return;
1258+
}
11251259

11261260
// The tentative definition is the only definition.
11271261
emitGlobalVarDefinition(d);
11281262
}
11291263

1264+
bool CIRGenModule::mustBeEmitted(const ValueDecl *global) {
1265+
// Never defer when EmitAllDecls is specified.
1266+
if (langOpts.EmitAllDecls)
1267+
return true;
1268+
1269+
const auto *vd = dyn_cast<VarDecl>(global);
1270+
if (vd &&
1271+
((codeGenOpts.KeepPersistentStorageVariables &&
1272+
(vd->getStorageDuration() == SD_Static ||
1273+
vd->getStorageDuration() == SD_Thread)) ||
1274+
(codeGenOpts.KeepStaticConsts && vd->getStorageDuration() == SD_Static &&
1275+
vd->getType().isConstQualified())))
1276+
return true;
1277+
1278+
// TODO(cir): We do want to defer function decls, but it's not implemented.
1279+
assert(!cir::MissingFeatures::deferredFuncDecls());
1280+
if (isa<FunctionDecl>(global))
1281+
return true;
1282+
1283+
return getASTContext().DeclMustBeEmitted(global);
1284+
}
1285+
1286+
bool CIRGenModule::mayBeEmittedEagerly(const ValueDecl *global) {
1287+
// In OpenMP 5.0 variables and function may be marked as
1288+
// device_type(host/nohost) and we should not emit them eagerly unless we sure
1289+
// that they must be emitted on the host/device. To be sure we need to have
1290+
// seen a declare target with an explicit mentioning of the function, we know
1291+
// we have if the level of the declare target attribute is -1. Note that we
1292+
// check somewhere else if we should emit this at all.
1293+
if (langOpts.OpenMP >= 50 && !langOpts.OpenMPSimd) {
1294+
std::optional<OMPDeclareTargetDeclAttr *> activeAttr =
1295+
OMPDeclareTargetDeclAttr::getActiveAttr(global);
1296+
if (!activeAttr || (*activeAttr)->getLevel() != (unsigned)-1)
1297+
return false;
1298+
}
1299+
1300+
const auto *fd = dyn_cast<FunctionDecl>(global);
1301+
if (fd) {
1302+
// Implicit template instantiations may change linkage if they are later
1303+
// explicitly instantiated, so they should not be emitted eagerly.
1304+
if (fd->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
1305+
return false;
1306+
// Defer until all versions have been semantically checked.
1307+
if (fd->hasAttr<TargetVersionAttr>() && !fd->isMultiVersion())
1308+
return false;
1309+
if (langOpts.SYCLIsDevice) {
1310+
errorNYI(fd->getSourceRange(), "mayBeEmittedEagerly: SYCL");
1311+
return false;
1312+
}
1313+
}
1314+
const auto *vd = dyn_cast<VarDecl>(global);
1315+
if (vd)
1316+
if (astContext.getInlineVariableDefinitionKind(vd) ==
1317+
ASTContext::InlineVariableDefinitionKind::WeakUnknown)
1318+
// A definition of an inline constexpr static data member may change
1319+
// linkage later if it's redeclared outside the class.
1320+
return false;
1321+
1322+
// If OpenMP is enabled and threadprivates must be generated like TLS, delay
1323+
// codegen for global variables, because they may be marked as threadprivate.
1324+
if (langOpts.OpenMP && langOpts.OpenMPUseTLS &&
1325+
astContext.getTargetInfo().isTLSSupported() && isa<VarDecl>(global) &&
1326+
!global->getType().isConstantStorage(astContext, false, false) &&
1327+
!OMPDeclareTargetDeclAttr::isDeclareTargetDeclaration(global))
1328+
return false;
1329+
1330+
assert((fd || vd) &&
1331+
"Only FunctionDecl and VarDecl should hit this path so far.");
1332+
return true;
1333+
}
1334+
11301335
static bool shouldAssumeDSOLocal(const CIRGenModule &cgm,
11311336
cir::CIRGlobalValueInterface gv) {
11321337
if (gv.hasLocalLinkage())
@@ -1394,6 +1599,13 @@ CIRGenModule::getGlobalVisibilityAttrFromDecl(const Decl *decl) {
13941599
return cirVisibility;
13951600
}
13961601

1602+
void CIRGenModule::release() {
1603+
emitDeferred();
1604+
1605+
// There's a lot of code that is not implemented yet.
1606+
assert(!cir::MissingFeatures::cgmRelease());
1607+
}
1608+
13971609
mlir::Type CIRGenModule::convertType(QualType type) {
13981610
return genTypes.convertType(type);
13991611
}

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,31 @@ class CIRGenModule : public CIRGenTypeCache {
167167
clang::CharUnits getNaturalTypeAlignment(clang::QualType t,
168168
LValueBaseInfo *baseInfo);
169169

170+
/// This contains all the decls which have definitions but which are deferred
171+
/// for emission and therefore should only be output if they are actually
172+
/// used. If a decl is in this, then it is known to have not been referenced
173+
/// yet.
174+
std::map<llvm::StringRef, clang::GlobalDecl> deferredDecls;
175+
176+
// This is a list of deferred decls which we have seen that *are* actually
177+
// referenced. These get code generated when the module is done.
178+
std::vector<clang::GlobalDecl> deferredDeclsToEmit;
179+
void addDeferredDeclToEmit(clang::GlobalDecl GD) {
180+
deferredDeclsToEmit.emplace_back(GD);
181+
}
182+
170183
void emitTopLevelDecl(clang::Decl *decl);
171184

185+
/// Determine whether the definition must be emitted; if this returns \c
186+
/// false, the definition can be emitted lazily if it's used.
187+
bool mustBeEmitted(const clang::ValueDecl *d);
188+
189+
/// Determine whether the definition can be emitted eagerly, or should be
190+
/// delayed until the end of the translation unit. This is relevant for
191+
/// definitions whose linkage can change, e.g. implicit function
192+
/// instantiations which may later be explicitly instantiated.
193+
bool mayBeEmittedEagerly(const clang::ValueDecl *d);
194+
172195
bool verifyModule() const;
173196

174197
/// Return the address of the given function. If funcType is non-null, then
@@ -179,6 +202,10 @@ class CIRGenModule : public CIRGenTypeCache {
179202
bool forVTable = false, bool dontDefer = false,
180203
ForDefinition_t isForDefinition = NotForDefinition);
181204

205+
mlir::Operation *
206+
getAddrOfGlobal(clang::GlobalDecl gd,
207+
ForDefinition_t isForDefinition = NotForDefinition);
208+
182209
/// Emit code for a single global function or variable declaration. Forward
183210
/// declarations are emitted lazily.
184211
void emitGlobal(clang::GlobalDecl gd);
@@ -234,8 +261,17 @@ class CIRGenModule : public CIRGenTypeCache {
234261
return builder.getSizeFromCharUnits(size);
235262
}
236263

264+
/// Emit any needed decls for which code generation was deferred.
265+
void emitDeferred();
266+
267+
/// Helper for `emitDeferred` to apply actual codegen.
268+
void emitGlobalDecl(const clang::GlobalDecl &d);
269+
237270
const llvm::Triple &getTriple() const { return target.getTriple(); }
238271

272+
// Finalize CIR code generation.
273+
void release();
274+
239275
/// -------
240276
/// Visibility and Linkage
241277
/// -------

clang/lib/CIR/CodeGen/CIRGenerator.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ bool CIRGenerator::HandleTopLevelDecl(DeclGroupRef group) {
6969
return true;
7070
}
7171

72+
void CIRGenerator::HandleTranslationUnit(ASTContext &astContext) {
73+
// Release the Builder when there is no error.
74+
if (!diags.hasErrorOccurred() && cgm)
75+
cgm->release();
76+
77+
// If there are errors before or when releasing the cgm, reset the module to
78+
// stop here before invoking the backend.
79+
assert(!cir::MissingFeatures::cleanupAfterErrorDiags());
80+
}
81+
7282
void CIRGenerator::HandleInlineFunctionDefinition(FunctionDecl *d) {
7383
if (diags.hasErrorOccurred())
7484
return;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR --implicit-check-not=unusedExternal \
3+
// RUN: --implicit-check-not=unusedInternal
4+
5+
extern int usedExternal;
6+
extern int unusedExternal;
7+
8+
int locallyDefined;
9+
10+
namespace {
11+
int unsedInternal;
12+
int usedInternal;
13+
}
14+
15+
void use() {
16+
usedInternal;
17+
usedExternal;
18+
}
19+
20+
// CIR: cir.global external @locallyDefined = #cir.int<0> : !s32i
21+
// CIR: cir.global "private" internal dsolocal @_ZN12_GLOBAL__N_112usedInternalE = #cir.int<0> : !s32i
22+
// CIR: cir.global "private" external @usedExternal : !s32i

0 commit comments

Comments
 (0)