Skip to content

Commit ca84f2a

Browse files
authored
[CIR] Upstream support for generating global ctor regions (#161298)
This adds support for handling global variables with non-trivial constructors. The constructor call is emitted in CIR as a 'ctor' region associated with the global definition. This form of global definition cannot be lowered to LLVM IR yet. A later change will add support in LoweringPrepare to move the ctor code into a __cxx_global_var_init() function and add that function to the list of global global ctors, but for now we must stop at the initial CIR generation.
1 parent f61be43 commit ca84f2a

File tree

11 files changed

+230
-13
lines changed

11 files changed

+230
-13
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ struct MissingFeatures {
248248
static bool metaDataNode() { return false; }
249249
static bool moduleNameHash() { return false; }
250250
static bool msabi() { return false; }
251-
static bool needsGlobalCtorDtor() { return false; }
252251
static bool nrvo() { return false; }
253252
static bool objCBlocks() { return false; }
254253
static bool objCGC() { return false; }

clang/lib/CIR/CodeGen/CIRGenCXX.cpp

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,89 @@
1515

1616
#include "clang/AST/GlobalDecl.h"
1717
#include "clang/CIR/MissingFeatures.h"
18+
#include "llvm/Support/SaveAndRestore.h"
1819

1920
using namespace clang;
2021
using namespace clang::CIRGen;
2122

23+
static void emitDeclInit(CIRGenFunction &cgf, const VarDecl *varDecl,
24+
cir::GlobalOp globalOp) {
25+
assert((varDecl->hasGlobalStorage() ||
26+
(varDecl->hasLocalStorage() &&
27+
cgf.getContext().getLangOpts().OpenCLCPlusPlus)) &&
28+
"VarDecl must have global or local (in the case of OpenCL) storage!");
29+
assert(!varDecl->getType()->isReferenceType() &&
30+
"Should not call emitDeclInit on a reference!");
31+
32+
CIRGenBuilderTy &builder = cgf.getBuilder();
33+
34+
// Set up the ctor region.
35+
mlir::OpBuilder::InsertionGuard guard(builder);
36+
mlir::Block *block = builder.createBlock(&globalOp.getCtorRegion());
37+
CIRGenFunction::LexicalScope lexScope{cgf, globalOp.getLoc(),
38+
builder.getInsertionBlock()};
39+
lexScope.setAsGlobalInit();
40+
builder.setInsertionPointToStart(block);
41+
42+
Address declAddr(cgf.cgm.getAddrOfGlobalVar(varDecl),
43+
cgf.cgm.getASTContext().getDeclAlign(varDecl));
44+
45+
QualType type = varDecl->getType();
46+
LValue lv = cgf.makeAddrLValue(declAddr, type);
47+
48+
const Expr *init = varDecl->getInit();
49+
switch (CIRGenFunction::getEvaluationKind(type)) {
50+
case cir::TEK_Scalar:
51+
assert(!cir::MissingFeatures::objCGC());
52+
cgf.emitScalarInit(init, cgf.getLoc(varDecl->getLocation()), lv, false);
53+
break;
54+
case cir::TEK_Complex:
55+
cgf.cgm.errorNYI(varDecl->getSourceRange(), "complex global initializer");
56+
break;
57+
case cir::TEK_Aggregate:
58+
assert(!cir::MissingFeatures::aggValueSlotGC());
59+
cgf.emitAggExpr(init,
60+
AggValueSlot::forLValue(lv, AggValueSlot::IsDestructed,
61+
AggValueSlot::IsNotAliased,
62+
AggValueSlot::DoesNotOverlap));
63+
break;
64+
}
65+
66+
// Finish the ctor region.
67+
builder.setInsertionPointToEnd(block);
68+
cir::YieldOp::create(builder, globalOp.getLoc());
69+
}
70+
71+
static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
72+
cir::GlobalOp addr) {
73+
// Honor __attribute__((no_destroy)) and bail instead of attempting
74+
// to emit a reference to a possibly nonexistent destructor, which
75+
// in turn can cause a crash. This will result in a global constructor
76+
// that isn't balanced out by a destructor call as intended by the
77+
// attribute. This also checks for -fno-c++-static-destructors and
78+
// bails even if the attribute is not present.
79+
QualType::DestructionKind dtorKind = vd->needsDestruction(cgf.getContext());
80+
81+
// FIXME: __attribute__((cleanup)) ?
82+
83+
switch (dtorKind) {
84+
case QualType::DK_none:
85+
return;
86+
87+
case QualType::DK_cxx_destructor:
88+
break;
89+
90+
case QualType::DK_objc_strong_lifetime:
91+
case QualType::DK_objc_weak_lifetime:
92+
case QualType::DK_nontrivial_c_struct:
93+
// We don't care about releasing objects during process teardown.
94+
assert(!vd->getTLSKind() && "should have rejected this");
95+
return;
96+
}
97+
98+
cgf.cgm.errorNYI(vd->getSourceRange(), "global with destructor");
99+
}
100+
22101
cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl gd) {
23102
const CIRGenFunctionInfo &fnInfo =
24103
getTypes().arrangeCXXStructorDeclaration(gd);
@@ -38,3 +117,63 @@ cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl gd) {
38117
assert(!cir::MissingFeatures::opFuncAttributesForDefinition());
39118
return fn;
40119
}
120+
121+
// Global variables requiring non-trivial initialization are handled
122+
// differently in CIR than in classic codegen. Classic codegen emits
123+
// a global init function (__cxx_global_var_init) and inserts
124+
// initialization for each global there. In CIR, we attach a ctor
125+
// region to the global variable and insert the initialization code
126+
// into the ctor region. This will be moved into the
127+
// __cxx_global_var_init function during the LoweringPrepare pass.
128+
void CIRGenModule::emitCXXGlobalVarDeclInit(const VarDecl *varDecl,
129+
cir::GlobalOp addr,
130+
bool performInit) {
131+
QualType ty = varDecl->getType();
132+
133+
// TODO: handle address space
134+
// The address space of a static local variable (addr) may be different
135+
// from the address space of the "this" argument of the constructor. In that
136+
// case, we need an addrspacecast before calling the constructor.
137+
//
138+
// struct StructWithCtor {
139+
// __device__ StructWithCtor() {...}
140+
// };
141+
// __device__ void foo() {
142+
// __shared__ StructWithCtor s;
143+
// ...
144+
// }
145+
//
146+
// For example, in the above CUDA code, the static local variable s has a
147+
// "shared" address space qualifier, but the constructor of StructWithCtor
148+
// expects "this" in the "generic" address space.
149+
assert(!cir::MissingFeatures::addressSpace());
150+
151+
// Create a CIRGenFunction to emit the initializer. While this isn't a true
152+
// function, the handling works the same way.
153+
CIRGenFunction cgf{*this, builder, true};
154+
llvm::SaveAndRestore<CIRGenFunction *> savedCGF(curCGF, &cgf);
155+
curCGF->curFn = addr;
156+
157+
CIRGenFunction::SourceLocRAIIObject fnLoc{cgf,
158+
getLoc(varDecl->getLocation())};
159+
160+
assert(!cir::MissingFeatures::astVarDeclInterface());
161+
162+
if (!ty->isReferenceType()) {
163+
assert(!cir::MissingFeatures::openMP());
164+
165+
bool needsDtor = varDecl->needsDestruction(getASTContext()) ==
166+
QualType::DK_cxx_destructor;
167+
// PerformInit, constant store invariant / destroy handled below.
168+
if (performInit)
169+
emitDeclInit(cgf, varDecl, addr);
170+
171+
if (varDecl->getType().isConstantStorage(getASTContext(), true, !needsDtor))
172+
errorNYI(varDecl->getSourceRange(), "global with constant storage");
173+
else
174+
emitDeclDestroy(cgf, varDecl, addr);
175+
return;
176+
}
177+
178+
errorNYI(varDecl->getSourceRange(), "global with reference type");
179+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This contains code dealing with code generation of C++ declarations
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "CIRGenModule.h"
14+
#include "clang/AST/Attr.h"
15+
#include "clang/Basic/LangOptions.h"
16+
17+
using namespace clang;
18+
using namespace clang::CIRGen;
19+
20+
void CIRGenModule::emitCXXGlobalVarDeclInitFunc(const VarDecl *vd,
21+
cir::GlobalOp addr,
22+
bool performInit) {
23+
assert(!cir::MissingFeatures::cudaSupport());
24+
25+
assert(!cir::MissingFeatures::deferredCXXGlobalInit());
26+
27+
emitCXXGlobalVarDeclInit(vd, addr, performInit);
28+
}

clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,9 @@ class ConstExprEmitter
775775
}
776776

777777
mlir::Attribute VisitCXXConstructExpr(CXXConstructExpr *e, QualType ty) {
778-
cgm.errorNYI(e->getBeginLoc(), "ConstExprEmitter::VisitCXXConstructExpr");
778+
if (!e->getConstructor()->isTrivial())
779+
return nullptr;
780+
cgm.errorNYI(e->getBeginLoc(), "trivial constructor const handling");
779781
return {};
780782
}
781783

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,10 +342,12 @@ void CIRGenFunction::LexicalScope::cleanup() {
342342
cir::ReturnOp CIRGenFunction::LexicalScope::emitReturn(mlir::Location loc) {
343343
CIRGenBuilderTy &builder = cgf.getBuilder();
344344

345-
if (!cgf.curFn.getFunctionType().hasVoidReturn()) {
345+
auto fn = dyn_cast<cir::FuncOp>(cgf.curFn);
346+
assert(fn && "emitReturn from non-function");
347+
if (!fn.getFunctionType().hasVoidReturn()) {
346348
// Load the value from `__retval` and return it via the `cir.return` op.
347349
auto value = builder.create<cir::LoadOp>(
348-
loc, cgf.curFn.getFunctionType().getReturnType(), *cgf.fnRetAlloca);
350+
loc, fn.getFunctionType().getReturnType(), *cgf.fnRetAlloca);
349351
return builder.create<cir::ReturnOp>(loc,
350352
llvm::ArrayRef(value.getResult()));
351353
}
@@ -459,7 +461,9 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
459461
const auto *md = cast<CXXMethodDecl>(d);
460462
if (md->getParent()->isLambda() && md->getOverloadedOperator() == OO_Call) {
461463
// We're in a lambda.
462-
curFn.setLambda(true);
464+
auto fn = dyn_cast<cir::FuncOp>(curFn);
465+
assert(fn && "lambda in non-function region");
466+
fn.setLambda(true);
463467

464468
// Figure out the captures.
465469
md->getParent()->getCaptureFields(lambdaCaptureFields,

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,10 @@ class CIRGenFunction : public CIRGenTypeCache {
9898
/// This is the inner-most code context, which includes blocks.
9999
const clang::Decl *curCodeDecl = nullptr;
100100

101-
/// The function for which code is currently being generated.
102-
cir::FuncOp curFn;
101+
/// The current function or global initializer that is generated code for.
102+
/// This is usually a cir::FuncOp, but it can also be a cir::GlobalOp for
103+
/// global initializers.
104+
mlir::Operation *curFn = nullptr;
103105

104106
using DeclMapTy = llvm::DenseMap<const clang::Decl *, Address>;
105107
/// This keeps track of the CIR allocas or globals for local C
@@ -116,7 +118,11 @@ class CIRGenFunction : public CIRGenTypeCache {
116118
CIRGenModule &getCIRGenModule() { return cgm; }
117119
const CIRGenModule &getCIRGenModule() const { return cgm; }
118120

119-
mlir::Block *getCurFunctionEntryBlock() { return &curFn.getRegion().front(); }
121+
mlir::Block *getCurFunctionEntryBlock() {
122+
// We currently assume this isn't called for a global initializer.
123+
auto fn = mlir::cast<cir::FuncOp>(curFn);
124+
return &fn.getRegion().front();
125+
}
120126

121127
/// Sanitizers enabled for this function.
122128
clang::SanitizerSet sanOpts;

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,6 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd,
730730
// since this is the job for its original source.
731731
bool isDefinitionAvailableExternally =
732732
astContext.GetGVALinkageForVariable(vd) == GVA_AvailableExternally;
733-
assert(!cir::MissingFeatures::needsGlobalCtorDtor());
734733

735734
// It is useless to emit the definition for an available_externally variable
736735
// which can't be marked as const.
@@ -743,6 +742,10 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd,
743742
return;
744743

745744
mlir::Attribute init;
745+
bool needsGlobalCtor = false;
746+
bool needsGlobalDtor =
747+
!isDefinitionAvailableExternally &&
748+
vd->needsDestruction(astContext) == QualType::DK_cxx_destructor;
746749
const VarDecl *initDecl;
747750
const Expr *initExpr = vd->getAnyInitializer(initDecl);
748751

@@ -777,8 +780,8 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd,
777780
if (initDecl->hasFlexibleArrayInit(astContext))
778781
errorNYI(vd->getSourceRange(), "flexible array initializer");
779782
init = builder.getZeroInitAttr(convertType(qt));
780-
if (astContext.GetGVALinkageForVariable(vd) != GVA_AvailableExternally)
781-
errorNYI(vd->getSourceRange(), "global constructor");
783+
if (!isDefinitionAvailableExternally)
784+
needsGlobalCtor = true;
782785
} else {
783786
errorNYI(vd->getSourceRange(), "static initializer");
784787
}
@@ -787,8 +790,7 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd,
787790
// We don't need an initializer, so remove the entry for the delayed
788791
// initializer position (just in case this entry was delayed) if we
789792
// also don't need to register a destructor.
790-
if (vd->needsDestruction(astContext) == QualType::DK_cxx_destructor)
791-
errorNYI(vd->getSourceRange(), "delayed destructor");
793+
assert(!cir::MissingFeatures::deferredCXXGlobalInit());
792794
}
793795
}
794796

@@ -827,6 +829,9 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd,
827829
if (emitter)
828830
emitter->finalize(gv);
829831

832+
assert(!cir::MissingFeatures::opGlobalConstant());
833+
assert(!cir::MissingFeatures::opGlobalSection());
834+
830835
// Set CIR's linkage type as appropriate.
831836
cir::GlobalLinkageKind linkage =
832837
getCIRLinkageVarDefinition(vd, /*IsConstant=*/false);
@@ -844,6 +849,10 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd,
844849
assert(!cir::MissingFeatures::opGlobalThreadLocal());
845850

846851
maybeSetTrivialComdat(*vd, gv);
852+
853+
// Emit the initializer function if necessary.
854+
if (needsGlobalCtor || needsGlobalDtor)
855+
emitCXXGlobalVarDeclInitFunc(vd, gv, needsGlobalCtor);
847856
}
848857

849858
void CIRGenModule::emitGlobalDefinition(clang::GlobalDecl gd,

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,13 @@ class CIRGenModule : public CIRGenTypeCache {
426426
void emitGlobalVarDefinition(const clang::VarDecl *vd,
427427
bool isTentative = false);
428428

429+
/// Emit the function that initializes the specified global
430+
void emitCXXGlobalVarDeclInit(const VarDecl *varDecl, cir::GlobalOp addr,
431+
bool performInit);
432+
433+
void emitCXXGlobalVarDeclInitFunc(const VarDecl *vd, cir::GlobalOp addr,
434+
bool performInit);
435+
429436
void emitGlobalOpenACCDecl(const clang::OpenACCConstructDecl *cd);
430437

431438
// C++ related functions.

clang/lib/CIR/CodeGen/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ add_clang_library(clangCIR
1818
CIRGenCXXABI.cpp
1919
CIRGenBuiltin.cpp
2020
CIRGenDecl.cpp
21+
CIRGenDeclCXX.cpp
2122
CIRGenDeclOpenACC.cpp
2223
CIRGenException.cpp
2324
CIRGenExpr.cpp

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1711,6 +1711,11 @@ CIRToLLVMGlobalOpLowering::matchAndRewriteRegionInitializedGlobal(
17111711
mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
17121712
cir::GlobalOp op, OpAdaptor adaptor,
17131713
mlir::ConversionPatternRewriter &rewriter) const {
1714+
// If this global requires non-trivial initialization or destruction,
1715+
// that needs to be moved to runtime handlers during LoweringPrepare.
1716+
if (!op.getCtorRegion().empty() || !op.getDtorRegion().empty())
1717+
return op.emitError() << "GlobalOp ctor and dtor regions should be removed "
1718+
"in LoweringPrepare";
17141719

17151720
std::optional<mlir::Attribute> init = op.getInitialValue();
17161721

0 commit comments

Comments
 (0)