Skip to content
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
14 changes: 14 additions & 0 deletions clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,25 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
global.getSymName());
}

mlir::Value createGetGlobal(cir::GlobalOp global) {
return createGetGlobal(global.getLoc(), global);
}

cir::StoreOp createStore(mlir::Location loc, mlir::Value val, mlir::Value dst,
mlir::IntegerAttr align = {}) {
return create<cir::StoreOp>(loc, val, dst, align);
}

[[nodiscard]] cir::GlobalOp createGlobal(mlir::ModuleOp mlirModule,
mlir::Location loc,
mlir::StringRef name,
mlir::Type type,
cir::GlobalLinkageKind linkage) {
mlir::OpBuilder::InsertionGuard guard(*this);
setInsertionPointToStart(mlirModule.getBody());
return create<cir::GlobalOp>(loc, name, type, linkage);
}

cir::GetMemberOp createGetMember(mlir::Location loc, mlir::Type resultTy,
mlir::Value base, llvm::StringRef name,
unsigned index) {
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ namespace clang::CIRGen {
class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
const CIRGenTypeCache &typeCache;
llvm::StringMap<unsigned> recordNames;
llvm::StringMap<unsigned> globalsVersioning;

public:
CIRGenBuilderTy(mlir::MLIRContext &mlirContext, const CIRGenTypeCache &tc)
Expand Down Expand Up @@ -370,6 +371,23 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
/// pointed to by \p arrayPtr.
mlir::Value maybeBuildArrayDecay(mlir::Location loc, mlir::Value arrayPtr,
mlir::Type eltTy);

/// Creates a versioned global variable. If the symbol is already taken, an ID
/// will be appended to the symbol. The returned global must always be queried
/// for its name so it can be referenced correctly.
[[nodiscard]] cir::GlobalOp
createVersionedGlobal(mlir::ModuleOp module, mlir::Location loc,
mlir::StringRef name, mlir::Type type,
cir::GlobalLinkageKind linkage) {
// Create a unique name if the given name is already taken.
std::string uniqueName;
if (unsigned version = globalsVersioning[name.str()]++)
uniqueName = name.str() + "." + std::to_string(version);
else
uniqueName = name.str();

return createGlobal(module, loc, uniqueName, type, linkage);
}
};

} // namespace clang::CIRGen
Expand Down
248 changes: 246 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,25 @@ void CIRGenFunction::emitVarDecl(const VarDecl &d) {
if (d.hasExternalStorage())
return;

if (d.getStorageDuration() != SD_Automatic)
cgm.errorNYI(d.getSourceRange(), "emitVarDecl automatic storage duration");
if (d.getStorageDuration() != SD_Automatic) {
// Static sampler variables translated to function calls.
if (d.getType()->isSamplerT()) {
// Nothing needs to be done here, but let's flag it as an error until we
// have a test. It requires OpenCL support.
cgm.errorNYI(d.getSourceRange(), "emitVarDecl static sampler type");
return;
}

cir::GlobalLinkageKind linkage =
cgm.getCIRLinkageVarDefinition(&d, /*IsConstant=*/false);

// FIXME: We need to force the emission/use of a guard variable for
// some variables even if we can constant-evaluate them because
// we can't guarantee every translation unit will constant-evaluate them.

return emitStaticVarDecl(d, linkage);
}

if (d.getType().getAddressSpace() == LangAS::opencl_local)
cgm.errorNYI(d.getSourceRange(), "emitVarDecl openCL address space");

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

static std::string getStaticDeclName(CIRGenModule &cgm, const VarDecl &d) {
if (cgm.getLangOpts().CPlusPlus)
return cgm.getMangledName(&d).str();

// If this isn't C++, we don't need a mangled name, just a pretty one.
assert(!d.isExternallyVisible() && "name shouldn't matter");
std::string contextName;
const DeclContext *dc = d.getDeclContext();
if (auto *cd = dyn_cast<CapturedDecl>(dc))
dc = cast<DeclContext>(cd->getNonClosureContext());
if (const auto *fd = dyn_cast<FunctionDecl>(dc))
contextName = std::string(cgm.getMangledName(fd));
else if (isa<BlockDecl>(dc))
cgm.errorNYI(d.getSourceRange(), "block decl context for static var");
else if (isa<ObjCMethodDecl>(dc))
cgm.errorNYI(d.getSourceRange(), "ObjC decl context for static var");
else
cgm.errorNYI(d.getSourceRange(), "Unknown context for static var decl");

contextName += "." + d.getNameAsString();
return contextName;
}

// TODO(cir): LLVM uses a Constant base class. Maybe CIR could leverage an
// interface for all constants?
cir::GlobalOp
CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &d,
cir::GlobalLinkageKind linkage) {
// In general, we don't always emit static var decls once before we reference
// them. It is possible to reference them before emitting the function that
// contains them, and it is possible to emit the containing function multiple
// times.
if (cir::GlobalOp existingGV = getStaticLocalDeclAddress(&d))
return existingGV;

QualType ty = d.getType();
assert(ty->isConstantSizeType() && "VLAs can't be static");

// Use the label if the variable is renamed with the asm-label extension.
if (d.hasAttr<AsmLabelAttr>())
errorNYI(d.getSourceRange(), "getOrCreateStaticVarDecl: asm label");

std::string name = getStaticDeclName(*this, d);

mlir::Type lty = getTypes().convertTypeForMem(ty);
assert(!cir::MissingFeatures::addressSpace());

if (d.hasAttr<LoaderUninitializedAttr>() || d.hasAttr<CUDASharedAttr>())
errorNYI(d.getSourceRange(),
"getOrCreateStaticVarDecl: LoaderUninitializedAttr");
assert(!cir::MissingFeatures::addressSpace());

mlir::Attribute init = builder.getZeroInitAttr(convertType(ty));

cir::GlobalOp gv = builder.createVersionedGlobal(
getModule(), getLoc(d.getLocation()), name, lty, linkage);
// TODO(cir): infer visibility from linkage in global op builder.
gv.setVisibility(getMLIRVisibilityFromCIRLinkage(linkage));
gv.setInitialValueAttr(init);
gv.setAlignment(getASTContext().getDeclAlign(&d).getAsAlign().value());

if (supportsCOMDAT() && gv.isWeakForLinker())
gv.setComdat(true);

assert(!cir::MissingFeatures::opGlobalThreadLocal());

setGVProperties(gv, &d);

// OG checks if the expected address space, denoted by the type, is the
// same as the actual address space indicated by attributes. If they aren't
// the same, an addrspacecast is emitted when this variable is accessed.
// In CIR however, cir.get_global already carries that information in
// !cir.ptr type - if this global is in OpenCL local address space, then its
// type would be !cir.ptr<..., addrspace(offload_local)>. Therefore we don't
// need an explicit address space cast in CIR: they will get emitted when
// lowering to LLVM IR.

// Ensure that the static local gets initialized by making sure the parent
// function gets emitted eventually.
const Decl *dc = cast<Decl>(d.getDeclContext());

// We can't name blocks or captured statements directly, so try to emit their
// parents.
if (isa<BlockDecl>(dc) || isa<CapturedDecl>(dc)) {
dc = dc->getNonClosureContext();
// FIXME: Ensure that global blocks get emitted.
if (!dc)
errorNYI(d.getSourceRange(), "non-closure context");
}

GlobalDecl gd;
if (isa<CXXConstructorDecl>(dc))
errorNYI(d.getSourceRange(), "C++ constructors static var context");
else if (isa<CXXDestructorDecl>(dc))
errorNYI(d.getSourceRange(), "C++ destructors static var context");
else if (const auto *fd = dyn_cast<FunctionDecl>(dc))
gd = GlobalDecl(fd);
else {
// Don't do anything for Obj-C method decls or global closures. We should
// never defer them.
assert(isa<ObjCMethodDecl>(dc) && "unexpected parent code decl");
}
if (gd.getDecl() && cir::MissingFeatures::openMP()) {
// Disable emission of the parent function for the OpenMP device codegen.
errorNYI(d.getSourceRange(), "OpenMP");
}

return gv;
}

/// Add the initializer for 'd' to the global variable that has already been
/// created for it. If the initializer has a different type than gv does, this
/// may free gv and return a different one. Otherwise it just returns gv.
cir::GlobalOp CIRGenFunction::addInitializerToStaticVarDecl(
const VarDecl &d, cir::GlobalOp gv, cir::GetGlobalOp gvAddr) {
ConstantEmitter emitter(*this);
mlir::TypedAttr init =
mlir::cast<mlir::TypedAttr>(emitter.tryEmitForInitializer(d));

// If constant emission failed, then this should be a C++ static
// initializer.
if (!init) {
cgm.errorNYI(d.getSourceRange(), "static var without initializer");
return gv;
}

// TODO(cir): There should be debug code here to assert that the decl size
// matches the CIR data layout type alloc size, but the code for calculating
// the type alloc size is not implemented yet.
assert(!cir::MissingFeatures::dataLayoutTypeAllocSize());

// The initializer may differ in type from the global. Rewrite
// the global to match the initializer. (We have to do this
// because some types, like unions, can't be completely represented
// in the LLVM type system.)
if (gv.getSymType() != init.getType()) {
gv.setSymType(init.getType());

// Normally this should be done with a call to cgm.replaceGlobal(oldGV, gv),
// but since at this point the current block hasn't been really attached,
// there's no visibility into the GetGlobalOp corresponding to this Global.
// Given those constraints, thread in the GetGlobalOp and update it
// directly.
assert(!cir::MissingFeatures::addressSpace());
gvAddr.getAddr().setType(builder.getPointerTo(init.getType()));
}

bool needsDtor =
d.needsDestruction(getContext()) == QualType::DK_cxx_destructor;

assert(!cir::MissingFeatures::opGlobalConstant());
gv.setInitialValueAttr(init);

emitter.finalize(gv);

if (needsDtor) {
// We have a constant initializer, but a nontrivial destructor. We still
// need to perform a guarded "initialization" in order to register the
// destructor.
cgm.errorNYI(d.getSourceRange(), "C++ guarded init");
}

return gv;
}

void CIRGenFunction::emitStaticVarDecl(const VarDecl &d,
cir::GlobalLinkageKind linkage) {
// Check to see if we already have a global variable for this
// declaration. This can happen when double-emitting function
// bodies, e.g. with complete and base constructors.
cir::GlobalOp globalOp = cgm.getOrCreateStaticVarDecl(d, linkage);
// TODO(cir): we should have a way to represent global ops as values without
// having to emit a get global op. Sometimes these emissions are not used.
mlir::Value addr = builder.createGetGlobal(globalOp);
auto getAddrOp = mlir::cast<cir::GetGlobalOp>(addr.getDefiningOp());

CharUnits alignment = getContext().getDeclAlign(&d);

// Store into LocalDeclMap before generating initializer to handle
// circular references.
mlir::Type elemTy = convertTypeForMem(d.getType());
setAddrOfLocalVar(&d, Address(addr, elemTy, alignment));

// We can't have a VLA here, but we can have a pointer to a VLA,
// even though that doesn't really make any sense.
// Make sure to evaluate VLA bounds now so that we have them for later.
if (d.getType()->isVariablyModifiedType()) {
cgm.errorNYI(d.getSourceRange(),
"emitStaticVarDecl: variably modified type");
}

// Save the type in case adding the initializer forces a type change.
mlir::Type expectedType = addr.getType();

cir::GlobalOp var = globalOp;

assert(!cir::MissingFeatures::cudaSupport());

// If this value has an initializer, emit it.
if (d.getInit())
var = addInitializerToStaticVarDecl(d, var, getAddrOp);

var.setAlignment(alignment.getAsAlign().value());

// There are a lot of attributes that need to be handled here. Until
// we start to support them, we just report an error if there are any.
if (d.hasAttrs())
cgm.errorNYI(d.getSourceRange(), "static var with attrs");

if (cgm.getCodeGenOpts().KeepPersistentStorageVariables)
cgm.errorNYI(d.getSourceRange(), "static var keep persistent storage");

// From traditional codegen:
// We may have to cast the constant because of the initializer
// mismatch above.
//
// FIXME: It is really dangerous to store this in the map; if anyone
// RAUW's the GV uses of this constant will be invalid.
mlir::Value castedAddr =
builder.createBitcast(getAddrOp.getAddr(), expectedType);
localDeclMap.find(&d)->second = Address(castedAddr, elemTy, alignment);
cgm.setStaticLocalDeclAddress(&d, var);

assert(!cir::MissingFeatures::sanitizers());
assert(!cir::MissingFeatures::generateDebugInfo());
}

void CIRGenFunction::emitScalarInit(const Expr *init, mlir::Location loc,
LValue lvalue, bool capturedByInit) {
assert(!cir::MissingFeatures::objCLifetime());
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,10 @@ class CIRGenFunction : public CIRGenTypeCache {
/// compare the result against zero, returning an Int1Ty value.
mlir::Value evaluateExprAsBool(const clang::Expr *e);

cir::GlobalOp addInitializerToStaticVarDecl(const VarDecl &d,
cir::GlobalOp gv,
cir::GetGlobalOp gvAddr);

/// Set the address of a local variable.
void setAddrOfLocalVar(const clang::VarDecl *vd, Address addr) {
assert(!localDeclMap.count(vd) && "Decl already exists in LocalDeclMap!");
Expand Down Expand Up @@ -955,6 +959,8 @@ class CIRGenFunction : public CIRGenTypeCache {
void emitScalarInit(const clang::Expr *init, mlir::Location loc,
LValue lvalue, bool capturedByInit = false);

void emitStaticVarDecl(const VarDecl &d, cir::GlobalLinkageKind linkage);

void emitStoreOfScalar(mlir::Value value, Address addr, bool isVolatile,
clang::QualType ty, bool isInit = false,
bool isNontemporal = false);
Expand Down
13 changes: 13 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,21 @@ class CIRGenModule : public CIRGenTypeCache {

mlir::Operation *lastGlobalOp = nullptr;

llvm::DenseMap<const Decl *, cir::GlobalOp> staticLocalDeclMap;

mlir::Operation *getGlobalValue(llvm::StringRef ref);

cir::GlobalOp getStaticLocalDeclAddress(const VarDecl *d) {
return staticLocalDeclMap[d];
}

void setStaticLocalDeclAddress(const VarDecl *d, cir::GlobalOp c) {
staticLocalDeclMap[d] = c;
}

cir::GlobalOp getOrCreateStaticVarDecl(const VarDecl &d,
cir::GlobalLinkageKind linkage);

/// If the specified mangled name is not in the module, create and return an
/// mlir::GlobalOp value
cir::GlobalOp getOrCreateCIRGlobal(llvm::StringRef mangledName, mlir::Type ty,
Expand Down
37 changes: 37 additions & 0 deletions clang/test/CIR/CodeGen/static-vars.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s

void func1(void) {
// Should lower default-initialized static vars.
static int i;
// CHECK-DAG: cir.global "private" internal dsolocal @func1.i = #cir.int<0> : !s32i

// Should lower constant-initialized static vars.
static int j = 1;
// CHECK-DAG: cir.global "private" internal dsolocal @func1.j = #cir.int<1> : !s32i

// Should properly shadow static vars in nested scopes.
{
static int j = 2;
// CHECK-DAG: cir.global "private" internal dsolocal @func1.j.1 = #cir.int<2> : !s32i
}
{
static int j = 3;
// CHECK-DAG: cir.global "private" internal dsolocal @func1.j.2 = #cir.int<3> : !s32i
}

// Should lower basic static vars arithmetics.
j++;
// CHECK-DAG: %[[#V2:]] = cir.get_global @func1.j : !cir.ptr<!s32i>
// CHECK-DAG: %[[#V3:]] = cir.load{{.*}} %[[#V2]] : !cir.ptr<!s32i>, !s32i
// CHECK-DAG: %[[#V4:]] = cir.unary(inc, %[[#V3]]) nsw : !s32i, !s32i
// CHECK-DAG: cir.store{{.*}} %[[#V4]], %[[#V2]] : !s32i, !cir.ptr<!s32i>
}

// Should shadow static vars on different functions.
void func2(void) {
static char i;
// CHECK-DAG: cir.global "private" internal dsolocal @func2.i = #cir.int<0> : !s8i
static float j;
// CHECK-DAG: cir.global "private" internal dsolocal @func2.j = #cir.fp<0.000000e+00> : !cir.float
}
Loading