Skip to content

[CIR] Add support for function linkage and visibility #145600

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 3 commits into from
Jun 25, 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
30 changes: 28 additions & 2 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1737,25 +1737,51 @@ def GetMemberOp : CIR_Op<"get_member"> {

def FuncOp : CIR_Op<"func", [
AutomaticAllocationScope, CallableOpInterface, FunctionOpInterface,
DeclareOpInterfaceMethods<CIRGlobalValueInterface>,
IsolatedFromAbove
]> {
let summary = "Declare or define a function";
let description = [{
The `cir.func` operation defines a function, similar to the `mlir::FuncOp`
built-in.

The function linkage information is specified by `linkage`, as defined by
`GlobalLinkageKind` attribute.

Example:

```mlir
// External function definitions.
cir.func @abort()

// A function with internal linkage.
cir.func internal @count(%x: i64) -> (i64)
return %x : i64

// Linkage information
cir.func linkonce_odr @some_method(...)
```
}];

let arguments = (ins SymbolNameAttr:$sym_name,
CIR_VisibilityAttr:$global_visibility,
TypeAttrOf<CIR_FuncType>:$function_type,
UnitAttr:$dso_local,
Copy link
Member

Choose a reason for hiding this comment

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

Should we elaborate dso_local from a simple boolean flag into something like a RuntimePreemption enum that could be either NonPreemptable or Preemptable?

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's a 1-to-1 correspondance between this and the dso_local representation in LLVM IR, and we are able to model the computation of this flag after the same computation in classic codegen. I don't think there's benefit in trying to generalize this as an intermediate state when we'll need to recreate it when lowering to LLVM IR anyway.

DefaultValuedAttr<CIR_GlobalLinkageKind,
"cir::GlobalLinkageKind::ExternalLinkage">:$linkage,
OptionalAttr<StrAttr>:$sym_visibility,
Copy link
Member

Choose a reason for hiding this comment

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

What is this attribute for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe that it's used by some general MLIR interfaces.

UnitAttr:$comdat,
OptionalAttr<DictArrayAttr>:$arg_attrs,
OptionalAttr<DictArrayAttr>:$res_attrs);

let regions = (region AnyRegion:$body);

let skipDefaultBuilders = 1;

let builders = [OpBuilder<(ins "llvm::StringRef":$sym_name,
"FuncType":$type)>];
let builders = [OpBuilder<(ins
"llvm::StringRef":$sym_name, "FuncType":$type,
CArg<"cir::GlobalLinkageKind", "cir::GlobalLinkageKind::ExternalLinkage">:$linkage)
>];

let extraClassDeclaration = [{
/// Returns the region on the current operation that is callable. This may
Expand Down
10 changes: 6 additions & 4 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,18 @@ struct MissingFeatures {

// FuncOp handling
static bool opFuncOpenCLKernelMetadata() { return false; }
static bool opFuncAstDeclAttr() { return false; }
static bool opFuncCallingConv() { return false; }
static bool opFuncExtraAttrs() { return false; }
static bool opFuncDsoLocal() { return false; }
static bool opFuncLinkage() { return false; }
static bool opFuncVisibility() { return false; }
static bool opFuncNoProto() { return false; }
static bool opFuncCPUAndFeaturesAttributes() { return false; }
static bool opFuncSection() { return false; }
static bool opFuncSetComdat() { return false; }
static bool opFuncMultipleReturnVals() { return false; }
static bool opFuncAttributesForDefinition() { return false; }
static bool opFuncMaybeHandleStaticInExternC() { return false; }
static bool opFuncGlobalAliases() { return false; }
static bool setLLVMFunctionFEnvAttributes() { return false; }
static bool setFunctionAttributes() { return false; }

// CallOp handling
static bool opCallPseudoDtor() { return false; }
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/CIR/CodeGen/CIRGenCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl gd) {
cir::FuncType funcType = getTypes().getFunctionType(fnInfo);
cir::FuncOp fn = getAddrOfCXXStructor(gd, &fnInfo, /*FnType=*/nullptr,
/*DontDefer=*/true, ForDefinition);
assert(!cir::MissingFeatures::opFuncLinkage());
setFunctionLinkage(gd, fn);
CIRGenFunction cgf{*this, builder};
curCGF = &cgf;
{
Expand Down
82 changes: 77 additions & 5 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -406,14 +406,34 @@ void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
/*DontDefer=*/true, ForDefinition);
}

// Already emitted.
if (!funcOp.isDeclaration())
Copy link
Collaborator

Choose a reason for hiding this comment

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

So this was a little jarring to me... any chance we could add a isDefinition to cir::FuncOp (or isDefined?) and use that instead? Took me 2-3 reads to figure out What is going on here :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function is part of CIRGlobalValueInterface, which is mirroring llvm::GlobalValue. Classic codegen calls llvm::GlobalValue::isDeclaration() here. In other places, we will eventually have calls to isDeclarationForLinker, which is also a method in llvm::GlobalValue.

I'm on the fence as to whether this is sufficient justification for keeping such a problematic name. The other thing to consider here is how FuncOp::isDeclaration is implemented. Currently is just returns FuncOp::isExternal (which is also a bit obscure in meaning. The incubator implementation also has some handling for the case where the FuncOp is an alias, which is part of the llvm::GlobalValue::isDeclaration implementation.

bool GlobalValue::isDeclaration() const {
  // Globals are definitions if they have an initializer.
  if (const GlobalVariable *GV = dyn_cast<GlobalVariable>(this))
    return GV->getNumOperands() == 0;
 
  // Functions are definitions if they have a body.
  if (const Function *F = dyn_cast<Function>(this))
    return F->empty() && !F->isMaterializable();
 
  // Aliases and ifuncs are always definitions.
  assert(isa<GlobalAlias>(this) || isa<GlobalIFunc>(this));
  return false;
}

We haven't implemented ifuncs in the incubator, but when we do they'll need to be handled here. I'm not even sure how we get a function that is materializable, or if that's possible from the clang front end.

It turns out that isExternal is a function provided by some MLIR code we're inheriting and it ends up being resolved to getFunctionBody().empty(). I think the FuncOp::isDeclaration implementation would be greatly improved by just making this check directly.

return;

setFunctionLinkage(gd, funcOp);
setGVProperties(funcOp, funcDecl);
assert(!cir::MissingFeatures::opFuncMaybeHandleStaticInExternC());
maybeSetTrivialComdat(*funcDecl, funcOp);
assert(!cir::MissingFeatures::setLLVMFunctionFEnvAttributes());

CIRGenFunction cgf(*this, builder);
curCGF = &cgf;
{
mlir::OpBuilder::InsertionGuard guard(builder);
cgf.generateCode(gd, funcOp, funcType);
}
curCGF = nullptr;

setNonAliasAttributes(gd, funcOp);
assert(!cir::MissingFeatures::opFuncAttributesForDefinition());

if (const ConstructorAttr *ca = funcDecl->getAttr<ConstructorAttr>())
errorNYI(funcDecl->getSourceRange(), "constructor attribute");
if (const DestructorAttr *da = funcDecl->getAttr<DestructorAttr>())
errorNYI(funcDecl->getSourceRange(), "destructor attribute");

if (funcDecl->getAttr<AnnotateAttr>())
errorNYI(funcDecl->getSourceRange(), "deferredAnnotations");
}

mlir::Operation *CIRGenModule::getGlobalValue(StringRef name) {
Expand Down Expand Up @@ -855,10 +875,12 @@ static bool shouldBeInCOMDAT(CIRGenModule &cgm, const Decl &d) {
void CIRGenModule::maybeSetTrivialComdat(const Decl &d, mlir::Operation *op) {
if (!shouldBeInCOMDAT(*this, d))
return;
if (auto globalOp = dyn_cast_or_null<cir::GlobalOp>(op))
if (auto globalOp = dyn_cast_or_null<cir::GlobalOp>(op)) {
globalOp.setComdat(true);

assert(!cir::MissingFeatures::opFuncSetComdat());
} else {
auto funcOp = cast<cir::FuncOp>(op);
funcOp.setComdat(true);
}
}

void CIRGenModule::updateCompletedType(const TagDecl *td) {
Expand Down Expand Up @@ -1028,6 +1050,17 @@ CIRGenModule::getCIRLinkageVarDefinition(const VarDecl *vd, bool isConstant) {
return getCIRLinkageForDeclarator(vd, linkage, isConstant);
}

cir::GlobalLinkageKind CIRGenModule::getFunctionLinkage(GlobalDecl gd) {
const auto *fd = cast<FunctionDecl>(gd.getDecl());

GVALinkage linkage = astContext.GetGVALinkageForFunction(fd);

if (const auto *dtor = dyn_cast<CXXDestructorDecl>(fd))
errorNYI(fd->getSourceRange(), "getFunctionLinkage: CXXDestructorDecl");

return getCIRLinkageForDeclarator(fd, linkage, /*IsConstantVariable=*/false);
}

static cir::GlobalOp
generateStringLiteral(mlir::Location loc, mlir::TypedAttr c,
cir::GlobalLinkageKind lt, CIRGenModule &cgm,
Expand Down Expand Up @@ -1534,6 +1567,27 @@ void CIRGenModule::setGVPropertiesAux(mlir::Operation *op,
assert(!cir::MissingFeatures::opGlobalPartition());
}

void CIRGenModule::setFunctionAttributes(GlobalDecl globalDecl,
cir::FuncOp func,
bool isIncompleteFunction,
bool isThunk) {
// NOTE(cir): Original CodeGen checks if this is an intrinsic. In CIR we
// represent them in dedicated ops. The correct attributes are ensured during
// translation to LLVM. Thus, we don't need to check for them here.

assert(!cir::MissingFeatures::setFunctionAttributes());
assert(!cir::MissingFeatures::setTargetAttributes());

// TODO(cir): This needs a lot of work to better match CodeGen. That
// ultimately ends up in setGlobalVisibility, which already has the linkage of
// the LLVM GV (corresponding to our FuncOp) computed, so it doesn't have to
// recompute it here. This is a minimal fix for now.
if (!isLocalLinkage(getFunctionLinkage(globalDecl))) {
const Decl *decl = globalDecl.getDecl();
func.setGlobalVisibilityAttr(getGlobalVisibilityAttrFromDecl(decl));
}
}

cir::FuncOp CIRGenModule::getOrCreateCIRFunction(
StringRef mangledName, mlir::Type funcType, GlobalDecl gd, bool forVTable,
bool dontDefer, bool isThunk, ForDefinition_t isForDefinition,
Expand Down Expand Up @@ -1576,8 +1630,9 @@ cir::FuncOp CIRGenModule::getOrCreateCIRFunction(
// If there are two attempts to define the same mangled name, issue an
// error.
auto fn = cast<cir::FuncOp>(entry);
assert((!isForDefinition || !fn || !fn.isDeclaration()) &&
"Duplicate function definition");
if (isForDefinition && fn && !fn.isDeclaration()) {
errorNYI(d->getSourceRange(), "Duplicate function definition");
}
if (fn && fn.getFunctionType() == funcType) {
return fn;
}
Expand All @@ -1598,6 +1653,9 @@ cir::FuncOp CIRGenModule::getOrCreateCIRFunction(
invalidLoc ? theModule->getLoc() : getLoc(funcDecl->getSourceRange()),
mangledName, mlir::cast<cir::FuncType>(funcType), funcDecl);

if (d)
setFunctionAttributes(gd, funcOp, /*isIncompleteFunction=*/false, isThunk);

// 'dontDefer' actually means don't move this to the deferredDeclsToEmit list.
if (dontDefer) {
// TODO(cir): This assertion will need an additional condition when we
Expand Down Expand Up @@ -1668,6 +1726,20 @@ CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name,

func = builder.create<cir::FuncOp>(loc, name, funcType);

assert(!cir::MissingFeatures::opFuncAstDeclAttr());
assert(!cir::MissingFeatures::opFuncNoProto());

assert(func.isDeclaration() && "expected empty body");

// A declaration gets private visibility by default, but external linkage
// as the default linkage.
func.setLinkageAttr(cir::GlobalLinkageKindAttr::get(
&getMLIRContext(), cir::GlobalLinkageKind::ExternalLinkage));
mlir::SymbolTable::setSymbolVisibility(
func, mlir::SymbolTable::Visibility::Private);

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

if (!cgf)
theModule.push_back(func);
}
Expand Down
12 changes: 11 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,10 @@ class CIRGenModule : public CIRGenTypeCache {
void setGVProperties(mlir::Operation *op, const NamedDecl *d) const;
void setGVPropertiesAux(mlir::Operation *op, const NamedDecl *d) const;

/// Set function attributes for a function declaration.
void setFunctionAttributes(GlobalDecl gd, cir::FuncOp f,
bool isIncompleteFunction, bool isThunk);

void emitGlobalDefinition(clang::GlobalDecl gd,
mlir::Operation *op = nullptr);
void emitGlobalFunctionDefinition(clang::GlobalDecl gd, mlir::Operation *op);
Expand Down Expand Up @@ -340,10 +344,16 @@ class CIRGenModule : public CIRGenTypeCache {
clang::VisibilityAttr::VisibilityType visibility);
cir::VisibilityAttr getGlobalVisibilityAttrFromDecl(const Decl *decl);
static mlir::SymbolTable::Visibility getMLIRVisibility(cir::GlobalOp op);

cir::GlobalLinkageKind getFunctionLinkage(GlobalDecl gd);
cir::GlobalLinkageKind getCIRLinkageForDeclarator(const DeclaratorDecl *dd,
GVALinkage linkage,
bool isConstantVariable);
void setFunctionLinkage(GlobalDecl gd, cir::FuncOp f) {
cir::GlobalLinkageKind l = getFunctionLinkage(gd);
f.setLinkageAttr(cir::GlobalLinkageKindAttr::get(&getMLIRContext(), l));
mlir::SymbolTable::setSymbolVisibility(f,
getMLIRVisibilityFromCIRLinkage(l));
}

cir::GlobalLinkageKind getCIRLinkageVarDefinition(const VarDecl *vd,
bool isConstant);
Expand Down
Loading