llvm-project/clang/lib/CIR/CodeGen/CIRGenModule.cpp
Morris Hafner 0045bfca9c
[CIR} Add support for static member variable instantiation (#153200)
This patch handles both implicit and explicit template instantiations of
template class static member variables.
2025-08-13 18:03:05 +02:00

2161 lines
81 KiB
C++

//===- CIRGenModule.cpp - Per-Module state for CIR generation -------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This is the internal per-translation-unit state used for CIR translation.
//
//===----------------------------------------------------------------------===//
#include "CIRGenModule.h"
#include "CIRGenCXXABI.h"
#include "CIRGenConstantEmitter.h"
#include "CIRGenFunction.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclOpenACC.h"
#include "clang/AST/GlobalDecl.h"
#include "clang/AST/RecordLayout.h"
#include "clang/Basic/SourceManager.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Interfaces/CIROpInterfaces.h"
#include "clang/CIR/MissingFeatures.h"
#include "CIRGenFunctionInfo.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Location.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/Verifier.h"
using namespace clang;
using namespace clang::CIRGen;
static CIRGenCXXABI *createCXXABI(CIRGenModule &cgm) {
switch (cgm.getASTContext().getCXXABIKind()) {
case TargetCXXABI::GenericItanium:
case TargetCXXABI::GenericAArch64:
case TargetCXXABI::AppleARM64:
return CreateCIRGenItaniumCXXABI(cgm);
case TargetCXXABI::Fuchsia:
case TargetCXXABI::GenericARM:
case TargetCXXABI::iOS:
case TargetCXXABI::WatchOS:
case TargetCXXABI::GenericMIPS:
case TargetCXXABI::WebAssembly:
case TargetCXXABI::XL:
case TargetCXXABI::Microsoft:
cgm.errorNYI("C++ ABI kind not yet implemented");
return nullptr;
}
llvm_unreachable("invalid C++ ABI kind");
}
CIRGenModule::CIRGenModule(mlir::MLIRContext &mlirContext,
clang::ASTContext &astContext,
const clang::CodeGenOptions &cgo,
DiagnosticsEngine &diags)
: builder(mlirContext, *this), astContext(astContext),
langOpts(astContext.getLangOpts()), codeGenOpts(cgo),
theModule{mlir::ModuleOp::create(mlir::UnknownLoc::get(&mlirContext))},
diags(diags), target(astContext.getTargetInfo()),
abi(createCXXABI(*this)), genTypes(*this), vtables(*this) {
// Initialize cached types
VoidTy = cir::VoidType::get(&getMLIRContext());
VoidPtrTy = cir::PointerType::get(VoidTy);
SInt8Ty = cir::IntType::get(&getMLIRContext(), 8, /*isSigned=*/true);
SInt16Ty = cir::IntType::get(&getMLIRContext(), 16, /*isSigned=*/true);
SInt32Ty = cir::IntType::get(&getMLIRContext(), 32, /*isSigned=*/true);
SInt64Ty = cir::IntType::get(&getMLIRContext(), 64, /*isSigned=*/true);
SInt128Ty = cir::IntType::get(&getMLIRContext(), 128, /*isSigned=*/true);
UInt8Ty = cir::IntType::get(&getMLIRContext(), 8, /*isSigned=*/false);
UInt8PtrTy = cir::PointerType::get(UInt8Ty);
UInt16Ty = cir::IntType::get(&getMLIRContext(), 16, /*isSigned=*/false);
UInt32Ty = cir::IntType::get(&getMLIRContext(), 32, /*isSigned=*/false);
UInt64Ty = cir::IntType::get(&getMLIRContext(), 64, /*isSigned=*/false);
UInt128Ty = cir::IntType::get(&getMLIRContext(), 128, /*isSigned=*/false);
FP16Ty = cir::FP16Type::get(&getMLIRContext());
BFloat16Ty = cir::BF16Type::get(&getMLIRContext());
FloatTy = cir::SingleType::get(&getMLIRContext());
DoubleTy = cir::DoubleType::get(&getMLIRContext());
FP80Ty = cir::FP80Type::get(&getMLIRContext());
FP128Ty = cir::FP128Type::get(&getMLIRContext());
PointerAlignInBytes =
astContext
.toCharUnitsFromBits(
astContext.getTargetInfo().getPointerAlign(LangAS::Default))
.getQuantity();
// TODO(CIR): Should be updated once TypeSizeInfoAttr is upstreamed
const unsigned sizeTypeSize =
astContext.getTypeSize(astContext.getSignedSizeType());
SizeAlignInBytes = astContext.toCharUnitsFromBits(sizeTypeSize).getQuantity();
// In CIRGenTypeCache, UIntPtrTy and SizeType are fields of the same union
UIntPtrTy =
cir::IntType::get(&getMLIRContext(), sizeTypeSize, /*isSigned=*/false);
PtrDiffTy =
cir::IntType::get(&getMLIRContext(), sizeTypeSize, /*isSigned=*/true);
theModule->setAttr(cir::CIRDialect::getTripleAttrName(),
builder.getStringAttr(getTriple().str()));
if (cgo.OptimizationLevel > 0 || cgo.OptimizeSize > 0)
theModule->setAttr(cir::CIRDialect::getOptInfoAttrName(),
cir::OptInfoAttr::get(&mlirContext,
cgo.OptimizationLevel,
cgo.OptimizeSize));
}
CIRGenModule::~CIRGenModule() = default;
/// FIXME: this could likely be a common helper and not necessarily related
/// with codegen.
/// Return the best known alignment for an unknown pointer to a
/// particular class.
CharUnits CIRGenModule::getClassPointerAlignment(const CXXRecordDecl *rd) {
if (!rd->hasDefinition())
return CharUnits::One(); // Hopefully won't be used anywhere.
auto &layout = astContext.getASTRecordLayout(rd);
// If the class is final, then we know that the pointer points to an
// object of that type and can use the full alignment.
if (rd->isEffectivelyFinal())
return layout.getAlignment();
// Otherwise, we have to assume it could be a subclass.
return layout.getNonVirtualAlignment();
}
CharUnits CIRGenModule::getNaturalTypeAlignment(QualType t,
LValueBaseInfo *baseInfo) {
assert(!cir::MissingFeatures::opTBAA());
// FIXME: This duplicates logic in ASTContext::getTypeAlignIfKnown, but
// that doesn't return the information we need to compute baseInfo.
// Honor alignment typedef attributes even on incomplete types.
// We also honor them straight for C++ class types, even as pointees;
// there's an expressivity gap here.
if (const auto *tt = t->getAs<TypedefType>()) {
if (unsigned align = tt->getDecl()->getMaxAlignment()) {
if (baseInfo)
*baseInfo = LValueBaseInfo(AlignmentSource::AttributedType);
return astContext.toCharUnitsFromBits(align);
}
}
// Analyze the base element type, so we don't get confused by incomplete
// array types.
t = astContext.getBaseElementType(t);
if (t->isIncompleteType()) {
// We could try to replicate the logic from
// ASTContext::getTypeAlignIfKnown, but nothing uses the alignment if the
// type is incomplete, so it's impossible to test. We could try to reuse
// getTypeAlignIfKnown, but that doesn't return the information we need
// to set baseInfo. So just ignore the possibility that the alignment is
// greater than one.
if (baseInfo)
*baseInfo = LValueBaseInfo(AlignmentSource::Type);
return CharUnits::One();
}
if (baseInfo)
*baseInfo = LValueBaseInfo(AlignmentSource::Type);
CharUnits alignment;
if (t.getQualifiers().hasUnaligned()) {
alignment = CharUnits::One();
} else {
assert(!cir::MissingFeatures::alignCXXRecordDecl());
alignment = astContext.getTypeAlignInChars(t);
}
// Cap to the global maximum type alignment unless the alignment
// was somehow explicit on the type.
if (unsigned maxAlign = astContext.getLangOpts().MaxTypeAlign) {
if (alignment.getQuantity() > maxAlign &&
!astContext.isAlignmentRequired(t))
alignment = CharUnits::fromQuantity(maxAlign);
}
return alignment;
}
const TargetCIRGenInfo &CIRGenModule::getTargetCIRGenInfo() {
if (theTargetCIRGenInfo)
return *theTargetCIRGenInfo;
const llvm::Triple &triple = getTarget().getTriple();
switch (triple.getArch()) {
default:
assert(!cir::MissingFeatures::targetCIRGenInfoArch());
// Currently we just fall through to x86_64.
[[fallthrough]];
case llvm::Triple::x86_64: {
switch (triple.getOS()) {
default:
assert(!cir::MissingFeatures::targetCIRGenInfoOS());
// Currently we just fall through to x86_64.
[[fallthrough]];
case llvm::Triple::Linux:
theTargetCIRGenInfo = createX8664TargetCIRGenInfo(genTypes);
return *theTargetCIRGenInfo;
}
}
}
}
mlir::Location CIRGenModule::getLoc(SourceLocation cLoc) {
assert(cLoc.isValid() && "expected valid source location");
const SourceManager &sm = astContext.getSourceManager();
PresumedLoc pLoc = sm.getPresumedLoc(cLoc);
StringRef filename = pLoc.getFilename();
return mlir::FileLineColLoc::get(builder.getStringAttr(filename),
pLoc.getLine(), pLoc.getColumn());
}
mlir::Location CIRGenModule::getLoc(SourceRange cRange) {
assert(cRange.isValid() && "expected a valid source range");
mlir::Location begin = getLoc(cRange.getBegin());
mlir::Location end = getLoc(cRange.getEnd());
mlir::Attribute metadata;
return mlir::FusedLoc::get({begin, end}, metadata, builder.getContext());
}
mlir::Operation *
CIRGenModule::getAddrOfGlobal(GlobalDecl gd, ForDefinition_t isForDefinition) {
const Decl *d = gd.getDecl();
if (isa<CXXConstructorDecl>(d) || isa<CXXDestructorDecl>(d))
return getAddrOfCXXStructor(gd, /*FnInfo=*/nullptr, /*FnType=*/nullptr,
/*DontDefer=*/false, isForDefinition);
if (isa<CXXMethodDecl>(d)) {
const CIRGenFunctionInfo &fi =
getTypes().arrangeCXXMethodDeclaration(cast<CXXMethodDecl>(d));
cir::FuncType ty = getTypes().getFunctionType(fi);
return getAddrOfFunction(gd, ty, /*ForVTable=*/false, /*DontDefer=*/false,
isForDefinition);
}
if (isa<FunctionDecl>(d)) {
const CIRGenFunctionInfo &fi = getTypes().arrangeGlobalDeclaration(gd);
cir::FuncType ty = getTypes().getFunctionType(fi);
return getAddrOfFunction(gd, ty, /*ForVTable=*/false, /*DontDefer=*/false,
isForDefinition);
}
return getAddrOfGlobalVar(cast<VarDecl>(d), /*ty=*/nullptr, isForDefinition)
.getDefiningOp();
}
void CIRGenModule::emitGlobalDecl(const clang::GlobalDecl &d) {
// We call getAddrOfGlobal with isForDefinition set to ForDefinition in
// order to get a Value with exactly the type we need, not something that
// might have been created for another decl with the same mangled name but
// different type.
mlir::Operation *op = getAddrOfGlobal(d, ForDefinition);
// In case of different address spaces, we may still get a cast, even with
// IsForDefinition equal to ForDefinition. Query mangled names table to get
// GlobalValue.
if (!op)
op = getGlobalValue(getMangledName(d));
assert(op && "expected a valid global op");
// Check to see if we've already emitted this. This is necessary for a
// couple of reasons: first, decls can end up in deferred-decls queue
// multiple times, and second, decls can end up with definitions in unusual
// ways (e.g. by an extern inline function acquiring a strong function
// redefinition). Just ignore those cases.
// TODO: Not sure what to map this to for MLIR
mlir::Operation *globalValueOp = op;
if (auto gv = dyn_cast<cir::GetGlobalOp>(op))
globalValueOp =
mlir::SymbolTable::lookupSymbolIn(getModule(), gv.getNameAttr());
if (auto cirGlobalValue =
dyn_cast<cir::CIRGlobalValueInterface>(globalValueOp))
if (!cirGlobalValue.isDeclaration())
return;
// If this is OpenMP, check if it is legal to emit this global normally.
assert(!cir::MissingFeatures::openMP());
// Otherwise, emit the definition and move on to the next one.
emitGlobalDefinition(d, op);
}
void CIRGenModule::emitDeferred() {
// Emit code for any potentially referenced deferred decls. Since a previously
// unused static decl may become used during the generation of code for a
// static function, iterate until no changes are made.
assert(!cir::MissingFeatures::openMP());
assert(!cir::MissingFeatures::deferredVtables());
assert(!cir::MissingFeatures::cudaSupport());
// Stop if we're out of both deferred vtables and deferred declarations.
if (deferredDeclsToEmit.empty())
return;
// Grab the list of decls to emit. If emitGlobalDefinition schedules more
// work, it will not interfere with this.
std::vector<GlobalDecl> curDeclsToEmit;
curDeclsToEmit.swap(deferredDeclsToEmit);
for (const GlobalDecl &d : curDeclsToEmit) {
emitGlobalDecl(d);
// If we found out that we need to emit more decls, do that recursively.
// This has the advantage that the decls are emitted in a DFS and related
// ones are close together, which is convenient for testing.
if (!deferredDeclsToEmit.empty()) {
emitDeferred();
assert(deferredDeclsToEmit.empty());
}
}
}
void CIRGenModule::emitGlobal(clang::GlobalDecl gd) {
if (const auto *cd = dyn_cast<clang::OpenACCConstructDecl>(gd.getDecl())) {
emitGlobalOpenACCDecl(cd);
return;
}
const auto *global = cast<ValueDecl>(gd.getDecl());
if (const auto *fd = dyn_cast<FunctionDecl>(global)) {
// Update deferred annotations with the latest declaration if the function
// was already used or defined.
if (fd->hasAttr<AnnotateAttr>())
errorNYI(fd->getSourceRange(), "deferredAnnotations");
if (!fd->doesThisDeclarationHaveABody()) {
if (!fd->doesDeclarationForceExternallyVisibleDefinition())
return;
errorNYI(fd->getSourceRange(),
"function declaration that forces code gen");
return;
}
} else {
const auto *vd = cast<VarDecl>(global);
assert(vd->isFileVarDecl() && "Cannot emit local var decl as global.");
if (vd->isThisDeclarationADefinition() != VarDecl::Definition &&
!astContext.isMSStaticDataMemberInlineDefinition(vd)) {
assert(!cir::MissingFeatures::openMP());
// If this declaration may have caused an inline variable definition to
// change linkage, make sure that it's emitted.
if (astContext.getInlineVariableDefinitionKind(vd) ==
ASTContext::InlineVariableDefinitionKind::Strong)
getAddrOfGlobalVar(vd);
// Otherwise, we can ignore this declaration. The variable will be emitted
// on its first use.
return;
}
}
// Defer code generation to first use when possible, e.g. if this is an inline
// function. If the global must always be emitted, do it eagerly if possible
// to benefit from cache locality. Deferring code generation is necessary to
// avoid adding initializers to external declarations.
if (mustBeEmitted(global) && mayBeEmittedEagerly(global)) {
// Emit the definition if it can't be deferred.
emitGlobalDefinition(gd);
return;
}
// If we're deferring emission of a C++ variable with an initializer, remember
// the order in which it appeared on the file.
assert(!cir::MissingFeatures::deferredCXXGlobalInit());
llvm::StringRef mangledName = getMangledName(gd);
if (getGlobalValue(mangledName) != nullptr) {
// The value has already been used and should therefore be emitted.
addDeferredDeclToEmit(gd);
} else if (mustBeEmitted(global)) {
// The value must be emitted, but cannot be emitted eagerly.
assert(!mayBeEmittedEagerly(global));
addDeferredDeclToEmit(gd);
} else {
// Otherwise, remember that we saw a deferred decl with this name. The first
// use of the mangled name will cause it to move into deferredDeclsToEmit.
deferredDecls[mangledName] = gd;
}
}
void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
mlir::Operation *op) {
auto const *funcDecl = cast<FunctionDecl>(gd.getDecl());
const CIRGenFunctionInfo &fi = getTypes().arrangeGlobalDeclaration(gd);
cir::FuncType funcType = getTypes().getFunctionType(fi);
cir::FuncOp funcOp = dyn_cast_if_present<cir::FuncOp>(op);
if (!funcOp || funcOp.getFunctionType() != funcType) {
funcOp = getAddrOfFunction(gd, funcType, /*ForVTable=*/false,
/*DontDefer=*/true, ForDefinition);
}
// Already emitted.
if (!funcOp.isDeclaration())
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 (funcDecl->getAttr<ConstructorAttr>())
errorNYI(funcDecl->getSourceRange(), "constructor attribute");
if (funcDecl->getAttr<DestructorAttr>())
errorNYI(funcDecl->getSourceRange(), "destructor attribute");
if (funcDecl->getAttr<AnnotateAttr>())
errorNYI(funcDecl->getSourceRange(), "deferredAnnotations");
}
void CIRGenModule::handleCXXStaticMemberVarInstantiation(VarDecl *vd) {
VarDecl::DefinitionKind dk = vd->isThisDeclarationADefinition();
if (dk == VarDecl::Definition && vd->hasAttr<DLLImportAttr>())
return;
TemplateSpecializationKind tsk = vd->getTemplateSpecializationKind();
// If we have a definition, this might be a deferred decl. If the
// instantiation is explicit, make sure we emit it at the end.
if (vd->getDefinition() && tsk == TSK_ExplicitInstantiationDefinition)
getAddrOfGlobalVar(vd);
emitTopLevelDecl(vd);
}
mlir::Operation *CIRGenModule::getGlobalValue(StringRef name) {
return mlir::SymbolTable::lookupSymbolIn(theModule, name);
}
cir::GlobalOp CIRGenModule::createGlobalOp(CIRGenModule &cgm,
mlir::Location loc, StringRef name,
mlir::Type t,
mlir::Operation *insertPoint) {
cir::GlobalOp g;
CIRGenBuilderTy &builder = cgm.getBuilder();
{
mlir::OpBuilder::InsertionGuard guard(builder);
// If an insertion point is provided, we're replacing an existing global,
// otherwise, create the new global immediately after the last gloabl we
// emitted.
if (insertPoint) {
builder.setInsertionPoint(insertPoint);
} else {
// Group global operations together at the top of the module.
if (cgm.lastGlobalOp)
builder.setInsertionPointAfter(cgm.lastGlobalOp);
else
builder.setInsertionPointToStart(cgm.getModule().getBody());
}
g = builder.create<cir::GlobalOp>(loc, name, t);
if (!insertPoint)
cgm.lastGlobalOp = g;
// Default to private until we can judge based on the initializer,
// since MLIR doesn't allow public declarations.
mlir::SymbolTable::setSymbolVisibility(
g, mlir::SymbolTable::Visibility::Private);
}
return g;
}
void CIRGenModule::setCommonAttributes(GlobalDecl gd, mlir::Operation *gv) {
const Decl *d = gd.getDecl();
if (isa_and_nonnull<NamedDecl>(d))
setGVProperties(gv, dyn_cast<NamedDecl>(d));
assert(!cir::MissingFeatures::defaultVisibility());
assert(!cir::MissingFeatures::opGlobalUsedOrCompilerUsed());
}
void CIRGenModule::setNonAliasAttributes(GlobalDecl gd, mlir::Operation *op) {
setCommonAttributes(gd, op);
assert(!cir::MissingFeatures::opGlobalUsedOrCompilerUsed());
assert(!cir::MissingFeatures::opGlobalSection());
assert(!cir::MissingFeatures::opFuncCPUAndFeaturesAttributes());
assert(!cir::MissingFeatures::opFuncSection());
assert(!cir::MissingFeatures::setTargetAttributes());
}
static void setLinkageForGV(cir::GlobalOp &gv, const NamedDecl *nd) {
// Set linkage and visibility in case we never see a definition.
LinkageInfo lv = nd->getLinkageAndVisibility();
// Don't set internal linkage on declarations.
// "extern_weak" is overloaded in LLVM; we probably should have
// separate linkage types for this.
if (isExternallyVisible(lv.getLinkage()) &&
(nd->hasAttr<WeakAttr>() || nd->isWeakImported()))
gv.setLinkage(cir::GlobalLinkageKind::ExternalWeakLinkage);
}
/// 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;
}
mlir::Location loc = getLoc(d->getSourceRange());
// mlir::SymbolTable::Visibility::Public is the default, no need to explicitly
// mark it as such.
cir::GlobalOp gv =
CIRGenModule::createGlobalOp(*this, loc, mangledName, ty,
/*insertPoint=*/entry.getOperation());
// This is the first use or definition of a mangled name. If there is a
// deferred decl with this name, remember that we need to emit it at the end
// of the file.
auto ddi = deferredDecls.find(mangledName);
if (ddi != deferredDecls.end()) {
// Move the potentially referenced deferred decl to the DeferredDeclsToEmit
// list, and remove it from DeferredDecls (since we don't need it anymore).
addDeferredDeclToEmit(ddi->second);
deferredDecls.erase(ddi);
}
// Handle things which are present even on external declarations.
if (d) {
if (langOpts.OpenMP && !langOpts.OpenMPSimd)
errorNYI(d->getSourceRange(), "OpenMP target global variable");
gv.setAlignmentAttr(getSize(astContext.getDeclAlign(d)));
assert(!cir::MissingFeatures::opGlobalConstant());
setLinkageForGV(gv, d);
if (d->getTLSKind())
errorNYI(d->getSourceRange(), "thread local global variable");
setGVProperties(gv, d);
// If required by the ABI, treat declarations of static data members with
// inline initializers as definitions.
if (astContext.isMSStaticDataMemberInlineDefinition(d))
errorNYI(d->getSourceRange(), "MS static data member inline definition");
assert(!cir::MissingFeatures::opGlobalSection());
gv.setGlobalVisibilityAttr(getGlobalVisibilityAttrFromDecl(d));
// Handle XCore specific ABI requirements.
if (getTriple().getArch() == llvm::Triple::xcore)
errorNYI(d->getSourceRange(), "XCore specific ABI requirements");
// Check if we a have a const declaration with an initializer, we may be
// able to emit it as available_externally to expose it's value to the
// optimizer.
if (getLangOpts().CPlusPlus && gv.isPublic() &&
d->getType().isConstQualified() && gv.isDeclaration() &&
!d->hasDefinition() && d->hasInit() && !d->hasAttr<DLLImportAttr>())
errorNYI(d->getSourceRange(),
"external const declaration with initializer");
}
return gv;
}
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);
StringRef mangledName = getMangledName(d);
return getOrCreateCIRGlobal(mangledName, 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());
}
cir::GlobalViewAttr CIRGenModule::getAddrOfGlobalVarAttr(const VarDecl *d) {
assert(d->hasGlobalStorage() && "Not a global variable");
mlir::Type ty = getTypes().convertTypeForMem(d->getType());
cir::GlobalOp globalOp = getOrCreateCIRGlobal(d, ty, NotForDefinition);
assert(!cir::MissingFeatures::addressSpace());
cir::PointerType ptrTy = builder.getPointerTo(globalOp.getSymType());
return builder.getGlobalViewAttr(ptrTy, globalOp);
}
void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd,
bool isTentative) {
if (getLangOpts().OpenCL || getLangOpts().OpenMPIsTargetDevice) {
errorNYI(vd->getSourceRange(), "emit OpenCL/OpenMP global variable");
return;
}
// Whether the definition of the variable is available externally.
// If yes, we shouldn't emit the GloablCtor and GlobalDtor for the variable
// since this is the job for its original source.
bool isDefinitionAvailableExternally =
astContext.GetGVALinkageForVariable(vd) == GVA_AvailableExternally;
assert(!cir::MissingFeatures::needsGlobalCtorDtor());
// It is useless to emit the definition for an available_externally variable
// which can't be marked as const.
if (isDefinitionAvailableExternally &&
(!vd->hasConstantInitialization() ||
// TODO: Update this when we have interface to check constexpr
// destructor.
vd->needsDestruction(astContext) ||
!vd->getType().isConstantStorage(astContext, true, true)))
return;
mlir::Attribute init;
const VarDecl *initDecl;
const Expr *initExpr = vd->getAnyInitializer(initDecl);
std::optional<ConstantEmitter> emitter;
assert(!cir::MissingFeatures::cudaSupport());
if (vd->hasAttr<LoaderUninitializedAttr>()) {
errorNYI(vd->getSourceRange(), "loader uninitialized attribute");
return;
} else if (!initExpr) {
// This is a tentative definition; tentative definitions are
// implicitly initialized with { 0 }.
//
// Note that tentative definitions are only emitted at the end of
// a translation unit, so they should never have incomplete
// type. In addition, EmitTentativeDefinition makes sure that we
// never attempt to emit a tentative definition if a real one
// exists. A use may still exists, however, so we still may need
// to do a RAUW.
assert(!vd->getType()->isIncompleteType() && "Unexpected incomplete type");
init = builder.getZeroInitAttr(convertType(vd->getType()));
} else {
emitter.emplace(*this);
mlir::Attribute initializer = emitter->tryEmitForInitializer(*initDecl);
if (!initializer) {
QualType qt = initExpr->getType();
if (vd->getType()->isReferenceType())
qt = vd->getType();
if (getLangOpts().CPlusPlus) {
if (initDecl->hasFlexibleArrayInit(astContext))
errorNYI(vd->getSourceRange(), "flexible array initializer");
init = builder.getZeroInitAttr(convertType(qt));
if (astContext.GetGVALinkageForVariable(vd) != GVA_AvailableExternally)
errorNYI(vd->getSourceRange(), "global constructor");
} else {
errorNYI(vd->getSourceRange(), "static initializer");
}
} else {
init = initializer;
// We don't need an initializer, so remove the entry for the delayed
// initializer position (just in case this entry was delayed) if we
// also don't need to register a destructor.
if (vd->needsDestruction(astContext) == QualType::DK_cxx_destructor)
errorNYI(vd->getSourceRange(), "delayed destructor");
}
}
mlir::Type initType;
if (mlir::isa<mlir::SymbolRefAttr>(init)) {
errorNYI(vd->getSourceRange(), "global initializer is a symbol reference");
return;
} else {
assert(mlir::isa<mlir::TypedAttr>(init) && "This should have a type");
auto typedInitAttr = mlir::cast<mlir::TypedAttr>(init);
initType = typedInitAttr.getType();
}
assert(!mlir::isa<mlir::NoneType>(initType) && "Should have a type by now");
cir::GlobalOp gv =
getOrCreateCIRGlobal(vd, initType, ForDefinition_t(!isTentative));
// TODO(cir): Strip off pointer casts from Entry if we get them?
if (!gv || gv.getSymType() != initType) {
errorNYI(vd->getSourceRange(), "global initializer with type mismatch");
return;
}
assert(!cir::MissingFeatures::maybeHandleStaticInExternC());
if (vd->hasAttr<AnnotateAttr>()) {
errorNYI(vd->getSourceRange(), "annotate global variable");
}
if (langOpts.CUDA) {
errorNYI(vd->getSourceRange(), "CUDA global variable");
}
// Set initializer and finalize emission
CIRGenModule::setInitializer(gv, init);
if (emitter)
emitter->finalize(gv);
// Set CIR's linkage type as appropriate.
cir::GlobalLinkageKind linkage =
getCIRLinkageVarDefinition(vd, /*IsConstant=*/false);
// Set CIR linkage and DLL storage class.
gv.setLinkage(linkage);
// FIXME(cir): setLinkage should likely set MLIR's visibility automatically.
gv.setVisibility(getMLIRVisibilityFromCIRLinkage(linkage));
assert(!cir::MissingFeatures::opGlobalDLLImportExport());
if (linkage == cir::GlobalLinkageKind::CommonLinkage)
errorNYI(initExpr->getSourceRange(), "common linkage");
setNonAliasAttributes(vd, gv);
assert(!cir::MissingFeatures::opGlobalThreadLocal());
maybeSetTrivialComdat(*vd, gv);
}
void CIRGenModule::emitGlobalDefinition(clang::GlobalDecl gd,
mlir::Operation *op) {
const auto *decl = cast<ValueDecl>(gd.getDecl());
if (const auto *fd = dyn_cast<FunctionDecl>(decl)) {
// TODO(CIR): Skip generation of CIR for functions with available_externally
// linkage at -O0.
if (const auto *method = dyn_cast<CXXMethodDecl>(decl)) {
// Make sure to emit the definition(s) before we emit the thunks. This is
// necessary for the generation of certain thunks.
if (isa<CXXConstructorDecl>(method) || isa<CXXDestructorDecl>(method))
abi->emitCXXStructor(gd);
else if (fd->isMultiVersion())
errorNYI(method->getSourceRange(), "multiversion functions");
else
emitGlobalFunctionDefinition(gd, op);
if (method->isVirtual())
errorNYI(method->getSourceRange(), "virtual member function");
return;
}
if (fd->isMultiVersion())
errorNYI(fd->getSourceRange(), "multiversion functions");
emitGlobalFunctionDefinition(gd, op);
return;
}
if (const auto *vd = dyn_cast<VarDecl>(decl))
return emitGlobalVarDefinition(vd, !vd->hasDefinition());
llvm_unreachable("Invalid argument to CIRGenModule::emitGlobalDefinition");
}
mlir::Attribute
CIRGenModule::getConstantArrayFromStringLiteral(const StringLiteral *e) {
assert(!e->getType()->isPointerType() && "Strings are always arrays");
// Don't emit it as the address of the string, emit the string data itself
// as an inline array.
if (e->getCharByteWidth() == 1) {
SmallString<64> str(e->getString());
// Resize the string to the right size, which is indicated by its type.
const ConstantArrayType *cat =
astContext.getAsConstantArrayType(e->getType());
uint64_t finalSize = cat->getZExtSize();
str.resize(finalSize);
mlir::Type eltTy = convertType(cat->getElementType());
return builder.getString(str, eltTy, finalSize);
}
errorNYI(e->getSourceRange(),
"getConstantArrayFromStringLiteral: wide characters");
return mlir::Attribute();
}
bool CIRGenModule::supportsCOMDAT() const {
return getTriple().supportsCOMDAT();
}
static bool shouldBeInCOMDAT(CIRGenModule &cgm, const Decl &d) {
if (!cgm.supportsCOMDAT())
return false;
if (d.hasAttr<SelectAnyAttr>())
return true;
GVALinkage linkage;
if (auto *vd = dyn_cast<VarDecl>(&d))
linkage = cgm.getASTContext().GetGVALinkageForVariable(vd);
else
linkage =
cgm.getASTContext().GetGVALinkageForFunction(cast<FunctionDecl>(&d));
switch (linkage) {
case clang::GVA_Internal:
case clang::GVA_AvailableExternally:
case clang::GVA_StrongExternal:
return false;
case clang::GVA_DiscardableODR:
case clang::GVA_StrongODR:
return true;
}
llvm_unreachable("No such linkage");
}
void CIRGenModule::maybeSetTrivialComdat(const Decl &d, mlir::Operation *op) {
if (!shouldBeInCOMDAT(*this, d))
return;
if (auto globalOp = dyn_cast_or_null<cir::GlobalOp>(op)) {
globalOp.setComdat(true);
} else {
auto funcOp = cast<cir::FuncOp>(op);
funcOp.setComdat(true);
}
}
void CIRGenModule::updateCompletedType(const TagDecl *td) {
// Make sure that this type is translated.
genTypes.updateCompletedType(td);
}
void CIRGenModule::addReplacement(StringRef name, mlir::Operation *op) {
replacements[name] = op;
}
void CIRGenModule::replacePointerTypeArgs(cir::FuncOp oldF, cir::FuncOp newF) {
std::optional<mlir::SymbolTable::UseRange> optionalUseRange =
oldF.getSymbolUses(theModule);
if (!optionalUseRange)
return;
for (const mlir::SymbolTable::SymbolUse &u : *optionalUseRange) {
// CallTryOp only shows up after FlattenCFG.
auto call = mlir::dyn_cast<cir::CallOp>(u.getUser());
if (!call)
continue;
for (const auto [argOp, fnArgType] :
llvm::zip(call.getArgs(), newF.getFunctionType().getInputs())) {
if (argOp.getType() == fnArgType)
continue;
// The purpose of this entire function is to insert bitcasts in the case
// where these types don't match, but I haven't seen a case where that
// happens.
errorNYI(call.getLoc(), "replace call with mismatched types");
}
}
}
void CIRGenModule::applyReplacements() {
for (auto &i : replacements) {
StringRef mangledName = i.first();
mlir::Operation *replacement = i.second;
mlir::Operation *entry = getGlobalValue(mangledName);
if (!entry)
continue;
assert(isa<cir::FuncOp>(entry) && "expected function");
auto oldF = cast<cir::FuncOp>(entry);
auto newF = dyn_cast<cir::FuncOp>(replacement);
if (!newF) {
// In classic codegen, this can be a global alias, a bitcast, or a GEP.
errorNYI(replacement->getLoc(), "replacement is not a function");
continue;
}
// LLVM has opaque pointer but CIR not. So we may have to handle these
// different pointer types when performing replacement.
replacePointerTypeArgs(oldF, newF);
// Replace old with new, but keep the old order.
if (oldF.replaceAllSymbolUses(newF.getSymNameAttr(), theModule).failed())
llvm_unreachable("internal error, cannot RAUW symbol");
if (newF) {
newF->moveBefore(oldF);
oldF->erase();
}
}
}
cir::GlobalOp CIRGenModule::createOrReplaceCXXRuntimeVariable(
mlir::Location loc, StringRef name, mlir::Type ty,
cir::GlobalLinkageKind linkage, clang::CharUnits alignment) {
auto gv = mlir::dyn_cast_or_null<cir::GlobalOp>(
mlir::SymbolTable::lookupSymbolIn(theModule, name));
if (gv) {
// There should be handling added here to check the type as assert that
// gv was a declaration if the type doesn't match and handling below
// to replace the variable if it was a declaration.
errorNYI(loc, "createOrReplaceCXXRuntimeVariable: already exists");
return gv;
}
// Create a new variable.
gv = createGlobalOp(*this, loc, name, ty);
// Set up extra information and add to the module
gv.setLinkageAttr(
cir::GlobalLinkageKindAttr::get(&getMLIRContext(), linkage));
mlir::SymbolTable::setSymbolVisibility(gv,
CIRGenModule::getMLIRVisibility(gv));
if (supportsCOMDAT() && cir::isWeakForLinker(linkage) &&
!gv.hasAvailableExternallyLinkage()) {
gv.setComdat(true);
}
gv.setAlignmentAttr(getSize(alignment));
setDSOLocal(static_cast<mlir::Operation *>(gv));
return gv;
}
// TODO(CIR): this could be a common method between LLVM codegen.
static bool isVarDeclStrongDefinition(const ASTContext &astContext,
CIRGenModule &cgm, const VarDecl *vd,
bool noCommon) {
// Don't give variables common linkage if -fno-common was specified unless it
// was overridden by a NoCommon attribute.
if ((noCommon || vd->hasAttr<NoCommonAttr>()) && !vd->hasAttr<CommonAttr>())
return true;
// C11 6.9.2/2:
// A declaration of an identifier for an object that has file scope without
// an initializer, and without a storage-class specifier or with the
// storage-class specifier static, constitutes a tentative definition.
if (vd->getInit() || vd->hasExternalStorage())
return true;
// A variable cannot be both common and exist in a section.
if (vd->hasAttr<SectionAttr>())
return true;
// A variable cannot be both common and exist in a section.
// We don't try to determine which is the right section in the front-end.
// If no specialized section name is applicable, it will resort to default.
if (vd->hasAttr<PragmaClangBSSSectionAttr>() ||
vd->hasAttr<PragmaClangDataSectionAttr>() ||
vd->hasAttr<PragmaClangRelroSectionAttr>() ||
vd->hasAttr<PragmaClangRodataSectionAttr>())
return true;
// Thread local vars aren't considered common linkage.
if (vd->getTLSKind())
return true;
// Tentative definitions marked with WeakImportAttr are true definitions.
if (vd->hasAttr<WeakImportAttr>())
return true;
// A variable cannot be both common and exist in a comdat.
if (shouldBeInCOMDAT(cgm, *vd))
return true;
// Declarations with a required alignment do not have common linkage in MSVC
// mode.
if (astContext.getTargetInfo().getCXXABI().isMicrosoft()) {
if (vd->hasAttr<AlignedAttr>())
return true;
QualType varType = vd->getType();
if (astContext.isAlignmentRequired(varType))
return true;
if (const auto *rt = varType->getAs<RecordType>()) {
const RecordDecl *rd = rt->getOriginalDecl()->getDefinitionOrSelf();
for (const FieldDecl *fd : rd->fields()) {
if (fd->isBitField())
continue;
if (fd->hasAttr<AlignedAttr>())
return true;
if (astContext.isAlignmentRequired(fd->getType()))
return true;
}
}
}
// Microsoft's link.exe doesn't support alignments greater than 32 bytes for
// common symbols, so symbols with greater alignment requirements cannot be
// common.
// Other COFF linkers (ld.bfd and LLD) support arbitrary power-of-two
// alignments for common symbols via the aligncomm directive, so this
// restriction only applies to MSVC environments.
if (astContext.getTargetInfo().getTriple().isKnownWindowsMSVCEnvironment() &&
astContext.getTypeAlignIfKnown(vd->getType()) >
astContext.toBits(CharUnits::fromQuantity(32)))
return true;
return false;
}
cir::GlobalLinkageKind CIRGenModule::getCIRLinkageForDeclarator(
const DeclaratorDecl *dd, GVALinkage linkage, bool isConstantVariable) {
if (linkage == GVA_Internal)
return cir::GlobalLinkageKind::InternalLinkage;
if (dd->hasAttr<WeakAttr>()) {
if (isConstantVariable)
return cir::GlobalLinkageKind::WeakODRLinkage;
return cir::GlobalLinkageKind::WeakAnyLinkage;
}
if (const auto *fd = dd->getAsFunction())
if (fd->isMultiVersion() && linkage == GVA_AvailableExternally)
return cir::GlobalLinkageKind::LinkOnceAnyLinkage;
// We are guaranteed to have a strong definition somewhere else,
// so we can use available_externally linkage.
if (linkage == GVA_AvailableExternally)
return cir::GlobalLinkageKind::AvailableExternallyLinkage;
// Note that Apple's kernel linker doesn't support symbol
// coalescing, so we need to avoid linkonce and weak linkages there.
// Normally, this means we just map to internal, but for explicit
// instantiations we'll map to external.
// In C++, the compiler has to emit a definition in every translation unit
// that references the function. We should use linkonce_odr because
// a) if all references in this translation unit are optimized away, we
// don't need to codegen it. b) if the function persists, it needs to be
// merged with other definitions. c) C++ has the ODR, so we know the
// definition is dependable.
if (linkage == GVA_DiscardableODR)
return !astContext.getLangOpts().AppleKext
? cir::GlobalLinkageKind::LinkOnceODRLinkage
: cir::GlobalLinkageKind::InternalLinkage;
// An explicit instantiation of a template has weak linkage, since
// explicit instantiations can occur in multiple translation units
// and must all be equivalent. However, we are not allowed to
// throw away these explicit instantiations.
//
// CUDA/HIP: For -fno-gpu-rdc case, device code is limited to one TU,
// so say that CUDA templates are either external (for kernels) or internal.
// This lets llvm perform aggressive inter-procedural optimizations. For
// -fgpu-rdc case, device function calls across multiple TU's are allowed,
// therefore we need to follow the normal linkage paradigm.
if (linkage == GVA_StrongODR) {
if (getLangOpts().AppleKext)
return cir::GlobalLinkageKind::ExternalLinkage;
if (getLangOpts().CUDA && getLangOpts().CUDAIsDevice &&
!getLangOpts().GPURelocatableDeviceCode)
return dd->hasAttr<CUDAGlobalAttr>()
? cir::GlobalLinkageKind::ExternalLinkage
: cir::GlobalLinkageKind::InternalLinkage;
return cir::GlobalLinkageKind::WeakODRLinkage;
}
// C++ doesn't have tentative definitions and thus cannot have common
// linkage.
if (!getLangOpts().CPlusPlus && isa<VarDecl>(dd) &&
!isVarDeclStrongDefinition(astContext, *this, cast<VarDecl>(dd),
getCodeGenOpts().NoCommon)) {
errorNYI(dd->getBeginLoc(), "common linkage", dd->getDeclKindName());
return cir::GlobalLinkageKind::CommonLinkage;
}
// selectany symbols are externally visible, so use weak instead of
// linkonce. MSVC optimizes away references to const selectany globals, so
// all definitions should be the same and ODR linkage should be used.
// http://msdn.microsoft.com/en-us/library/5tkz6s71.aspx
if (dd->hasAttr<SelectAnyAttr>())
return cir::GlobalLinkageKind::WeakODRLinkage;
// Otherwise, we have strong external linkage.
assert(linkage == GVA_StrongExternal);
return cir::GlobalLinkageKind::ExternalLinkage;
}
/// This function is called when we implement a function with no prototype, e.g.
/// "int foo() {}". If there are existing call uses of the old function in the
/// module, this adjusts them to call the new function directly.
///
/// This is not just a cleanup: the always_inline pass requires direct calls to
/// functions to be able to inline them. If there is a bitcast in the way, it
/// won't inline them. Instcombine normally deletes these calls, but it isn't
/// run at -O0.
void CIRGenModule::replaceUsesOfNonProtoTypeWithRealFunction(
mlir::Operation *old, cir::FuncOp newFn) {
// If we're redefining a global as a function, don't transform it.
auto oldFn = mlir::dyn_cast<cir::FuncOp>(old);
if (!oldFn)
return;
// TODO(cir): this RAUW ignores the features below.
assert(!cir::MissingFeatures::opFuncExceptions());
assert(!cir::MissingFeatures::opFuncParameterAttributes());
assert(!cir::MissingFeatures::opFuncOperandBundles());
if (oldFn->getAttrs().size() <= 1)
errorNYI(old->getLoc(),
"replaceUsesOfNonProtoTypeWithRealFunction: Attribute forwarding");
// Mark new function as originated from a no-proto declaration.
newFn.setNoProto(oldFn.getNoProto());
// Iterate through all calls of the no-proto function.
std::optional<mlir::SymbolTable::UseRange> symUses =
oldFn.getSymbolUses(oldFn->getParentOp());
for (const mlir::SymbolTable::SymbolUse &use : symUses.value()) {
mlir::OpBuilder::InsertionGuard guard(builder);
if (auto noProtoCallOp = mlir::dyn_cast<cir::CallOp>(use.getUser())) {
builder.setInsertionPoint(noProtoCallOp);
// Patch call type with the real function type.
cir::CallOp realCallOp = builder.createCallOp(
noProtoCallOp.getLoc(), newFn, noProtoCallOp.getOperands());
// Replace old no proto call with fixed call.
noProtoCallOp.replaceAllUsesWith(realCallOp);
noProtoCallOp.erase();
} else if (auto getGlobalOp =
mlir::dyn_cast<cir::GetGlobalOp>(use.getUser())) {
// Replace type
getGlobalOp.getAddr().setType(
cir::PointerType::get(newFn.getFunctionType()));
} else {
errorNYI(use.getUser()->getLoc(),
"replaceUsesOfNonProtoTypeWithRealFunction: unexpected use");
}
}
}
cir::GlobalLinkageKind
CIRGenModule::getCIRLinkageVarDefinition(const VarDecl *vd, bool isConstant) {
assert(!isConstant && "constant variables NYI");
GVALinkage linkage = astContext.GetGVALinkageForVariable(vd);
return getCIRLinkageForDeclarator(vd, linkage, isConstant);
}
cir::GlobalLinkageKind CIRGenModule::getFunctionLinkage(GlobalDecl gd) {
const auto *d = cast<FunctionDecl>(gd.getDecl());
GVALinkage linkage = astContext.GetGVALinkageForFunction(d);
if (const auto *dtor = dyn_cast<CXXDestructorDecl>(d))
return getCXXABI().getCXXDestructorLinkage(linkage, dtor, gd.getDtorType());
return getCIRLinkageForDeclarator(d, linkage, /*isConstantVariable=*/false);
}
static cir::GlobalOp
generateStringLiteral(mlir::Location loc, mlir::TypedAttr c,
cir::GlobalLinkageKind lt, CIRGenModule &cgm,
StringRef globalName, CharUnits alignment) {
assert(!cir::MissingFeatures::addressSpace());
// Create a global variable for this string
// FIXME(cir): check for insertion point in module level.
cir::GlobalOp gv =
CIRGenModule::createGlobalOp(cgm, loc, globalName, c.getType());
// Set up extra information and add to the module
gv.setAlignmentAttr(cgm.getSize(alignment));
gv.setLinkageAttr(
cir::GlobalLinkageKindAttr::get(cgm.getBuilder().getContext(), lt));
assert(!cir::MissingFeatures::opGlobalThreadLocal());
assert(!cir::MissingFeatures::opGlobalUnnamedAddr());
CIRGenModule::setInitializer(gv, c);
if (gv.isWeakForLinker()) {
assert(cgm.supportsCOMDAT() && "Only COFF uses weak string literals");
gv.setComdat(true);
}
cgm.setDSOLocal(static_cast<mlir::Operation *>(gv));
return gv;
}
// LLVM IR automatically uniques names when new llvm::GlobalVariables are
// created. This is handy, for example, when creating globals for string
// literals. Since we don't do that when creating cir::GlobalOp's, we need
// a mechanism to generate a unique name in advance.
//
// For now, this mechanism is only used in cases where we know that the
// name is compiler-generated, so we don't use the MLIR symbol table for
// the lookup.
std::string CIRGenModule::getUniqueGlobalName(const std::string &baseName) {
// If this is the first time we've generated a name for this basename, use
// it as is and start a counter for this base name.
auto it = cgGlobalNames.find(baseName);
if (it == cgGlobalNames.end()) {
cgGlobalNames[baseName] = 1;
return baseName;
}
std::string result =
baseName + "." + std::to_string(cgGlobalNames[baseName]++);
// There should not be any symbol with this name in the module.
assert(!mlir::SymbolTable::lookupSymbolIn(theModule, result));
return result;
}
/// Return a pointer to a constant array for the given string literal.
cir::GlobalOp CIRGenModule::getGlobalForStringLiteral(const StringLiteral *s,
StringRef name) {
CharUnits alignment =
astContext.getAlignOfGlobalVarInChars(s->getType(), /*VD=*/nullptr);
mlir::Attribute c = getConstantArrayFromStringLiteral(s);
if (getLangOpts().WritableStrings) {
errorNYI(s->getSourceRange(),
"getGlobalForStringLiteral: Writable strings");
}
// Mangle the string literal if that's how the ABI merges duplicate strings.
// Don't do it if they are writable, since we don't want writes in one TU to
// affect strings in another.
if (getCXXABI().getMangleContext().shouldMangleStringLiteral(s) &&
!getLangOpts().WritableStrings) {
errorNYI(s->getSourceRange(),
"getGlobalForStringLiteral: mangle string literals");
}
// Unlike LLVM IR, CIR doesn't automatically unique names for globals, so
// we need to do that explicitly.
std::string uniqueName = getUniqueGlobalName(name.str());
mlir::Location loc = getLoc(s->getSourceRange());
auto typedC = llvm::cast<mlir::TypedAttr>(c);
cir::GlobalOp gv =
generateStringLiteral(loc, typedC, cir::GlobalLinkageKind::PrivateLinkage,
*this, uniqueName, alignment);
setDSOLocal(static_cast<mlir::Operation *>(gv));
assert(!cir::MissingFeatures::sanitizers());
return gv;
}
void CIRGenModule::emitExplicitCastExprType(const ExplicitCastExpr *e,
CIRGenFunction *cgf) {
if (cgf && e->getType()->isVariablyModifiedType())
cgf->emitVariablyModifiedType(e->getType());
assert(!cir::MissingFeatures::generateDebugInfo() &&
"emitExplicitCastExprType");
}
void CIRGenModule::emitDeclContext(const DeclContext *dc) {
for (Decl *decl : dc->decls()) {
// Unlike other DeclContexts, the contents of an ObjCImplDecl at TU scope
// are themselves considered "top-level", so EmitTopLevelDecl on an
// ObjCImplDecl does not recursively visit them. We need to do that in
// case they're nested inside another construct (LinkageSpecDecl /
// ExportDecl) that does stop them from being considered "top-level".
if (auto *oid = dyn_cast<ObjCImplDecl>(decl))
errorNYI(oid->getSourceRange(), "emitDeclConext: ObjCImplDecl");
emitTopLevelDecl(decl);
}
}
// Emit code for a single top level declaration.
void CIRGenModule::emitTopLevelDecl(Decl *decl) {
// Ignore dependent declarations.
if (decl->isTemplated())
return;
switch (decl->getKind()) {
default:
errorNYI(decl->getBeginLoc(), "declaration of kind",
decl->getDeclKindName());
break;
case Decl::CXXConversion:
case Decl::CXXMethod:
case Decl::Function: {
auto *fd = cast<FunctionDecl>(decl);
// Consteval functions shouldn't be emitted.
if (!fd->isConsteval())
emitGlobal(fd);
break;
}
case Decl::Var:
case Decl::Decomposition:
case Decl::VarTemplateSpecialization: {
auto *vd = cast<VarDecl>(decl);
if (isa<DecompositionDecl>(decl)) {
errorNYI(decl->getSourceRange(), "global variable decompositions");
break;
}
emitGlobal(vd);
break;
}
case Decl::OpenACCRoutine:
emitGlobalOpenACCDecl(cast<OpenACCRoutineDecl>(decl));
break;
case Decl::OpenACCDeclare:
emitGlobalOpenACCDecl(cast<OpenACCDeclareDecl>(decl));
break;
case Decl::Enum:
case Decl::Using: // using X; [C++]
case Decl::UsingDirective: // using namespace X; [C++]
case Decl::UsingEnum: // using enum X; [C++]
case Decl::NamespaceAlias:
case Decl::Typedef:
case Decl::TypeAlias: // using foo = bar; [C++11]
case Decl::Record:
assert(!cir::MissingFeatures::generateDebugInfo());
break;
// No code generation needed.
case Decl::ClassTemplate:
case Decl::Concept:
case Decl::CXXDeductionGuide:
case Decl::Empty:
case Decl::FunctionTemplate:
case Decl::StaticAssert:
case Decl::TypeAliasTemplate:
case Decl::UsingShadow:
case Decl::VarTemplate:
case Decl::VarTemplatePartialSpecialization:
break;
case Decl::CXXConstructor:
getCXXABI().emitCXXConstructors(cast<CXXConstructorDecl>(decl));
break;
case Decl::CXXDestructor:
getCXXABI().emitCXXDestructors(cast<CXXDestructorDecl>(decl));
break;
// C++ Decls
case Decl::LinkageSpec:
case Decl::Namespace:
emitDeclContext(Decl::castToDeclContext(decl));
break;
case Decl::ClassTemplateSpecialization:
case Decl::CXXRecord:
assert(!cir::MissingFeatures::generateDebugInfo());
assert(!cir::MissingFeatures::cxxRecordStaticMembers());
break;
case Decl::FileScopeAsm:
// File-scope asm is ignored during device-side CUDA compilation.
if (langOpts.CUDA && langOpts.CUDAIsDevice)
break;
// File-scope asm is ignored during device-side OpenMP compilation.
if (langOpts.OpenMPIsTargetDevice)
break;
// File-scope asm is ignored during device-side SYCL compilation.
if (langOpts.SYCLIsDevice)
break;
auto *file_asm = cast<FileScopeAsmDecl>(decl);
std::string line = file_asm->getAsmString();
globalScopeAsm.push_back(builder.getStringAttr(line));
break;
}
}
void CIRGenModule::setInitializer(cir::GlobalOp &op, mlir::Attribute value) {
// Recompute visibility when updating initializer.
op.setInitialValueAttr(value);
assert(!cir::MissingFeatures::opGlobalVisibility());
}
std::pair<cir::FuncType, cir::FuncOp> CIRGenModule::getAddrAndTypeOfCXXStructor(
GlobalDecl gd, const CIRGenFunctionInfo *fnInfo, cir::FuncType fnType,
bool dontDefer, ForDefinition_t isForDefinition) {
auto *md = cast<CXXMethodDecl>(gd.getDecl());
if (isa<CXXDestructorDecl>(md)) {
// Always alias equivalent complete destructors to base destructors in the
// MS ABI.
if (getTarget().getCXXABI().isMicrosoft() &&
gd.getDtorType() == Dtor_Complete &&
md->getParent()->getNumVBases() == 0)
errorNYI(md->getSourceRange(),
"getAddrAndTypeOfCXXStructor: MS ABI complete destructor");
}
if (!fnType) {
if (!fnInfo)
fnInfo = &getTypes().arrangeCXXStructorDeclaration(gd);
fnType = getTypes().getFunctionType(*fnInfo);
}
auto fn = getOrCreateCIRFunction(getMangledName(gd), fnType, gd,
/*ForVtable=*/false, dontDefer,
/*IsThunk=*/false, isForDefinition);
return {fnType, fn};
}
cir::FuncOp CIRGenModule::getAddrOfFunction(clang::GlobalDecl gd,
mlir::Type funcType, bool forVTable,
bool dontDefer,
ForDefinition_t isForDefinition) {
assert(!cast<FunctionDecl>(gd.getDecl())->isConsteval() &&
"consteval function should never be emitted");
if (!funcType) {
const auto *fd = cast<FunctionDecl>(gd.getDecl());
funcType = convertType(fd->getType());
}
// Devirtualized destructor calls may come through here instead of via
// getAddrOfCXXStructor. Make sure we use the MS ABI base destructor instead
// of the complete destructor when necessary.
if (const auto *dd = dyn_cast<CXXDestructorDecl>(gd.getDecl())) {
if (getTarget().getCXXABI().isMicrosoft() &&
gd.getDtorType() == Dtor_Complete &&
dd->getParent()->getNumVBases() == 0)
errorNYI(dd->getSourceRange(),
"getAddrOfFunction: MS ABI complete destructor");
}
StringRef mangledName = getMangledName(gd);
cir::FuncOp func =
getOrCreateCIRFunction(mangledName, funcType, gd, forVTable, dontDefer,
/*isThunk=*/false, isForDefinition);
return func;
}
static std::string getMangledNameImpl(CIRGenModule &cgm, GlobalDecl gd,
const NamedDecl *nd) {
SmallString<256> buffer;
llvm::raw_svector_ostream out(buffer);
MangleContext &mc = cgm.getCXXABI().getMangleContext();
assert(!cir::MissingFeatures::moduleNameHash());
if (mc.shouldMangleDeclName(nd)) {
mc.mangleName(gd.getWithDecl(nd), out);
} else {
IdentifierInfo *ii = nd->getIdentifier();
assert(ii && "Attempt to mangle unnamed decl.");
const auto *fd = dyn_cast<FunctionDecl>(nd);
if (fd &&
fd->getType()->castAs<FunctionType>()->getCallConv() == CC_X86RegCall) {
cgm.errorNYI(nd->getSourceRange(), "getMangledName: X86RegCall");
} else if (fd && fd->hasAttr<CUDAGlobalAttr>() &&
gd.getKernelReferenceKind() == KernelReferenceKind::Stub) {
cgm.errorNYI(nd->getSourceRange(), "getMangledName: CUDA device stub");
}
out << ii->getName();
}
// Check if the module name hash should be appended for internal linkage
// symbols. This should come before multi-version target suffixes are
// appendded. This is to keep the name and module hash suffix of the internal
// linkage function together. The unique suffix should only be added when name
// mangling is done to make sure that the final name can be properly
// demangled. For example, for C functions without prototypes, name mangling
// is not done and the unique suffix should not be appended then.
assert(!cir::MissingFeatures::moduleNameHash());
if (const auto *fd = dyn_cast<FunctionDecl>(nd)) {
if (fd->isMultiVersion()) {
cgm.errorNYI(nd->getSourceRange(),
"getMangledName: multi-version functions");
}
}
if (cgm.getLangOpts().GPURelocatableDeviceCode) {
cgm.errorNYI(nd->getSourceRange(),
"getMangledName: GPU relocatable device code");
}
return std::string(out.str());
}
StringRef CIRGenModule::getMangledName(GlobalDecl gd) {
GlobalDecl canonicalGd = gd.getCanonicalDecl();
// Some ABIs don't have constructor variants. Make sure that base and complete
// constructors get mangled the same.
if (const auto *cd = dyn_cast<CXXConstructorDecl>(canonicalGd.getDecl())) {
if (!getTarget().getCXXABI().hasConstructorVariants()) {
errorNYI(cd->getSourceRange(),
"getMangledName: C++ constructor without variants");
return cast<NamedDecl>(gd.getDecl())->getIdentifier()->getName();
}
}
// Keep the first result in the case of a mangling collision.
const auto *nd = cast<NamedDecl>(gd.getDecl());
std::string mangledName = getMangledNameImpl(*this, gd, nd);
auto result = manglings.insert(std::make_pair(mangledName, gd));
return mangledDeclNames[canonicalGd] = result.first->first();
}
void CIRGenModule::emitTentativeDefinition(const VarDecl *d) {
assert(!d->getInit() && "Cannot emit definite definitions here!");
StringRef mangledName = getMangledName(d);
mlir::Operation *gv = getGlobalValue(mangledName);
// If we already have a definition, not declaration, with the same mangled
// name, emitting of declaration is not required (and would actually overwrite
// the emitted definition).
if (gv && !mlir::cast<cir::GlobalOp>(gv).isDeclaration())
return;
// If we have not seen a reference to this variable yet, place it into the
// deferred declarations table to be emitted if needed later.
if (!mustBeEmitted(d) && !gv) {
deferredDecls[mangledName] = d;
return;
}
// The tentative definition is the only definition.
emitGlobalVarDefinition(d);
}
bool CIRGenModule::mustBeEmitted(const ValueDecl *global) {
// Never defer when EmitAllDecls is specified.
if (langOpts.EmitAllDecls)
return true;
const auto *vd = dyn_cast<VarDecl>(global);
if (vd &&
((codeGenOpts.KeepPersistentStorageVariables &&
(vd->getStorageDuration() == SD_Static ||
vd->getStorageDuration() == SD_Thread)) ||
(codeGenOpts.KeepStaticConsts && vd->getStorageDuration() == SD_Static &&
vd->getType().isConstQualified())))
return true;
return getASTContext().DeclMustBeEmitted(global);
}
bool CIRGenModule::mayBeEmittedEagerly(const ValueDecl *global) {
// In OpenMP 5.0 variables and function may be marked as
// device_type(host/nohost) and we should not emit them eagerly unless we sure
// that they must be emitted on the host/device. To be sure we need to have
// seen a declare target with an explicit mentioning of the function, we know
// we have if the level of the declare target attribute is -1. Note that we
// check somewhere else if we should emit this at all.
if (langOpts.OpenMP >= 50 && !langOpts.OpenMPSimd) {
std::optional<OMPDeclareTargetDeclAttr *> activeAttr =
OMPDeclareTargetDeclAttr::getActiveAttr(global);
if (!activeAttr || (*activeAttr)->getLevel() != (unsigned)-1)
return false;
}
const auto *fd = dyn_cast<FunctionDecl>(global);
if (fd) {
// Implicit template instantiations may change linkage if they are later
// explicitly instantiated, so they should not be emitted eagerly.
if (fd->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
return false;
// Defer until all versions have been semantically checked.
if (fd->hasAttr<TargetVersionAttr>() && !fd->isMultiVersion())
return false;
if (langOpts.SYCLIsDevice) {
errorNYI(fd->getSourceRange(), "mayBeEmittedEagerly: SYCL");
return false;
}
}
const auto *vd = dyn_cast<VarDecl>(global);
if (vd)
if (astContext.getInlineVariableDefinitionKind(vd) ==
ASTContext::InlineVariableDefinitionKind::WeakUnknown)
// A definition of an inline constexpr static data member may change
// linkage later if it's redeclared outside the class.
return false;
// If OpenMP is enabled and threadprivates must be generated like TLS, delay
// codegen for global variables, because they may be marked as threadprivate.
if (langOpts.OpenMP && langOpts.OpenMPUseTLS &&
astContext.getTargetInfo().isTLSSupported() && isa<VarDecl>(global) &&
!global->getType().isConstantStorage(astContext, false, false) &&
!OMPDeclareTargetDeclAttr::isDeclareTargetDeclaration(global))
return false;
assert((fd || vd) &&
"Only FunctionDecl and VarDecl should hit this path so far.");
return true;
}
static bool shouldAssumeDSOLocal(const CIRGenModule &cgm,
cir::CIRGlobalValueInterface gv) {
if (gv.hasLocalLinkage())
return true;
if (!gv.hasDefaultVisibility() && !gv.hasExternalWeakLinkage())
return true;
// DLLImport explicitly marks the GV as external.
// so it shouldn't be dso_local
// But we don't have the info set now
assert(!cir::MissingFeatures::opGlobalDLLImportExport());
const llvm::Triple &tt = cgm.getTriple();
const CodeGenOptions &cgOpts = cgm.getCodeGenOpts();
if (tt.isOSCygMing()) {
// In MinGW and Cygwin, variables without DLLImport can still be
// automatically imported from a DLL by the linker; don't mark variables
// that potentially could come from another DLL as DSO local.
// With EmulatedTLS, TLS variables can be autoimported from other DLLs
// (and this actually happens in the public interface of libstdc++), so
// such variables can't be marked as DSO local. (Native TLS variables
// can't be dllimported at all, though.)
cgm.errorNYI("shouldAssumeDSOLocal: MinGW");
}
// On COFF, don't mark 'extern_weak' symbols as DSO local. If these symbols
// remain unresolved in the link, they can be resolved to zero, which is
// outside the current DSO.
if (tt.isOSBinFormatCOFF() && gv.hasExternalWeakLinkage())
return false;
// Every other GV is local on COFF.
// Make an exception for windows OS in the triple: Some firmware builds use
// *-win32-macho triples. This (accidentally?) produced windows relocations
// without GOT tables in older clang versions; Keep this behaviour.
// FIXME: even thread local variables?
if (tt.isOSBinFormatCOFF() || (tt.isOSWindows() && tt.isOSBinFormatMachO()))
return true;
// Only handle COFF and ELF for now.
if (!tt.isOSBinFormatELF())
return false;
llvm::Reloc::Model rm = cgOpts.RelocationModel;
const LangOptions &lOpts = cgm.getLangOpts();
if (rm != llvm::Reloc::Static && !lOpts.PIE) {
// On ELF, if -fno-semantic-interposition is specified and the target
// supports local aliases, there will be neither CC1
// -fsemantic-interposition nor -fhalf-no-semantic-interposition. Set
// dso_local on the function if using a local alias is preferable (can avoid
// PLT indirection).
if (!(isa<cir::FuncOp>(gv) && gv.canBenefitFromLocalAlias()))
return false;
return !(lOpts.SemanticInterposition || lOpts.HalfNoSemanticInterposition);
}
// A definition cannot be preempted from an executable.
if (!gv.isDeclarationForLinker())
return true;
// Most PIC code sequences that assume that a symbol is local cannot produce a
// 0 if it turns out the symbol is undefined. While this is ABI and relocation
// depended, it seems worth it to handle it here.
if (rm == llvm::Reloc::PIC_ && gv.hasExternalWeakLinkage())
return false;
// PowerPC64 prefers TOC indirection to avoid copy relocations.
if (tt.isPPC64())
return false;
if (cgOpts.DirectAccessExternalData) {
// If -fdirect-access-external-data (default for -fno-pic), set dso_local
// for non-thread-local variables. If the symbol is not defined in the
// executable, a copy relocation will be needed at link time. dso_local is
// excluded for thread-local variables because they generally don't support
// copy relocations.
if (auto globalOp = dyn_cast<cir::GlobalOp>(gv.getOperation())) {
// Assume variables are not thread-local until that support is added.
assert(!cir::MissingFeatures::opGlobalThreadLocal());
return true;
}
// -fno-pic sets dso_local on a function declaration to allow direct
// accesses when taking its address (similar to a data symbol). If the
// function is not defined in the executable, a canonical PLT entry will be
// needed at link time. -fno-direct-access-external-data can avoid the
// canonical PLT entry. We don't generalize this condition to -fpie/-fpic as
// it could just cause trouble without providing perceptible benefits.
if (isa<cir::FuncOp>(gv) && !cgOpts.NoPLT && rm == llvm::Reloc::Static)
return true;
}
// If we can use copy relocations we can assume it is local.
// Otherwise don't assume it is local.
return false;
}
void CIRGenModule::setGlobalVisibility(mlir::Operation *gv,
const NamedDecl *d) const {
assert(!cir::MissingFeatures::opGlobalVisibility());
}
void CIRGenModule::setDSOLocal(cir::CIRGlobalValueInterface gv) const {
gv.setDSOLocal(shouldAssumeDSOLocal(*this, gv));
}
void CIRGenModule::setDSOLocal(mlir::Operation *op) const {
if (auto globalValue = dyn_cast<cir::CIRGlobalValueInterface>(op))
setDSOLocal(globalValue);
}
void CIRGenModule::setGVProperties(mlir::Operation *op,
const NamedDecl *d) const {
assert(!cir::MissingFeatures::opGlobalDLLImportExport());
setGVPropertiesAux(op, d);
}
void CIRGenModule::setGVPropertiesAux(mlir::Operation *op,
const NamedDecl *d) const {
setGlobalVisibility(op, d);
setDSOLocal(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,
mlir::ArrayAttr extraAttrs) {
const Decl *d = gd.getDecl();
if (isThunk)
errorNYI(d->getSourceRange(), "getOrCreateCIRFunction: thunk");
// In what follows, we continue past 'errorNYI' as if nothing happened because
// the rest of the implementation is better than doing nothing.
if (const auto *fd = cast_or_null<FunctionDecl>(d)) {
// For the device mark the function as one that should be emitted.
if (getLangOpts().OpenMPIsTargetDevice && fd->isDefined() && !dontDefer &&
!isForDefinition)
errorNYI(fd->getSourceRange(),
"getOrCreateCIRFunction: OpenMP target function");
// Any attempts to use a MultiVersion function should result in retrieving
// the iFunc instead. Name mangling will handle the rest of the changes.
if (fd->isMultiVersion())
errorNYI(fd->getSourceRange(), "getOrCreateCIRFunction: multi-version");
}
// Lookup the entry, lazily creating it if necessary.
mlir::Operation *entry = getGlobalValue(mangledName);
if (entry) {
assert(mlir::isa<cir::FuncOp>(entry));
assert(!cir::MissingFeatures::weakRefReference());
// Handle dropped DLL attributes.
if (d && !d->hasAttr<DLLImportAttr>() && !d->hasAttr<DLLExportAttr>()) {
assert(!cir::MissingFeatures::setDLLStorageClass());
setDSOLocal(entry);
}
// If there are two attempts to define the same mangled name, issue an
// error.
auto fn = cast<cir::FuncOp>(entry);
if (isForDefinition && fn && !fn.isDeclaration()) {
errorNYI(d->getSourceRange(), "Duplicate function definition");
}
if (fn && fn.getFunctionType() == funcType) {
return fn;
}
if (!isForDefinition) {
return fn;
}
// TODO(cir): classic codegen checks here if this is a llvm::GlobalAlias.
// How will we support this?
}
auto *funcDecl = llvm::cast_or_null<FunctionDecl>(gd.getDecl());
bool invalidLoc = !funcDecl ||
funcDecl->getSourceRange().getBegin().isInvalid() ||
funcDecl->getSourceRange().getEnd().isInvalid();
cir::FuncOp funcOp = createCIRFunction(
invalidLoc ? theModule->getLoc() : getLoc(funcDecl->getSourceRange()),
mangledName, mlir::cast<cir::FuncType>(funcType), funcDecl);
// If we already created a function with the same mangled name (but different
// type) before, take its name and add it to the list of functions to be
// replaced with F at the end of CodeGen.
//
// This happens if there is a prototype for a function (e.g. "int f()") and
// then a definition of a different type (e.g. "int f(int x)").
if (entry) {
// Fetch a generic symbol-defining operation and its uses.
auto symbolOp = mlir::cast<mlir::SymbolOpInterface>(entry);
// This might be an implementation of a function without a prototype, in
// which case, try to do special replacement of calls which match the new
// prototype. The really key thing here is that we also potentially drop
// arguments from the call site so as to make a direct call, which makes the
// inliner happier and suppresses a number of optimizer warnings (!) about
// dropping arguments.
if (symbolOp.getSymbolUses(symbolOp->getParentOp()))
replaceUsesOfNonProtoTypeWithRealFunction(entry, funcOp);
// Obliterate no-proto declaration.
entry->erase();
}
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
// support incomplete functions.
assert(funcOp.getFunctionType() == funcType);
return funcOp;
}
// All MSVC dtors other than the base dtor are linkonce_odr and delegate to
// each other bottoming out wiht the base dtor. Therefore we emit non-base
// dtors on usage, even if there is no dtor definition in the TU.
if (isa_and_nonnull<CXXDestructorDecl>(d) &&
getCXXABI().useThunkForDtorVariant(cast<CXXDestructorDecl>(d),
gd.getDtorType()))
errorNYI(d->getSourceRange(), "getOrCreateCIRFunction: dtor");
// This is the first use or definition of a mangled name. If there is a
// deferred decl with this name, remember that we need to emit it at the end
// of the file.
auto ddi = deferredDecls.find(mangledName);
if (ddi != deferredDecls.end()) {
// Move the potentially referenced deferred decl to the
// DeferredDeclsToEmit list, and remove it from DeferredDecls (since we
// don't need it anymore).
addDeferredDeclToEmit(ddi->second);
deferredDecls.erase(ddi);
// Otherwise, there are cases we have to worry about where we're using a
// declaration for which we must emit a definition but where we might not
// find a top-level definition.
// - member functions defined inline in their classes
// - friend functions defined inline in some class
// - special member functions with implicit definitions
// If we ever change our AST traversal to walk into class methods, this
// will be unnecessary.
//
// We also don't emit a definition for a function if it's going to be an
// entry in a vtable, unless it's already marked as used.
} else if (getLangOpts().CPlusPlus && d) {
// Look for a declaration that's lexically in a record.
for (const auto *fd = cast<FunctionDecl>(d)->getMostRecentDecl(); fd;
fd = fd->getPreviousDecl()) {
if (isa<CXXRecordDecl>(fd->getLexicalDeclContext())) {
if (fd->doesThisDeclarationHaveABody()) {
addDeferredDeclToEmit(gd.getWithDecl(fd));
break;
}
}
}
}
return funcOp;
}
cir::FuncOp
CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name,
cir::FuncType funcType,
const clang::FunctionDecl *funcDecl) {
cir::FuncOp func;
{
mlir::OpBuilder::InsertionGuard guard(builder);
// Some global emissions are triggered while emitting a function, e.g.
// void s() { x.method() }
//
// Be sure to insert a new function before a current one.
CIRGenFunction *cgf = this->curCGF;
if (cgf)
builder.setInsertionPoint(cgf->curFn);
func = builder.create<cir::FuncOp>(loc, name, funcType);
assert(!cir::MissingFeatures::opFuncAstDeclAttr());
if (funcDecl && !funcDecl->hasPrototype())
func.setNoProto(true);
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);
}
return func;
}
mlir::SymbolTable::Visibility
CIRGenModule::getMLIRVisibility(cir::GlobalOp op) {
// MLIR doesn't accept public symbols declarations (only
// definitions).
if (op.isDeclaration())
return mlir::SymbolTable::Visibility::Private;
return getMLIRVisibilityFromCIRLinkage(op.getLinkage());
}
mlir::SymbolTable::Visibility
CIRGenModule::getMLIRVisibilityFromCIRLinkage(cir::GlobalLinkageKind glk) {
switch (glk) {
case cir::GlobalLinkageKind::InternalLinkage:
case cir::GlobalLinkageKind::PrivateLinkage:
return mlir::SymbolTable::Visibility::Private;
case cir::GlobalLinkageKind::ExternalLinkage:
case cir::GlobalLinkageKind::ExternalWeakLinkage:
case cir::GlobalLinkageKind::LinkOnceODRLinkage:
case cir::GlobalLinkageKind::AvailableExternallyLinkage:
case cir::GlobalLinkageKind::CommonLinkage:
case cir::GlobalLinkageKind::WeakAnyLinkage:
case cir::GlobalLinkageKind::WeakODRLinkage:
return mlir::SymbolTable::Visibility::Public;
default: {
llvm::errs() << "visibility not implemented for '"
<< stringifyGlobalLinkageKind(glk) << "'\n";
assert(0 && "not implemented");
}
}
llvm_unreachable("linkage should be handled above!");
}
cir::VisibilityKind CIRGenModule::getGlobalVisibilityKindFromClangVisibility(
clang::VisibilityAttr::VisibilityType visibility) {
switch (visibility) {
case clang::VisibilityAttr::VisibilityType::Default:
return cir::VisibilityKind::Default;
case clang::VisibilityAttr::VisibilityType::Hidden:
return cir::VisibilityKind::Hidden;
case clang::VisibilityAttr::VisibilityType::Protected:
return cir::VisibilityKind::Protected;
}
llvm_unreachable("unexpected visibility value");
}
cir::VisibilityAttr
CIRGenModule::getGlobalVisibilityAttrFromDecl(const Decl *decl) {
const clang::VisibilityAttr *va = decl->getAttr<clang::VisibilityAttr>();
cir::VisibilityAttr cirVisibility =
cir::VisibilityAttr::get(&getMLIRContext());
if (va) {
cirVisibility = cir::VisibilityAttr::get(
&getMLIRContext(),
getGlobalVisibilityKindFromClangVisibility(va->getVisibility()));
}
return cirVisibility;
}
void CIRGenModule::release() {
emitDeferred();
applyReplacements();
theModule->setAttr(cir::CIRDialect::getModuleLevelAsmAttrName(),
builder.getArrayAttr(globalScopeAsm));
// There's a lot of code that is not implemented yet.
assert(!cir::MissingFeatures::cgmRelease());
}
void CIRGenModule::emitAliasForGlobal(StringRef mangledName,
mlir::Operation *op, GlobalDecl aliasGD,
cir::FuncOp aliasee,
cir::GlobalLinkageKind linkage) {
auto *aliasFD = dyn_cast<FunctionDecl>(aliasGD.getDecl());
assert(aliasFD && "expected FunctionDecl");
// The aliasee function type is different from the alias one, this difference
// is specific to CIR because in LLVM the ptr types are already erased at this
// point.
const CIRGenFunctionInfo &fnInfo =
getTypes().arrangeCXXStructorDeclaration(aliasGD);
cir::FuncType fnType = getTypes().getFunctionType(fnInfo);
cir::FuncOp alias =
createCIRFunction(getLoc(aliasGD.getDecl()->getSourceRange()),
mangledName, fnType, aliasFD);
alias.setAliasee(aliasee.getName());
alias.setLinkage(linkage);
// Declarations cannot have public MLIR visibility, just mark them private
// but this really should have no meaning since CIR should not be using
// this information to derive linkage information.
mlir::SymbolTable::setSymbolVisibility(
alias, mlir::SymbolTable::Visibility::Private);
// Alias constructors and destructors are always unnamed_addr.
assert(!cir::MissingFeatures::opGlobalUnnamedAddr());
// Switch any previous uses to the alias.
if (op) {
errorNYI(aliasFD->getSourceRange(), "emitAliasForGlobal: previous uses");
} else {
// Name already set by createCIRFunction
}
// Finally, set up the alias with its proper name and attributes.
setCommonAttributes(aliasGD, alias);
}
mlir::Type CIRGenModule::convertType(QualType type) {
return genTypes.convertType(type);
}
bool CIRGenModule::verifyModule() const {
// Verify the module after we have finished constructing it, this will
// check the structural properties of the IR and invoke any specific
// verifiers we have on the CIR operations.
return mlir::verify(theModule).succeeded();
}
// TODO(cir): this can be shared with LLVM codegen.
CharUnits CIRGenModule::computeNonVirtualBaseClassOffset(
const CXXRecordDecl *derivedClass,
llvm::iterator_range<CastExpr::path_const_iterator> path) {
CharUnits offset = CharUnits::Zero();
const ASTContext &astContext = getASTContext();
const CXXRecordDecl *rd = derivedClass;
for (const CXXBaseSpecifier *base : path) {
assert(!base->isVirtual() && "Should not see virtual bases here!");
// Get the layout.
const ASTRecordLayout &layout = astContext.getASTRecordLayout(rd);
const auto *baseDecl =
cast<CXXRecordDecl>(
base->getType()->castAs<clang::RecordType>()->getOriginalDecl())
->getDefinitionOrSelf();
// Add the offset.
offset += layout.getBaseClassOffset(baseDecl);
rd = baseDecl;
}
return offset;
}
DiagnosticBuilder CIRGenModule::errorNYI(SourceLocation loc,
llvm::StringRef feature) {
unsigned diagID = diags.getCustomDiagID(
DiagnosticsEngine::Error, "ClangIR code gen Not Yet Implemented: %0");
return diags.Report(loc, diagID) << feature;
}
DiagnosticBuilder CIRGenModule::errorNYI(SourceRange loc,
llvm::StringRef feature) {
return errorNYI(loc.getBegin(), feature) << loc;
}