Skip to content

[CIR] Upstream support for cir.get_global #135095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 10, 2025
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
31 changes: 31 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -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. The 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
//===----------------------------------------------------------------------===//
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -136,6 +137,10 @@ struct MissingFeatures {
static bool objCGC() { return false; }
static bool weakRefReference() { return false; }
static bool hip() { 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; }
Expand Down
41 changes: 39 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();

Expand All @@ -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);
Expand Down
97 changes: 97 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,102 @@ void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
curCGF = nullptr;
}

mlir::Operation *CIRGenModule::getGlobalValue(StringRef name) {
return mlir::SymbolTable::lookupSymbolIn(theModule, name);
}

/// 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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we invert this and do an eraly-return to save everything below the tab?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There will be a bunch of things that need to happen after all of this when the implementation is complete. When we've done everything here, if isForDefinition is true, we fall through to execute the same code that handles the case where entry is null.

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();
Expand Down Expand Up @@ -507,6 +603,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);
Expand Down
25 changes: 25 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,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);
Expand Down
35 changes: 35 additions & 0 deletions clang/lib/CIR/Dialect/IR/CIRDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,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
//===----------------------------------------------------------------------===//
Expand Down
21 changes: 21 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -1418,6 +1438,7 @@ void ConvertCIRToLLVMPass::runOnOperation() {
CIRToLLVMCmpOpLowering,
CIRToLLVMConstantOpLowering,
CIRToLLVMFuncOpLowering,
CIRToLLVMGetGlobalOpLowering,
CIRToLLVMTrapOpLowering,
CIRToLLVMUnaryOpLowering
// clang-format on
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
25 changes: 25 additions & 0 deletions clang/test/CIR/CodeGen/basic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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]]