llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clangir Author: Andy Kaylor (andykaylor) <details> <summary>Changes</summary> This change adds support for handling the -mconstructor-aliases option in CIR. Aliases are not yet correctly lowered to LLVM IR. That will be implemented in a future change. --- Full diff: https://github.com/llvm/llvm-project/pull/145792.diff 7 Files Affected: - (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+2-1) - (modified) clang/include/clang/CIR/MissingFeatures.h (-1) - (modified) clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp (+87-2) - (modified) clang/lib/CIR/CodeGen/CIRGenModule.cpp (+104) - (modified) clang/lib/CIR/CodeGen/CIRGenModule.h (+17) - (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+15-5) - (added) clang/test/CIR/CodeGen/ctor-alias.cpp (+75) ``````````diff diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index ef77c46b011f7..7e45fa464f9d4 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -1772,7 +1772,8 @@ def FuncOp : CIR_Op<"func", [ OptionalAttr<StrAttr>:$sym_visibility, UnitAttr:$comdat, OptionalAttr<DictArrayAttr>:$arg_attrs, - OptionalAttr<DictArrayAttr>:$res_attrs); + OptionalAttr<DictArrayAttr>:$res_attrs, + OptionalAttr<FlatSymbolRefAttr>:$aliasee); let regions = (region AnyRegion:$body); diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 9e8944d1114b8..7009d6d7d702a 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -81,7 +81,6 @@ struct MissingFeatures { 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; } diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp index cd9096a0188a7..1044dfe507471 100644 --- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -79,17 +79,102 @@ void CIRGenItaniumCXXABI::emitInstanceFunctionProlog(SourceLocation loc, } } +// Find out how to cirgen the complete destructor and constructor +namespace { +enum class StructorCIRGen { Emit, RAUW, Alias, COMDAT }; +} + +static StructorCIRGen getCIRGenToUse(CIRGenModule &cgm, + const CXXMethodDecl *md) { + if (!cgm.getCodeGenOpts().CXXCtorDtorAliases) + return StructorCIRGen::Emit; + + // The complete and base structors are not equivalent if there are any virtual + // bases, so emit separate functions. + if (md->getParent()->getNumVBases()) { + // The return value is correct here, but other support for this is NYI. + cgm.errorNYI(md->getSourceRange(), "getCIRGenToUse: virtual bases"); + return StructorCIRGen::Emit; + } + + GlobalDecl aliasDecl; + if (const auto *dd = dyn_cast<CXXDestructorDecl>(md)) { + // The assignment is correct here, but other support for this is NYI. + cgm.errorNYI(md->getSourceRange(), "getCIRGenToUse: dtor"); + aliasDecl = GlobalDecl(dd, Dtor_Complete); + } else { + const auto *cd = cast<CXXConstructorDecl>(md); + aliasDecl = GlobalDecl(cd, Ctor_Complete); + } + + cir::GlobalLinkageKind linkage = cgm.getFunctionLinkage(aliasDecl); + + if (cir::isDiscardableIfUnused(linkage)) + return StructorCIRGen::RAUW; + + // FIXME: Should we allow available_externally aliases? + if (!cir::isValidLinkage(linkage)) + return StructorCIRGen::RAUW; + + if (cir::isWeakForLinker(linkage)) { + // Only ELF and wasm support COMDATs with arbitrary names (C5/D5). + if (cgm.getTarget().getTriple().isOSBinFormatELF() || + cgm.getTarget().getTriple().isOSBinFormatWasm()) + return StructorCIRGen::COMDAT; + return StructorCIRGen::Emit; + } + + return StructorCIRGen::Alias; +} + +static void emitConstructorDestructorAlias(CIRGenModule &cgm, + GlobalDecl aliasDecl, + GlobalDecl targetDecl) { + cir::GlobalLinkageKind linkage = cgm.getFunctionLinkage(aliasDecl); + + // Does this function alias already exists? + StringRef mangledName = cgm.getMangledName(aliasDecl); + auto globalValue = dyn_cast_or_null<cir::CIRGlobalValueInterface>( + cgm.getGlobalValue(mangledName)); + if (globalValue && !globalValue.isDeclaration()) + return; + + auto entry = cast_or_null<cir::FuncOp>(cgm.getGlobalValue(mangledName)); + + // Retrieve aliasee info. + auto aliasee = cast<cir::FuncOp>(cgm.getAddrOfGlobal(targetDecl)); + + // Populate actual alias. + cgm.emitAliasForGlobal(mangledName, entry, aliasDecl, aliasee, linkage); +} + void CIRGenItaniumCXXABI::emitCXXStructor(GlobalDecl gd) { auto *md = cast<CXXMethodDecl>(gd.getDecl()); auto *cd = dyn_cast<CXXConstructorDecl>(md); + StructorCIRGen cirGenType = getCIRGenToUse(cgm, md); + if (!cd) { cgm.errorNYI(md->getSourceRange(), "CXCABI emit destructor"); return; } - if (cgm.getCodeGenOpts().CXXCtorDtorAliases) - cgm.errorNYI(md->getSourceRange(), "Ctor/Dtor aliases"); + if (gd.getCtorType() == Ctor_Complete) { + GlobalDecl baseDecl = gd.getWithCtorType(Ctor_Base); + + if (cirGenType == StructorCIRGen::Alias || + cirGenType == StructorCIRGen::COMDAT) { + emitConstructorDestructorAlias(cgm, gd, baseDecl); + return; + } + + if (cirGenType == StructorCIRGen::RAUW) { + StringRef mangledName = cgm.getMangledName(gd); + mlir::Operation *aliasee = cgm.getAddrOfGlobal(baseDecl); + cgm.addReplacement(mangledName, aliasee); + return; + } + } auto fn = cgm.codegenCXXStructor(gd); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index f24bee44f26a7..1271f290d6193 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -888,6 +888,69 @@ void CIRGenModule::updateCompletedType(const TagDecl *td) { 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; + + mlir::OperandRange argOps = call.getArgs(); + mlir::ArrayRef<mlir::Type> funcArgTypes = + newF.getFunctionType().getInputs(); + // In the case of variadic functions, the call may have more arguments that + // the function type, so we can't use llvm::enumerate here. + for (unsigned i = 0; i < funcArgTypes.size(); i++) { + if (argOps[i].getType() == funcArgTypes[i]) + 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(); + } + } +} + // TODO(CIR): this could be a common method between LLVM codegen. static bool isVarDeclStrongDefinition(const ASTContext &astContext, CIRGenModule &cgm, const VarDecl *vd, @@ -1797,11 +1860,52 @@ CIRGenModule::getGlobalVisibilityAttrFromDecl(const Decl *decl) { void CIRGenModule::release() { emitDeferred(); + applyReplacements(); // 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); } diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 9f6a57c31d291..5a1eb9dea35c6 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -256,6 +256,10 @@ class CIRGenModule : public CIRGenTypeCache { /// declarations are emitted lazily. void emitGlobal(clang::GlobalDecl gd); + void emitAliasForGlobal(llvm::StringRef mangledName, mlir::Operation *op, + GlobalDecl aliasGD, cir::FuncOp aliasee, + cir::GlobalLinkageKind linkage); + mlir::Type convertType(clang::QualType type); /// Set the visibility for the given global. @@ -358,6 +362,8 @@ class CIRGenModule : public CIRGenTypeCache { cir::GlobalLinkageKind getCIRLinkageVarDefinition(const VarDecl *vd, bool isConstant); + void addReplacement(llvm::StringRef name, mlir::Operation *op); + /// Helpers to emit "not yet implemented" error diagnostics DiagnosticBuilder errorNYI(SourceLocation, llvm::StringRef); @@ -397,6 +403,17 @@ class CIRGenModule : public CIRGenTypeCache { llvm::MapVector<clang::GlobalDecl, llvm::StringRef> mangledDeclNames; llvm::StringMap<clang::GlobalDecl, llvm::BumpPtrAllocator> manglings; + // FIXME: should we use llvm::TrackingVH<mlir::Operation> here? + typedef llvm::StringMap<mlir::Operation *> ReplacementsTy; + ReplacementsTy replacements; + /// Call replaceAllUsesWith on all pairs in replacements. + void applyReplacements(); + + /// A helper function to replace all uses of OldF to NewF that replace + /// the type of pointer arguments. This is not needed to tradtional + /// pipeline since LLVM has opaque pointers but CIR not. + void replacePointerTypeArgs(cir::FuncOp oldF, cir::FuncOp newF); + void setNonAliasAttributes(GlobalDecl gd, mlir::Operation *op); }; } // namespace CIRGen diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 17157561357f9..d14fb6af5a988 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1419,13 +1419,17 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) { } // This function corresponds to `llvm::GlobalValue::isDeclaration` and should -// have a similar implementation. We don't currently support aliases, ifuncs, -// or materializable functions, but those should be handled here as they are -// implemented. +// have a similar implementation. We don't currently ifuncs or materializable +// functions, but those should be handled here as they are implemented. bool cir::FuncOp::isDeclaration() { - assert(!cir::MissingFeatures::opFuncGlobalAliases()); assert(!cir::MissingFeatures::supportIFuncAttr()); - return getFunctionBody().empty(); + + std::optional<StringRef> aliasee = getAliasee(); + if (!aliasee) + return getFunctionBody().empty(); + + // Aliases are always definitions. + return false; } mlir::Region *cir::FuncOp::getCallableRegion() { @@ -1460,6 +1464,12 @@ void cir::FuncOp::print(OpAsmPrinter &p) { function_interface_impl::printFunctionSignature( p, *this, fnType.getInputs(), fnType.isVarArg(), fnType.getReturnTypes()); + if (std::optional<StringRef> aliaseeName = getAliasee()) { + p << " alias("; + p.printSymbolName(*aliaseeName); + p << ")"; + } + // Print the body if this is not an external function. Region &body = getOperation()->getRegion(0); if (!body.empty()) { diff --git a/clang/test/CIR/CodeGen/ctor-alias.cpp b/clang/test/CIR/CodeGen/ctor-alias.cpp new file mode 100644 index 0000000000000..a20e206c6d714 --- /dev/null +++ b/clang/test/CIR/CodeGen/ctor-alias.cpp @@ -0,0 +1,75 @@ +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s + +struct B { + B(); +}; +B::B() { +} + +// OGCG: @_ZN1BC1Ev = unnamed_addr alias void (ptr), ptr @_ZN1BC2Ev + +// CHECK: cir.func{{.*}} @_ZN1BC2Ev(%arg0: !cir.ptr<!rec_B> +// CHECK: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!rec_B>>, ["this", init] +// CHECK: cir.store %arg0, %[[THIS_ADDR]] +// CHECK: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] : !cir.ptr<!cir.ptr<!rec_B>>, !cir.ptr<!rec_B> + +// CHECK: cir.func{{.*}} private dso_local @_ZN1BC1Ev(!cir.ptr<!rec_B>) alias(@_ZN1BC2Ev) + +// LLVM: define{{.*}} @_ZN1BC2Ev(ptr %[[THIS_ARG:.*]]) +// LLVM: %[[THIS_ADDR:.*]] = alloca ptr +// LLVM: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]] +// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]] + +// This should be an alias, like the similar OGCG alias above, but that's not +// implemented yet. +// LLVM: declare dso_local void @_ZN1BC1Ev(ptr) + +// OGCG: define{{.*}} @_ZN1BC2Ev(ptr{{.*}} %[[THIS_ARG:.*]]) +// OGCG: %[[THIS_ADDR:.*]] = alloca ptr +// OGCG: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]] +// OGCG: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]] + +// The constructor in this cases is handled by RAUW rather than aliasing. +struct Struk { + Struk() {} +}; + +void baz() { + Struk s; +} + +// CHECK: cir.func{{.*}} @_ZN5StrukC2Ev(%arg0: !cir.ptr<!rec_Struk> +// CHECK: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>>, ["this", init] +// CHECK: cir.store %arg0, %[[THIS_ADDR]] : !cir.ptr<!rec_Struk>, !cir.ptr<!cir.ptr<!rec_Struk>> +// CHECK: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] : !cir.ptr<!cir.ptr<!rec_Struk>>, !cir.ptr<!rec_Struk> +// CHECK: cir.return + +// CHECK-NOT: cir.func{{.*}} @_ZN5StrukC1Ev + +// CHECK: cir.func{{.*}} @_Z3bazv() +// CHECK: %[[S_ADDR:.*]] = cir.alloca !rec_Struk, !cir.ptr<!rec_Struk>, ["s", init] +// CHECK: cir.call @_ZN5StrukC2Ev(%[[S_ADDR]]) : (!cir.ptr<!rec_Struk>) -> () + +// LLVM: define linkonce_odr void @_ZN5StrukC2Ev(ptr %[[THIS_ARG]]) +// LLVM: %[[THIS_ADDR:.*]] = alloca ptr +// LLVM: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]] +// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]] + +// LLVM: define{{.*}} void @_Z3bazv() +// LLVM: %[[S_ADDR:.*]] = alloca %struct.Struk +// LLVM: call void @_ZN5StrukC2Ev(ptr{{.*}} %[[S_ADDR]]) + +// This function gets emitted before the constructor in OGCG. +// OGCG: define{{.*}} void @_Z3bazv() +// OGCG: %[[S_ADDR:.*]] = alloca %struct.Struk +// OGCG: call void @_ZN5StrukC2Ev(ptr{{.*}} %[[S_ADDR]]) + +// OGCG: define linkonce_odr void @_ZN5StrukC2Ev(ptr{{.*}} %[[THIS_ARG]]) +// OGCG: %[[THIS_ADDR:.*]] = alloca ptr +// OGCG: store ptr %[[THIS_ARG]], ptr %[[THIS_ADDR]] +// OGCG: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]] `````````` </details> https://github.com/llvm/llvm-project/pull/145792 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits