llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clangir Author: Andy Kaylor (andykaylor) <details> <summary>Changes</summary> This adds support for emitting static variables and their initializers. --- Patch is 21.07 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/143980.diff 8 Files Affected: - (modified) clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h (+12) - (modified) clang/include/clang/CIR/MissingFeatures.h (+1) - (modified) clang/lib/CIR/CodeGen/CIRGenBuilder.h (+18) - (modified) clang/lib/CIR/CodeGen/CIRGenDecl.cpp (+265-2) - (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+6) - (modified) clang/lib/CIR/CodeGen/CIRGenModule.h (+13) - (added) clang/test/CIR/CodeGen/static-vars.c (+37) - (added) clang/test/CIR/CodeGen/static-vars.cpp (+49) ``````````diff diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index a3754f4de66b0..dfff2740c75b5 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -185,11 +185,23 @@ 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 module, mlir::Location loc, mlir::StringRef name, + mlir::Type type, cir::GlobalLinkageKind linkage) { + mlir::OpBuilder::InsertionGuard guard(*this); + setInsertionPointToStart(module.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) { diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index fbd15d5c886d2..570b1544a6964 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -230,6 +230,7 @@ struct MissingFeatures { static bool emitCtorPrologue() { return false; } static bool thunks() { return false; } static bool runCleanupsScope() { return false; } + static bool dataLayoutAllocaSize() { return false; } // Missing types static bool dataMemberType() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h index fb1a290c18fa2..c81301e9a6fe1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h +++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h @@ -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) @@ -358,6 +359,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 diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp index 80b0172090aa3..1d703d78c8bf3 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -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"); @@ -219,6 +236,252 @@ 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. + std::string name; + if (d.hasAttr<AsmLabelAttr>()) + errorNYI(d.getSourceRange(), "getOrCreateStaticVarDecl: asm label"); + else + name = getStaticDeclName(*this, d); + + mlir::Type lty = getTypes().convertTypeForMem(ty); + assert(!cir::MissingFeatures::addressSpace()); + + // OpenCL variables in local address space and CUDA shared + // variables cannot have an initializer. + mlir::Attribute init = nullptr; + if (d.hasAttr<LoaderUninitializedAttr>()) + errorNYI(d.getSourceRange(), + "getOrCreateStaticVarDecl: LoaderUninitializedAttr"); + + 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 alloca size, but the code for calculating the + // alloca size is not implemented yet. + assert(!cir::MissingFeatures::dataLayoutAllocaSize()); + + // 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()); + + if (d.hasAttr<AnnotateAttr>()) + cgm.errorNYI(d.getSourceRange(), "static var annotation"); + + if (d.getAttr<PragmaClangBSSSectionAttr>()) + cgm.errorNYI(d.getSourceRange(), "static var BSS section attribute"); + if (d.getAttr<PragmaClangDataSectionAttr>()) + cgm.errorNYI(d.getSourceRange(), "static var Data section attribute"); + if (d.getAttr<PragmaClangRodataSectionAttr>()) + cgm.errorNYI(d.getSourceRange(), "static var Rodata section attribute"); + if (d.getAttr<PragmaClangRelroSectionAttr>()) + cgm.errorNYI(d.getSourceRange(), "static var Relro section attribute"); + + if (d.getAttr<SectionAttr>()) + cgm.errorNYI(d.getSourceRange(), + "static var object file section attribute"); + + if (d.hasAttr<RetainAttr>()) + cgm.errorNYI(d.getSourceRange(), "static var retain attribute"); + else if (d.hasAttr<UsedAttr>()) + cgm.errorNYI(d.getSourceRange(), "static var used attribute"); + + 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()); diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 361dcd5ef1c31..56ecee3f45367 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -465,6 +465,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!"); @@ -935,6 +939,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); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index f76fd8e733642..03606dba200fd 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -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, diff --git a/clang/test/CIR/CodeGen/static-vars.c b/clang/test/CIR/CodeGen/static-vars.c new file mode 100644 index 0000000000000..f45a41d9a00fc --- /dev/null +++ b/clang/test/CIR/CodeGen/static-vars.c @@ -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 +} diff --git a/clang/test/CIR/CodeGen/static-vars.cpp b/clang/test/CIR/CodeGen/static-vars.cpp new file mode 100644 index 0000000000000..9b892c69a6fed --- /dev/null +++ b/clang/test/CIR/CodeGen/static-vars.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t1.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t1.ll %s + +void func1(void) { + // Should lower default-initialized static vars. + static int i; + // CHECK-DAG: cir.global "private" internal dsolocal @_ZZ5func1vE1i = #cir.int<0> : !s32i + + // Should lower constant-initialized static vars. + static int j = 1; + // CHECK-DAG: cir.global "private" internal dsolocal @_ZZ5func1vE1j = #cir.int<1> : !s32i + + // Should properly shadow static vars in nested scopes. + { + static int j = 2; + // CHECK-DAG: cir.global "private" internal dsolocal @_ZZ5func1vE1j_0 = #cir.int<2> : !s32i + } + { + static int j = 3; + // CHECK-DAG: cir.global "private" internal ds... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/143980 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits