Skip to content

Commit 24bbc82

Browse files
authored
[CIR] Support for static variables (#143980)
This adds support for emitting static variables and their initializers.
1 parent f688480 commit 24bbc82

File tree

7 files changed

+383
-2
lines changed

7 files changed

+383
-2
lines changed

clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,25 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
185185
global.getSymName());
186186
}
187187

188+
mlir::Value createGetGlobal(cir::GlobalOp global) {
189+
return createGetGlobal(global.getLoc(), global);
190+
}
191+
188192
cir::StoreOp createStore(mlir::Location loc, mlir::Value val, mlir::Value dst,
189193
mlir::IntegerAttr align = {}) {
190194
return create<cir::StoreOp>(loc, val, dst, align);
191195
}
192196

197+
[[nodiscard]] cir::GlobalOp createGlobal(mlir::ModuleOp mlirModule,
198+
mlir::Location loc,
199+
mlir::StringRef name,
200+
mlir::Type type,
201+
cir::GlobalLinkageKind linkage) {
202+
mlir::OpBuilder::InsertionGuard guard(*this);
203+
setInsertionPointToStart(mlirModule.getBody());
204+
return create<cir::GlobalOp>(loc, name, type, linkage);
205+
}
206+
193207
cir::GetMemberOp createGetMember(mlir::Location loc, mlir::Type resultTy,
194208
mlir::Value base, llvm::StringRef name,
195209
unsigned index) {

clang/lib/CIR/CodeGen/CIRGenBuilder.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ namespace clang::CIRGen {
2424
class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
2525
const CIRGenTypeCache &typeCache;
2626
llvm::StringMap<unsigned> recordNames;
27+
llvm::StringMap<unsigned> globalsVersioning;
2728

2829
public:
2930
CIRGenBuilderTy(mlir::MLIRContext &mlirContext, const CIRGenTypeCache &tc)
@@ -371,6 +372,23 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
371372
/// pointed to by \p arrayPtr.
372373
mlir::Value maybeBuildArrayDecay(mlir::Location loc, mlir::Value arrayPtr,
373374
mlir::Type eltTy);
375+
376+
/// Creates a versioned global variable. If the symbol is already taken, an ID
377+
/// will be appended to the symbol. The returned global must always be queried
378+
/// for its name so it can be referenced correctly.
379+
[[nodiscard]] cir::GlobalOp
380+
createVersionedGlobal(mlir::ModuleOp module, mlir::Location loc,
381+
mlir::StringRef name, mlir::Type type,
382+
cir::GlobalLinkageKind linkage) {
383+
// Create a unique name if the given name is already taken.
384+
std::string uniqueName;
385+
if (unsigned version = globalsVersioning[name.str()]++)
386+
uniqueName = name.str() + "." + std::to_string(version);
387+
else
388+
uniqueName = name.str();
389+
390+
return createGlobal(module, loc, uniqueName, type, linkage);
391+
}
374392
};
375393

376394
} // namespace clang::CIRGen

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 246 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,25 @@ void CIRGenFunction::emitVarDecl(const VarDecl &d) {
208208
if (d.hasExternalStorage())
209209
return;
210210

211-
if (d.getStorageDuration() != SD_Automatic)
212-
cgm.errorNYI(d.getSourceRange(), "emitVarDecl automatic storage duration");
211+
if (d.getStorageDuration() != SD_Automatic) {
212+
// Static sampler variables translated to function calls.
213+
if (d.getType()->isSamplerT()) {
214+
// Nothing needs to be done here, but let's flag it as an error until we
215+
// have a test. It requires OpenCL support.
216+
cgm.errorNYI(d.getSourceRange(), "emitVarDecl static sampler type");
217+
return;
218+
}
219+
220+
cir::GlobalLinkageKind linkage =
221+
cgm.getCIRLinkageVarDefinition(&d, /*IsConstant=*/false);
222+
223+
// FIXME: We need to force the emission/use of a guard variable for
224+
// some variables even if we can constant-evaluate them because
225+
// we can't guarantee every translation unit will constant-evaluate them.
226+
227+
return emitStaticVarDecl(d, linkage);
228+
}
229+
213230
if (d.getType().getAddressSpace() == LangAS::opencl_local)
214231
cgm.errorNYI(d.getSourceRange(), "emitVarDecl openCL address space");
215232

@@ -219,6 +236,233 @@ void CIRGenFunction::emitVarDecl(const VarDecl &d) {
219236
return emitAutoVarDecl(d);
220237
}
221238

239+
static std::string getStaticDeclName(CIRGenModule &cgm, const VarDecl &d) {
240+
if (cgm.getLangOpts().CPlusPlus)
241+
return cgm.getMangledName(&d).str();
242+
243+
// If this isn't C++, we don't need a mangled name, just a pretty one.
244+
assert(!d.isExternallyVisible() && "name shouldn't matter");
245+
std::string contextName;
246+
const DeclContext *dc = d.getDeclContext();
247+
if (auto *cd = dyn_cast<CapturedDecl>(dc))
248+
dc = cast<DeclContext>(cd->getNonClosureContext());
249+
if (const auto *fd = dyn_cast<FunctionDecl>(dc))
250+
contextName = std::string(cgm.getMangledName(fd));
251+
else if (isa<BlockDecl>(dc))
252+
cgm.errorNYI(d.getSourceRange(), "block decl context for static var");
253+
else if (isa<ObjCMethodDecl>(dc))
254+
cgm.errorNYI(d.getSourceRange(), "ObjC decl context for static var");
255+
else
256+
cgm.errorNYI(d.getSourceRange(), "Unknown context for static var decl");
257+
258+
contextName += "." + d.getNameAsString();
259+
return contextName;
260+
}
261+
262+
// TODO(cir): LLVM uses a Constant base class. Maybe CIR could leverage an
263+
// interface for all constants?
264+
cir::GlobalOp
265+
CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &d,
266+
cir::GlobalLinkageKind linkage) {
267+
// In general, we don't always emit static var decls once before we reference
268+
// them. It is possible to reference them before emitting the function that
269+
// contains them, and it is possible to emit the containing function multiple
270+
// times.
271+
if (cir::GlobalOp existingGV = getStaticLocalDeclAddress(&d))
272+
return existingGV;
273+
274+
QualType ty = d.getType();
275+
assert(ty->isConstantSizeType() && "VLAs can't be static");
276+
277+
// Use the label if the variable is renamed with the asm-label extension.
278+
if (d.hasAttr<AsmLabelAttr>())
279+
errorNYI(d.getSourceRange(), "getOrCreateStaticVarDecl: asm label");
280+
281+
std::string name = getStaticDeclName(*this, d);
282+
283+
mlir::Type lty = getTypes().convertTypeForMem(ty);
284+
assert(!cir::MissingFeatures::addressSpace());
285+
286+
if (d.hasAttr<LoaderUninitializedAttr>() || d.hasAttr<CUDASharedAttr>())
287+
errorNYI(d.getSourceRange(),
288+
"getOrCreateStaticVarDecl: LoaderUninitializedAttr");
289+
assert(!cir::MissingFeatures::addressSpace());
290+
291+
mlir::Attribute init = builder.getZeroInitAttr(convertType(ty));
292+
293+
cir::GlobalOp gv = builder.createVersionedGlobal(
294+
getModule(), getLoc(d.getLocation()), name, lty, linkage);
295+
// TODO(cir): infer visibility from linkage in global op builder.
296+
gv.setVisibility(getMLIRVisibilityFromCIRLinkage(linkage));
297+
gv.setInitialValueAttr(init);
298+
gv.setAlignment(getASTContext().getDeclAlign(&d).getAsAlign().value());
299+
300+
if (supportsCOMDAT() && gv.isWeakForLinker())
301+
gv.setComdat(true);
302+
303+
assert(!cir::MissingFeatures::opGlobalThreadLocal());
304+
305+
setGVProperties(gv, &d);
306+
307+
// OG checks if the expected address space, denoted by the type, is the
308+
// same as the actual address space indicated by attributes. If they aren't
309+
// the same, an addrspacecast is emitted when this variable is accessed.
310+
// In CIR however, cir.get_global already carries that information in
311+
// !cir.ptr type - if this global is in OpenCL local address space, then its
312+
// type would be !cir.ptr<..., addrspace(offload_local)>. Therefore we don't
313+
// need an explicit address space cast in CIR: they will get emitted when
314+
// lowering to LLVM IR.
315+
316+
// Ensure that the static local gets initialized by making sure the parent
317+
// function gets emitted eventually.
318+
const Decl *dc = cast<Decl>(d.getDeclContext());
319+
320+
// We can't name blocks or captured statements directly, so try to emit their
321+
// parents.
322+
if (isa<BlockDecl>(dc) || isa<CapturedDecl>(dc)) {
323+
dc = dc->getNonClosureContext();
324+
// FIXME: Ensure that global blocks get emitted.
325+
if (!dc)
326+
errorNYI(d.getSourceRange(), "non-closure context");
327+
}
328+
329+
GlobalDecl gd;
330+
if (isa<CXXConstructorDecl>(dc))
331+
errorNYI(d.getSourceRange(), "C++ constructors static var context");
332+
else if (isa<CXXDestructorDecl>(dc))
333+
errorNYI(d.getSourceRange(), "C++ destructors static var context");
334+
else if (const auto *fd = dyn_cast<FunctionDecl>(dc))
335+
gd = GlobalDecl(fd);
336+
else {
337+
// Don't do anything for Obj-C method decls or global closures. We should
338+
// never defer them.
339+
assert(isa<ObjCMethodDecl>(dc) && "unexpected parent code decl");
340+
}
341+
if (gd.getDecl() && cir::MissingFeatures::openMP()) {
342+
// Disable emission of the parent function for the OpenMP device codegen.
343+
errorNYI(d.getSourceRange(), "OpenMP");
344+
}
345+
346+
return gv;
347+
}
348+
349+
/// Add the initializer for 'd' to the global variable that has already been
350+
/// created for it. If the initializer has a different type than gv does, this
351+
/// may free gv and return a different one. Otherwise it just returns gv.
352+
cir::GlobalOp CIRGenFunction::addInitializerToStaticVarDecl(
353+
const VarDecl &d, cir::GlobalOp gv, cir::GetGlobalOp gvAddr) {
354+
ConstantEmitter emitter(*this);
355+
mlir::TypedAttr init =
356+
mlir::cast<mlir::TypedAttr>(emitter.tryEmitForInitializer(d));
357+
358+
// If constant emission failed, then this should be a C++ static
359+
// initializer.
360+
if (!init) {
361+
cgm.errorNYI(d.getSourceRange(), "static var without initializer");
362+
return gv;
363+
}
364+
365+
// TODO(cir): There should be debug code here to assert that the decl size
366+
// matches the CIR data layout type alloc size, but the code for calculating
367+
// the type alloc size is not implemented yet.
368+
assert(!cir::MissingFeatures::dataLayoutTypeAllocSize());
369+
370+
// The initializer may differ in type from the global. Rewrite
371+
// the global to match the initializer. (We have to do this
372+
// because some types, like unions, can't be completely represented
373+
// in the LLVM type system.)
374+
if (gv.getSymType() != init.getType()) {
375+
gv.setSymType(init.getType());
376+
377+
// Normally this should be done with a call to cgm.replaceGlobal(oldGV, gv),
378+
// but since at this point the current block hasn't been really attached,
379+
// there's no visibility into the GetGlobalOp corresponding to this Global.
380+
// Given those constraints, thread in the GetGlobalOp and update it
381+
// directly.
382+
assert(!cir::MissingFeatures::addressSpace());
383+
gvAddr.getAddr().setType(builder.getPointerTo(init.getType()));
384+
}
385+
386+
bool needsDtor =
387+
d.needsDestruction(getContext()) == QualType::DK_cxx_destructor;
388+
389+
assert(!cir::MissingFeatures::opGlobalConstant());
390+
gv.setInitialValueAttr(init);
391+
392+
emitter.finalize(gv);
393+
394+
if (needsDtor) {
395+
// We have a constant initializer, but a nontrivial destructor. We still
396+
// need to perform a guarded "initialization" in order to register the
397+
// destructor.
398+
cgm.errorNYI(d.getSourceRange(), "C++ guarded init");
399+
}
400+
401+
return gv;
402+
}
403+
404+
void CIRGenFunction::emitStaticVarDecl(const VarDecl &d,
405+
cir::GlobalLinkageKind linkage) {
406+
// Check to see if we already have a global variable for this
407+
// declaration. This can happen when double-emitting function
408+
// bodies, e.g. with complete and base constructors.
409+
cir::GlobalOp globalOp = cgm.getOrCreateStaticVarDecl(d, linkage);
410+
// TODO(cir): we should have a way to represent global ops as values without
411+
// having to emit a get global op. Sometimes these emissions are not used.
412+
mlir::Value addr = builder.createGetGlobal(globalOp);
413+
auto getAddrOp = mlir::cast<cir::GetGlobalOp>(addr.getDefiningOp());
414+
415+
CharUnits alignment = getContext().getDeclAlign(&d);
416+
417+
// Store into LocalDeclMap before generating initializer to handle
418+
// circular references.
419+
mlir::Type elemTy = convertTypeForMem(d.getType());
420+
setAddrOfLocalVar(&d, Address(addr, elemTy, alignment));
421+
422+
// We can't have a VLA here, but we can have a pointer to a VLA,
423+
// even though that doesn't really make any sense.
424+
// Make sure to evaluate VLA bounds now so that we have them for later.
425+
if (d.getType()->isVariablyModifiedType()) {
426+
cgm.errorNYI(d.getSourceRange(),
427+
"emitStaticVarDecl: variably modified type");
428+
}
429+
430+
// Save the type in case adding the initializer forces a type change.
431+
mlir::Type expectedType = addr.getType();
432+
433+
cir::GlobalOp var = globalOp;
434+
435+
assert(!cir::MissingFeatures::cudaSupport());
436+
437+
// If this value has an initializer, emit it.
438+
if (d.getInit())
439+
var = addInitializerToStaticVarDecl(d, var, getAddrOp);
440+
441+
var.setAlignment(alignment.getAsAlign().value());
442+
443+
// There are a lot of attributes that need to be handled here. Until
444+
// we start to support them, we just report an error if there are any.
445+
if (d.hasAttrs())
446+
cgm.errorNYI(d.getSourceRange(), "static var with attrs");
447+
448+
if (cgm.getCodeGenOpts().KeepPersistentStorageVariables)
449+
cgm.errorNYI(d.getSourceRange(), "static var keep persistent storage");
450+
451+
// From traditional codegen:
452+
// We may have to cast the constant because of the initializer
453+
// mismatch above.
454+
//
455+
// FIXME: It is really dangerous to store this in the map; if anyone
456+
// RAUW's the GV uses of this constant will be invalid.
457+
mlir::Value castedAddr =
458+
builder.createBitcast(getAddrOp.getAddr(), expectedType);
459+
localDeclMap.find(&d)->second = Address(castedAddr, elemTy, alignment);
460+
cgm.setStaticLocalDeclAddress(&d, var);
461+
462+
assert(!cir::MissingFeatures::sanitizers());
463+
assert(!cir::MissingFeatures::generateDebugInfo());
464+
}
465+
222466
void CIRGenFunction::emitScalarInit(const Expr *init, mlir::Location loc,
223467
LValue lvalue, bool capturedByInit) {
224468
assert(!cir::MissingFeatures::objCLifetime());

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,10 @@ class CIRGenFunction : public CIRGenTypeCache {
469469
/// compare the result against zero, returning an Int1Ty value.
470470
mlir::Value evaluateExprAsBool(const clang::Expr *e);
471471

472+
cir::GlobalOp addInitializerToStaticVarDecl(const VarDecl &d,
473+
cir::GlobalOp gv,
474+
cir::GetGlobalOp gvAddr);
475+
472476
/// Set the address of a local variable.
473477
void setAddrOfLocalVar(const clang::VarDecl *vd, Address addr) {
474478
assert(!localDeclMap.count(vd) && "Decl already exists in LocalDeclMap!");
@@ -955,6 +959,8 @@ class CIRGenFunction : public CIRGenTypeCache {
955959
void emitScalarInit(const clang::Expr *init, mlir::Location loc,
956960
LValue lvalue, bool capturedByInit = false);
957961

962+
void emitStaticVarDecl(const VarDecl &d, cir::GlobalLinkageKind linkage);
963+
958964
void emitStoreOfScalar(mlir::Value value, Address addr, bool isVolatile,
959965
clang::QualType ty, bool isInit = false,
960966
bool isNontemporal = false);

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,21 @@ class CIRGenModule : public CIRGenTypeCache {
113113

114114
mlir::Operation *lastGlobalOp = nullptr;
115115

116+
llvm::DenseMap<const Decl *, cir::GlobalOp> staticLocalDeclMap;
117+
116118
mlir::Operation *getGlobalValue(llvm::StringRef ref);
117119

120+
cir::GlobalOp getStaticLocalDeclAddress(const VarDecl *d) {
121+
return staticLocalDeclMap[d];
122+
}
123+
124+
void setStaticLocalDeclAddress(const VarDecl *d, cir::GlobalOp c) {
125+
staticLocalDeclMap[d] = c;
126+
}
127+
128+
cir::GlobalOp getOrCreateStaticVarDecl(const VarDecl &d,
129+
cir::GlobalLinkageKind linkage);
130+
118131
/// If the specified mangled name is not in the module, create and return an
119132
/// mlir::GlobalOp value
120133
cir::GlobalOp getOrCreateCIRGlobal(llvm::StringRef mangledName, mlir::Type ty,

clang/test/CIR/CodeGen/static-vars.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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
3+
4+
void func1(void) {
5+
// Should lower default-initialized static vars.
6+
static int i;
7+
// CHECK-DAG: cir.global "private" internal dsolocal @func1.i = #cir.int<0> : !s32i
8+
9+
// Should lower constant-initialized static vars.
10+
static int j = 1;
11+
// CHECK-DAG: cir.global "private" internal dsolocal @func1.j = #cir.int<1> : !s32i
12+
13+
// Should properly shadow static vars in nested scopes.
14+
{
15+
static int j = 2;
16+
// CHECK-DAG: cir.global "private" internal dsolocal @func1.j.1 = #cir.int<2> : !s32i
17+
}
18+
{
19+
static int j = 3;
20+
// CHECK-DAG: cir.global "private" internal dsolocal @func1.j.2 = #cir.int<3> : !s32i
21+
}
22+
23+
// Should lower basic static vars arithmetics.
24+
j++;
25+
// CHECK-DAG: %[[#V2:]] = cir.get_global @func1.j : !cir.ptr<!s32i>
26+
// CHECK-DAG: %[[#V3:]] = cir.load{{.*}} %[[#V2]] : !cir.ptr<!s32i>, !s32i
27+
// CHECK-DAG: %[[#V4:]] = cir.unary(inc, %[[#V3]]) nsw : !s32i, !s32i
28+
// CHECK-DAG: cir.store{{.*}} %[[#V4]], %[[#V2]] : !s32i, !cir.ptr<!s32i>
29+
}
30+
31+
// Should shadow static vars on different functions.
32+
void func2(void) {
33+
static char i;
34+
// CHECK-DAG: cir.global "private" internal dsolocal @func2.i = #cir.int<0> : !s8i
35+
static float j;
36+
// CHECK-DAG: cir.global "private" internal dsolocal @func2.j = #cir.fp<0.000000e+00> : !cir.float
37+
}

0 commit comments

Comments
 (0)