https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/135095
>From 43f2d5e73037559055dcbe65d8871c05c532d9f0 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <akay...@nvidia.com> Date: Wed, 9 Apr 2025 14:25:35 -0700 Subject: [PATCH 1/2] [CIR] Upstream support for cir.get_global This adds basic support for referencing global variables from within functions via the cir.get_global operation. --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 31 ++++++ clang/include/clang/CIR/MissingFeatures.h | 5 + clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 41 ++++++- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 100 ++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenModule.h | 25 +++++ clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 35 ++++++ .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 21 ++++ .../CIR/Lowering/DirectToLLVM/LowerToLLVM.h | 10 ++ clang/test/CIR/CodeGen/basic.c | 25 +++++ 9 files changed, 291 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index ce6f6c70189d9..000eb10e5c4ca 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -1279,6 +1279,37 @@ def GlobalOp : CIR_Op<"global"> { let hasVerifier = 1; } +//===----------------------------------------------------------------------===// +// GetGlobalOp +//===----------------------------------------------------------------------===// + +def GetGlobalOp : CIR_Op<"get_global", + [Pure, DeclareOpInterfaceMethods<SymbolUserOpInterface>]> { + let summary = "Get the address of a global variable"; + let description = [{ + The `cir.get_global` operation retrieves the address pointing to a + named global variable. If the global variable is marked constant, writing + to the resulting address (such as through a `cir.store` operation) is + undefined. Resulting type must always be a `!cir.ptr<...>` type with the + same address space as the global variable. + + Example: + ```mlir + %x = cir.get_global @gv : !cir.ptr<i32> + ``` + }]; + + let arguments = (ins FlatSymbolRefAttr:$name); + let results = (outs Res<CIR_PointerType, "", []>:$addr); + + let assemblyFormat = [{ + $name `:` qualified(type($addr)) attr-dict + }]; + + // `GetGlobalOp` is fully verified by its traits. + let hasVerifier = 0; +} + //===----------------------------------------------------------------------===// // FuncOp //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 3188429ea3b1b..70ecb50a93e1d 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -35,6 +35,7 @@ struct MissingFeatures { static bool opGlobalThreadLocal() { return false; } static bool opGlobalConstant() { return false; } static bool opGlobalAlignment() { return false; } + static bool opGlobalWeakRef() { return false; } static bool supportIFuncAttr() { return false; } static bool supportVisibility() { return false; } @@ -113,6 +114,10 @@ struct MissingFeatures { static bool incrementProfileCounter() { return false; } static bool insertBuiltinUnpredictable() { return false; } static bool objCGC() { return false; } + static bool setObjCGCLValueClass() { return false; } + static bool mangledNames() { return false; } + static bool setDLLStorageClass() { return false; } + static bool openMP() { return false; } // Missing types static bool dataMemberType() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 4b6652ad0b9e6..de76f5e4dd4d7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -183,6 +183,43 @@ void CIRGenFunction::emitStoreThroughLValue(RValue src, LValue dst, emitStoreOfScalar(src.getScalarVal(), dst, isInit); } +static LValue emitGlobalVarDeclLValue(CIRGenFunction &cgf, const Expr *e, + const VarDecl *vd) { + QualType T = e->getType(); + + // If it's thread_local, emit a call to its wrapper function instead. + assert(!cir::MissingFeatures::opGlobalThreadLocal()); + if (vd->getTLSKind() == VarDecl::TLS_Dynamic) + cgf.cgm.errorNYI(e->getSourceRange(), + "emitGlobalVarDeclLValue: thread_local variable"); + + // Check if the variable is marked as declare target with link clause in + // device codegen. + if (cgf.getLangOpts().OpenMP) + cgf.cgm.errorNYI(e->getSourceRange(), "emitGlobalVarDeclLValue: OpenMP"); + + // Traditional LLVM codegen handles thread local separately, CIR handles + // as part of getAddrOfGlobalVar. + mlir::Value v = cgf.cgm.getAddrOfGlobalVar(vd); + + assert(!cir::MissingFeatures::addressSpace()); + mlir::Type realVarTy = cgf.convertTypeForMem(vd->getType()); + cir::PointerType realPtrTy = cgf.getBuilder().getPointerTo(realVarTy); + if (realPtrTy != v.getType()) + v = cgf.getBuilder().createBitcast(v.getLoc(), v, realPtrTy); + + CharUnits alignment = cgf.getContext().getDeclAlign(vd); + Address addr(v, realVarTy, alignment); + LValue lv; + if (vd->getType()->isReferenceType()) + cgf.cgm.errorNYI(e->getSourceRange(), + "emitGlobalVarDeclLValue: reference type"); + else + lv = cgf.makeAddrLValue(addr, T, AlignmentSource::Decl); + assert(!cir::MissingFeatures::setObjCGCLValueClass()); + return lv; +} + void CIRGenFunction::emitStoreOfScalar(mlir::Value value, Address addr, bool isVolatile, QualType ty, bool isInit, bool isNontemporal) { @@ -288,7 +325,7 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) { // Check if this is a global variable if (vd->hasLinkage() || vd->isStaticDataMember()) - cgm.errorNYI(vd->getSourceRange(), "emitDeclRefLValue: global variable"); + return emitGlobalVarDeclLValue(*this, e, vd); Address addr = Address::invalid(); @@ -299,7 +336,7 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) { } else { // Otherwise, it might be static local we haven't emitted yet for some // reason; most likely, because it's in an outer function. - cgm.errorNYI(vd->getSourceRange(), "emitDeclRefLValue: static local"); + cgm.errorNYI(e->getSourceRange(), "emitDeclRefLValue: static local"); } return makeAddrLValue(addr, ty, AlignmentSource::Type); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index d2259a9c41d22..3eecbfb7973fd 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -200,6 +200,105 @@ void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd, } } +mlir::Operation *CIRGenModule::getGlobalValue(StringRef name) { + mlir::Operation *global = mlir::SymbolTable::lookupSymbolIn(theModule, name); + if (!global) + return nullptr; + return global; +} + +/// If the specified mangled name is not in the module, +/// create and return an mlir GlobalOp with the specified type (TODO(cir): +/// address space). +/// +/// TODO(cir): +/// 1. If there is something in the module with the specified name, return +/// it potentially bitcasted to the right type. +/// +/// 2. If \p d is non-null, it specifies a decl that correspond to this. This +/// is used to set the attributes on the global when it is first created. +/// +/// 3. If \p isForDefinition is true, it is guaranteed that an actual global +/// with type \p ty will be returned, not conversion of a variable with the same +/// mangled name but some other type. +cir::GlobalOp +CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty, + LangAS langAS, const VarDecl *d, + ForDefinition_t isForDefinition) { + // Lookup the entry, lazily creating it if necessary. + cir::GlobalOp entry; + if (mlir::Operation *v = getGlobalValue(mangledName)) { + if (!isa<cir::GlobalOp>(v)) + errorNYI(d->getSourceRange(), "global with non-GlobalOp type"); + entry = cast<cir::GlobalOp>(v); + } + + if (entry) { + assert(!cir::MissingFeatures::addressSpace()); + assert(!cir::MissingFeatures::opGlobalWeakRef()); + + assert(!cir::MissingFeatures::setDLLStorageClass()); + assert(!cir::MissingFeatures::openMP()); + + if (entry.getSymType() == ty) + return entry; + + // If there are two attempts to define the same mangled name, issue an + // error. + // + // TODO(cir): look at mlir::GlobalValue::isDeclaration for all aspects of + // recognizing the global as a declaration, for now only check if + // initializer is present. + if (isForDefinition && !entry.isDeclaration()) { + errorNYI(d->getSourceRange(), "global with conflicting type"); + } + + // Address space check removed because it is unnecessary because CIR records + // address space info in types. + + // (If global is requested for a definition, we always need to create a new + // global, not just return a bitcast.) + if (!isForDefinition) + return entry; + } + + errorNYI(d->getSourceRange(), "reference of undeclared global"); +} + +cir::GlobalOp +CIRGenModule::getOrCreateCIRGlobal(const VarDecl *d, mlir::Type ty, + ForDefinition_t isForDefinition) { + assert(d->hasGlobalStorage() && "Not a global variable"); + QualType astTy = d->getType(); + if (!ty) + ty = getTypes().convertTypeForMem(astTy); + + assert(!cir::MissingFeatures::mangledNames()); + return getOrCreateCIRGlobal(d->getIdentifier()->getName(), ty, + astTy.getAddressSpace(), d, isForDefinition); +} + +/// Return the mlir::Value for the address of the given global variable. If +/// \p ty is non-null and if the global doesn't exist, then it will be created +/// with the specified type instead of whatever the normal requested type would +/// be. If \p isForDefinition is true, it is guaranteed that an actual global +/// with type \p ty will be returned, not conversion of a variable with the same +/// mangled name but some other type. +mlir::Value CIRGenModule::getAddrOfGlobalVar(const VarDecl *d, mlir::Type ty, + ForDefinition_t isForDefinition) { + assert(d->hasGlobalStorage() && "Not a global variable"); + QualType astTy = d->getType(); + if (!ty) + ty = getTypes().convertTypeForMem(astTy); + + assert(!cir::MissingFeatures::opGlobalThreadLocal()); + + cir::GlobalOp g = getOrCreateCIRGlobal(d, ty, isForDefinition); + mlir::Type ptrTy = builder.getPointerTo(g.getSymType()); + return builder.create<cir::GetGlobalOp>(getLoc(d->getSourceRange()), ptrTy, + g.getSymName()); +} + void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd, bool isTentative) { const QualType astTy = vd->getType(); @@ -505,6 +604,7 @@ cir::FuncOp CIRGenModule::getAddrOfFunction(clang::GlobalDecl gd, funcType = convertType(fd->getType()); } + assert(!cir::MissingFeatures::mangledNames()); cir::FuncOp func = getOrCreateCIRFunction( cast<NamedDecl>(gd.getDecl())->getIdentifier()->getName(), funcType, gd, forVTable, dontDefer, /*isThunk=*/false, isForDefinition); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index fe39244fc7834..b57228a4263fc 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -85,6 +85,31 @@ class CIRGenModule : public CIRGenTypeCache { const clang::LangOptions &getLangOpts() const { return langOpts; } mlir::MLIRContext &getMLIRContext() { return *builder.getContext(); } + /// ------- + /// Handling globals + /// ------- + + mlir::Operation *getGlobalValue(llvm::StringRef ref); + + /// 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, + LangAS langAS, const VarDecl *d, + ForDefinition_t isForDefinition); + + cir::GlobalOp getOrCreateCIRGlobal(const VarDecl *d, mlir::Type ty, + ForDefinition_t isForDefinition); + + /// Return the mlir::Value for the address of the given global variable. + /// If Ty is non-null and if the global doesn't exist, then it will be created + /// with the specified type instead of whatever the normal requested type + /// would be. If IsForDefinition is true, it is guaranteed that an actual + /// global with type Ty will be returned, not conversion of a variable with + /// the same mangled name but some other type. + mlir::Value + getAddrOfGlobalVar(const VarDecl *d, mlir::Type ty = {}, + ForDefinition_t isForDefinition = NotForDefinition); + /// Helpers to convert the presumed location of Clang's SourceLocation to an /// MLIR Location. mlir::Location getLoc(clang::SourceLocation cLoc); diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 6bac6123a339d..b4822ea1d8236 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -764,6 +764,41 @@ parseGlobalOpTypeAndInitialValue(OpAsmParser &parser, TypeAttr &typeAttr, return success(); } +//===----------------------------------------------------------------------===// +// GetGlobalOp +//===----------------------------------------------------------------------===// + +LogicalResult +cir::GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + // Verify that the result type underlying pointer type matches the type of + // the referenced cir.global or cir.func op. + mlir::Operation *op = + symbolTable.lookupNearestSymbolFrom(*this, getNameAttr()); + if (op == nullptr || !(isa<GlobalOp>(op) || isa<FuncOp>(op))) + return emitOpError("'") + << getName() + << "' does not reference a valid cir.global or cir.func"; + + mlir::Type symTy; + if (auto g = dyn_cast<GlobalOp>(op)) { + symTy = g.getSymType(); + assert(!cir::MissingFeatures::addressSpace()); + assert(!cir::MissingFeatures::opGlobalThreadLocal()); + } else if (auto f = dyn_cast<FuncOp>(op)) { + symTy = f.getFunctionType(); + } else { + llvm_unreachable("Unexpected operation for GetGlobalOp"); + } + + auto resultType = dyn_cast<PointerType>(getAddr().getType()); + if (!resultType || symTy != resultType.getPointee()) + return emitOpError("result type pointee type '") + << resultType.getPointee() << "' does not match type " << symTy + << " of the global @" << getName(); + + return success(); +} + //===----------------------------------------------------------------------===// // FuncOp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 7ca36409c9cac..7159f89c93a53 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -828,6 +828,26 @@ mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewrite( return mlir::LogicalResult::success(); } +mlir::LogicalResult CIRToLLVMGetGlobalOpLowering::matchAndRewrite( + cir::GetGlobalOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + // FIXME(cir): Premature DCE to avoid lowering stuff we're not using. + // CIRGen should mitigate this and not emit the get_global. + if (op->getUses().empty()) { + rewriter.eraseOp(op); + return mlir::success(); + } + + mlir::Type type = getTypeConverter()->convertType(op.getType()); + mlir::Operation *newop = + rewriter.create<mlir::LLVM::AddressOfOp>(op.getLoc(), type, op.getName()); + + assert(!cir::MissingFeatures::opGlobalThreadLocal()); + + rewriter.replaceOp(op, newop); + return mlir::success(); +} + /// Replace CIR global with a region initialized LLVM global and update /// insertion point to the end of the initializer block. void CIRToLLVMGlobalOpLowering::setupRegionInitializedLLVMGlobalOp( @@ -1418,6 +1438,7 @@ void ConvertCIRToLLVMPass::runOnOperation() { CIRToLLVMCmpOpLowering, CIRToLLVMConstantOpLowering, CIRToLLVMFuncOpLowering, + CIRToLLVMGetGlobalOpLowering, CIRToLLVMTrapOpLowering, CIRToLLVMUnaryOpLowering // clang-format on diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h index d53c4b31682bb..1de6c9c56b485 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h @@ -140,6 +140,16 @@ class CIRToLLVMFuncOpLowering : public mlir::OpConversionPattern<cir::FuncOp> { mlir::ConversionPatternRewriter &) const override; }; +class CIRToLLVMGetGlobalOpLowering + : public mlir::OpConversionPattern<cir::GetGlobalOp> { +public: + using mlir::OpConversionPattern<cir::GetGlobalOp>::OpConversionPattern; + + mlir::LogicalResult + matchAndRewrite(cir::GetGlobalOp op, OpAdaptor, + mlir::ConversionPatternRewriter &) const override; +}; + class CIRToLLVMGlobalOpLowering : public mlir::OpConversionPattern<cir::GlobalOp> { const mlir::DataLayout &dataLayout; diff --git a/clang/test/CIR/CodeGen/basic.c b/clang/test/CIR/CodeGen/basic.c index 7184e395ce386..cf687e44044c4 100644 --- a/clang/test/CIR/CodeGen/basic.c +++ b/clang/test/CIR/CodeGen/basic.c @@ -144,3 +144,28 @@ void f5(void) { // OGCG: br label %[[LOOP:.*]] // OGCG: [[LOOP]]: // OGCG: br label %[[LOOP]] + +int gv; +int f6(void) { + return gv; +} + +// CIR: cir.func @f6() -> !s32i +// CIR-NEXT: %[[RV:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["__retval"] {alignment = 4 : i64} +// CIR-NEXT: %[[GV_PTR:.*]] = cir.get_global @gv : !cir.ptr<!s32i> +// CIR-NEXT: %[[GV:.*]] = cir.load %[[GV_PTR]] : !cir.ptr<!s32i>, !s32i +// CIR-NEXT: cir.store %[[GV]], %[[RV]] : !s32i, !cir.ptr<!s32i> +// CIR-NEXT: %[[R:.*]] = cir.load %[[RV]] : !cir.ptr<!s32i>, !s32i +// CIR-NEXT: cir.return %[[R]] : !s32i + +// LLVM: define i32 @f6() +// LLVM-NEXT: %[[RV_PTR:.*]] = alloca i32, i64 1, align 4 +// LLVM-NEXT: %[[GV:.*]] = load i32, ptr @gv, align 4 +// LLVM-NEXT: store i32 %[[GV]], ptr %[[RV_PTR]], align 4 +// LLVM-NEXT: %[[RV:.*]] = load i32, ptr %[[RV_PTR]], align 4 +// LLVM-NEXT: ret i32 %[[RV]] + +// OGCG: define{{.*}} i32 @f6() +// OGCG-NEXT: entry: +// OGCG-NEXT: %[[GV:.*]] = load i32, ptr @gv, align 4 +// OGCG-NEXT: ret i32 %[[GV]] >From 168a8fd7d279bc6b8decb863c110745d59e40453 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <akay...@nvidia.com> Date: Thu, 10 Apr 2025 12:26:13 -0700 Subject: [PATCH 2/2] Address review feedback --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 2 +- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 000eb10e5c4ca..0cc59fa328adc 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -1290,7 +1290,7 @@ def GetGlobalOp : CIR_Op<"get_global", The `cir.get_global` operation retrieves the address pointing to a named global variable. If the global variable is marked constant, writing to the resulting address (such as through a `cir.store` operation) is - undefined. Resulting type must always be a `!cir.ptr<...>` type with the + undefined. The resulting type must always be a `!cir.ptr<...>` type with the same address space as the global variable. Example: diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 3eecbfb7973fd..f6c915fa47ccd 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -201,10 +201,7 @@ void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd, } mlir::Operation *CIRGenModule::getGlobalValue(StringRef name) { - mlir::Operation *global = mlir::SymbolTable::lookupSymbolIn(theModule, name); - if (!global) - return nullptr; - return global; + return mlir::SymbolTable::lookupSymbolIn(theModule, name); } /// If the specified mangled name is not in the module, _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits