Skip to content

[CIR] Support inline C++ member function definitions #142484

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 3, 2025

Conversation

andykaylor
Copy link
Contributor

This change upstreams the code to support emitting inline C++ function definitions, including the ASTConsumer handler for inline definitions and the code to load the 'this' pointer.

This necessitates introducing the Itanium CXXABI class. No other CXXABI subclasses are supported at this time. The Itanium CXXABI is used for AppleARM64, which will require its own handler for a few special cases (such as array cookies) later.

This change upstreams the code to support emitting inline C++ function
definitions, including the ASTConsumer handler for inline definitions
and the code to load the 'this' pointer.

This necessitates introducing the Itanium CXXABI class. No other CXXABI
subclasses are supported at this time. The Itanium CXXABI is used for
AppleARM64, which will require its own handler for a few special cases
(such as array cookies) later.
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Jun 2, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 2, 2025

@llvm/pr-subscribers-clangir

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

Changes

This change upstreams the code to support emitting inline C++ function definitions, including the ASTConsumer handler for inline definitions and the code to load the 'this' pointer.

This necessitates introducing the Itanium CXXABI class. No other CXXABI subclasses are supported at this time. The Itanium CXXABI is used for AppleARM64, which will require its own handler for a few special cases (such as array cookies) later.


Patch is 24.20 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/142484.diff

14 Files Affected:

  • (modified) clang/include/clang/CIR/CIRGenerator.h (+25)
  • (modified) clang/include/clang/CIR/MissingFeatures.h (+9-4)
  • (modified) clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp (+17-6)
  • (modified) clang/lib/CIR/CodeGen/CIRGenCXXABI.h (+14)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+2)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+21-1)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+25)
  • (added) clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp (+90)
  • (modified) clang/lib/CIR/CodeGen/CIRGenModule.cpp (-8)
  • (modified) clang/lib/CIR/CodeGen/CIRGenerator.cpp (+44-2)
  • (modified) clang/lib/CIR/CodeGen/CMakeLists.txt (+1)
  • (modified) clang/lib/CIR/FrontendAction/CIRGenAction.cpp (+4)
  • (added) clang/test/CIR/CodeGen/inline-cxx-func.cpp (+72)
  • (modified) clang/test/CIR/CodeGen/member-functions.cpp (+4-2)
diff --git a/clang/include/clang/CIR/CIRGenerator.h b/clang/include/clang/CIR/CIRGenerator.h
index bb20fdf72693b..8e49371e99020 100644
--- a/clang/include/clang/CIR/CIRGenerator.h
+++ b/clang/include/clang/CIR/CIRGenerator.h
@@ -43,10 +43,32 @@ class CIRGenerator : public clang::ASTConsumer {
 
   const clang::CodeGenOptions &codeGenOpts;
 
+  unsigned handlingTopLevelDecls;
+
+  /// Use this when emitting decls to block re-entrant decl emission. It will
+  /// emit all deferred decls on scope exit. Set EmitDeferred to false if decl
+  /// emission must be deferred longer, like at the end of a tag definition.
+  struct HandlingTopLevelDeclRAII {
+    CIRGenerator &self;
+    bool emitDeferred;
+    HandlingTopLevelDeclRAII(CIRGenerator &self, bool emitDeferred = true)
+        : self{self}, emitDeferred{emitDeferred} {
+      ++self.handlingTopLevelDecls;
+    }
+    ~HandlingTopLevelDeclRAII() {
+      unsigned Level = --self.handlingTopLevelDecls;
+      if (Level == 0 && emitDeferred)
+        self.emitDeferredDecls();
+    }
+  };
+
 protected:
   std::unique_ptr<mlir::MLIRContext> mlirContext;
   std::unique_ptr<clang::CIRGen::CIRGenModule> cgm;
 
+private:
+  llvm::SmallVector<clang::FunctionDecl *, 8> deferredInlineMemberFuncDefs;
+
 public:
   CIRGenerator(clang::DiagnosticsEngine &diags,
                llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
@@ -54,6 +76,7 @@ class CIRGenerator : public clang::ASTConsumer {
   ~CIRGenerator() override;
   void Initialize(clang::ASTContext &astContext) override;
   bool HandleTopLevelDecl(clang::DeclGroupRef group) override;
+  void HandleInlineFunctionDefinition(clang::FunctionDecl *d) override;
   void CompleteTentativeDefinition(clang::VarDecl *d) override;
 
   mlir::ModuleOp getModule() const;
@@ -61,6 +84,8 @@ class CIRGenerator : public clang::ASTConsumer {
   const mlir::MLIRContext &getMLIRContext() const { return *mlirContext; };
 
   bool verifyModule() const;
+
+  void emitDeferredDecls();
 };
 
 } // namespace cir
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 3b82de882953c..65978e51a23e9 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -137,8 +137,15 @@ struct MissingFeatures {
   static bool recordZeroInit() { return false; }
   static bool zeroSizeRecordMembers() { return false; }
 
-  // Misc
+  // CXXABI
   static bool cxxABI() { return false; }
+  static bool cxxabiThisAlignment() { return false; }
+  static bool cxxabiUseARMMethodPtrABI() { return false; }
+  static bool cxxabiUseARMGuardVarABI() { return false; }
+  static bool cxxabiAppleARM64CXXABI() { return false; }
+  static bool cxxabiStructorImplicitParam() { return false; }
+
+  // Misc
   static bool cirgenABIInfo() { return false; }
   static bool abiArgInfo() { return false; }
   static bool tryEmitAsConstant() { return false; }
@@ -187,7 +194,6 @@ struct MissingFeatures {
   static bool typeChecks() { return false; }
   static bool lambdaFieldToName() { return false; }
   static bool updateCompletedType() { return false; }
-  static bool targetSpecificCXXABI() { return false; }
   static bool moduleNameHash() { return false; }
   static bool constantFoldSwitchStatement() { return false; }
   static bool cudaSupport() { return false; }
@@ -196,13 +202,12 @@ struct MissingFeatures {
   static bool constEmitterVectorILE() { return false; }
   static bool needsGlobalCtorDtor() { return false; }
   static bool emitTypeCheck() { return false; }
-  static bool cxxabiThisDecl() { return false; }
-  static bool cxxabiThisAlignment() { return false; }
   static bool writebacks() { return false; }
   static bool cleanupsToDeactivate() { return false; }
   static bool stackBase() { return false; }
   static bool deferredDecls() { return false; }
   static bool setTargetAttributes() { return false; }
+  static bool coverageMapping() { return false; }
 
   // Missing types
   static bool dataMemberType() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp
index e368ccd006bb8..6cf4e5c658fb6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp
@@ -33,13 +33,24 @@ void CIRGenCXXABI::buildThisParam(CIRGenFunction &cgf,
                                 &cgm.getASTContext().Idents.get("this"),
                                 md->getThisType(), ImplicitParamKind::CXXThis);
   params.push_back(thisDecl);
-
-  // Classic codegen save thisDecl in CodeGenFunction::CXXABIThisDecl, but it
-  // doesn't seem to be needed in CIRGen.
-  assert(!cir::MissingFeatures::cxxabiThisDecl());
+  cgf.cxxabiThisDecl = thisDecl;
 
   // Classic codegen computes the alignment of thisDecl and saves it in
-  // CodeGenFunction::CXXABIThisAlignment, but it doesn't seem to be needed in
-  // CIRGen.
+  // CodeGenFunction::CXXABIThisAlignment, but it is only used in emitTypeCheck
+  // in CodeGenFunction::StartFunction().
   assert(!cir::MissingFeatures::cxxabiThisAlignment());
 }
+
+mlir::Value CIRGenCXXABI::loadIncomingCXXThis(CIRGenFunction &cgf) {
+  ImplicitParamDecl *vd = getThisDecl(cgf);
+  Address addr = cgf.getAddrOfLocalVar(vd);
+  return cgf.getBuilder().create<cir::LoadOp>(
+      cgf.getLoc(vd->getLocation()), addr.getElementType(), addr.getPointer());
+}
+
+void CIRGenCXXABI::setCXXABIThisValue(CIRGenFunction &cgf,
+                                      mlir::Value thisPtr) {
+  /// Initialize the 'this' slot.
+  assert(getThisDecl(cgf) && "no 'this' variable for function");
+  cgf.cxxabiThisValue = thisPtr;
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index b5c33a29442c7..107535ebc7275 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -15,6 +15,7 @@
 #define LLVM_CLANG_LIB_CIR_CIRGENCXXABI_H
 
 #include "CIRGenCall.h"
+#include "CIRGenFunction.h"
 #include "CIRGenModule.h"
 
 #include "clang/AST/Mangle.h"
@@ -34,7 +35,17 @@ class CIRGenCXXABI {
       : cgm(cgm), mangleContext(cgm.getASTContext().createMangleContext()) {}
   virtual ~CIRGenCXXABI();
 
+  void setCXXABIThisValue(CIRGenFunction &cgf, mlir::Value thisPtr);
+
 public:
+  clang::ImplicitParamDecl *getThisDecl(CIRGenFunction &cgf) {
+    return cgf.cxxabiThisDecl;
+  }
+
+  /// Emit the ABI-specific prolog for the function
+  virtual void emitInstanceFunctionProlog(SourceLocation Loc,
+                                          CIRGenFunction &cgf) = 0;
+
   /// Get the type of the implicit "this" parameter used by a method. May return
   /// zero if no specific type is applicable, e.g. if the ABI expects the "this"
   /// parameter to point to some artificial offset in a complete object due to
@@ -47,6 +58,9 @@ class CIRGenCXXABI {
   /// Build a parameter variable suitable for 'this'.
   void buildThisParam(CIRGenFunction &cgf, FunctionArgList &params);
 
+  /// Loads the incoming C++ this pointer as it was passed by the caller.
+  mlir::Value loadIncomingCXXThis(CIRGenFunction &cgf);
+
   /// Returns true if the given constructor or destructor is one of the kinds
   /// that the ABI says returns 'this' (only applies when called non-virtually
   /// for destructors).
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index b33bb71c99c90..36d52251a0106 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -569,6 +569,8 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
 
   mlir::Value VisitUnaryLNot(const UnaryOperator *e);
 
+  mlir::Value VisitCXXThisExpr(CXXThisExpr *te) { return cgf.loadCXXThis(); }
+
   /// Emit a conversion from the specified type to the specified destination
   /// type, both of which are CIR scalar types.
   /// TODO: do we need ScalarConversionOpts here? Should be done in another
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index bd67a3407d749..b008ee9b472a1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -343,7 +343,9 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
 
   curFn = fn;
 
-  const auto *fd = dyn_cast_or_null<FunctionDecl>(gd.getDecl());
+  const Decl *d = gd.getDecl();
+  const auto *fd = dyn_cast_or_null<FunctionDecl>(d);
+  curFuncDecl = d->getNonClosureContext();
 
   mlir::Block *entryBB = &fn.getBlocks().front();
   builder.setInsertionPointToStart(entryBB);
@@ -385,6 +387,24 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
   if (!returnType->isVoidType())
     emitAndUpdateRetAlloca(returnType, getLoc(fd->getBody()->getEndLoc()),
                            getContext().getTypeAlignInChars(returnType));
+
+  if (isa_and_nonnull<CXXMethodDecl>(d) &&
+      cast<CXXMethodDecl>(d)->isInstance()) {
+    cgm.getCXXABI().emitInstanceFunctionProlog(loc, *this);
+
+    const auto *md = cast<CXXMethodDecl>(d);
+    if (md->getParent()->isLambda() && md->getOverloadedOperator() == OO_Call) {
+      cgm.errorNYI(loc, "lambda call operator");
+    } else {
+      // Not in a lambda; just use 'this' from the method.
+      // FIXME: Should we generate a new load for each use of 'this'? The fast
+      // register allocator would be happier...
+      cxxThisValue = cxxabiThisValue;
+    }
+
+    assert(!cir::MissingFeatures::sanitizers());
+    assert(!cir::MissingFeatures::emitTypeCheck());
+  }
 }
 
 void CIRGenFunction::finishFunction(SourceLocation endLoc) {}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 0badde024b166..ef61aa7f4ee6d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -55,6 +55,15 @@ class CIRGenFunction : public CIRGenTypeCache {
   /// The compiler-generated variable that holds the return value.
   std::optional<mlir::Value> fnRetAlloca;
 
+  /// CXXThisDecl - When generating code for a C++ member function,
+  /// this will hold the implicit 'this' declaration.
+  ImplicitParamDecl *cxxabiThisDecl = nullptr;
+  mlir::Value cxxabiThisValue = nullptr;
+  mlir::Value cxxThisValue = nullptr;
+
+  // Holds the Decl for the current outermost non-closure context
+  const clang::Decl *curFuncDecl = nullptr;
+
   /// The function for which code is currently being generated.
   cir::FuncOp curFn;
 
@@ -307,6 +316,22 @@ class CIRGenFunction : public CIRGenTypeCache {
     return LValue::makeAddr(addr, ty, baseInfo);
   }
 
+  /// Return the address of a local variable.
+  Address getAddrOfLocalVar(const clang::VarDecl *vd) {
+    auto it = localDeclMap.find(vd);
+    assert(it != localDeclMap.end() &&
+           "Invalid argument to getAddrOfLocalVar(), no decl!");
+    return it->second;
+  }
+
+  /// Load the value for 'this'. This function is only valid while generating
+  /// code for an C++ member function.
+  /// FIXME(cir): this should return a mlir::Value!
+  mlir::Value loadCXXThis() {
+    assert(cxxThisValue && "no 'this' value for this function");
+    return cxxThisValue;
+  }
+
   /// Get an appropriate 'undef' rvalue for the given type.
   /// TODO: What's the equivalent for MLIR? Currently we're only using this for
   /// void types so it just returns RValue::get(nullptr) but it'll need
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
new file mode 100644
index 0000000000000..fdd8b63fb6da0
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -0,0 +1,90 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 provides C++ code generation targeting the Itanium C++ ABI.  The class
+// in this file generates structures that follow the Itanium C++ ABI, which is
+// documented at:
+//  https://itanium-cxx-abi.github.io/cxx-abi/abi.html
+//  https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html
+//
+// It also supports the closely-related ARM ABI, documented at:
+// https://developer.arm.com/documentation/ihi0041/g/
+//
+//===----------------------------------------------------------------------===//
+
+#include "CIRGenCXXABI.h"
+#include "CIRGenFunction.h"
+
+#include "clang/AST/GlobalDecl.h"
+#include "llvm/Support/ErrorHandling.h"
+
+using namespace clang;
+using namespace clang::CIRGen;
+
+namespace {
+
+class CIRGenItaniumCXXABI : public CIRGenCXXABI {
+public:
+  CIRGenItaniumCXXABI(CIRGenModule &cgm) : CIRGenCXXABI(cgm) {
+    assert(!cir::MissingFeatures::cxxabiUseARMMethodPtrABI());
+    assert(!cir::MissingFeatures::cxxabiUseARMGuardVarABI());
+  }
+
+  void emitInstanceFunctionProlog(SourceLocation Loc,
+                                  CIRGenFunction &CGF) override;
+};
+
+} // namespace
+
+void CIRGenItaniumCXXABI::emitInstanceFunctionProlog(SourceLocation loc,
+                                                     CIRGenFunction &cgf) {
+  // Naked functions have no prolog.
+  if (cgf.curFuncDecl && cgf.curFuncDecl->hasAttr<NakedAttr>()) {
+    cgf.cgm.errorNYI(cgf.curFuncDecl->getLocation(),
+                     "emitInstanceFunctionProlog: Naked");
+  }
+
+  /// Initialize the 'this' slot. In the Itanium C++ ABI, no prologue
+  /// adjustments are required, because they are all handled by thunks.
+  setCXXABIThisValue(cgf, loadIncomingCXXThis(cgf));
+
+  /// Classic codegen has code here to initialize the 'vtt' slot if
+  // getStructorImplicitParamDecl(cgf) returns a non-null value, but in the
+  // current implementation (of classic codegen) it never does.
+  assert(!cir::MissingFeatures::cxxabiStructorImplicitParam());
+
+  /// If this is a function that the ABI specifies returns 'this', initialize
+  /// the return slot to this' at the start of the function.
+  ///
+  /// Unlike the setting of return types, this is done within the ABI
+  /// implementation instead of by clients of CIRGenCXXBI because:
+  /// 1) getThisValue is currently protected
+  /// 2) in theory, an ABI could implement 'this' returns some other way;
+  ///    HasThisReturn only specifies a contract, not the implementation
+  if (hasThisReturn(cgf.curGD)) {
+    cgf.cgm.errorNYI(cgf.curFuncDecl->getLocation(),
+                     "emitInstanceFunctionProlog: hasThisReturn");
+  }
+}
+
+CIRGenCXXABI *clang::CIRGen::CreateCIRGenItaniumCXXABI(CIRGenModule &cgm) {
+  switch (cgm.getASTContext().getCXXABIKind()) {
+  case TargetCXXABI::GenericItanium:
+  case TargetCXXABI::GenericAArch64:
+    return new CIRGenItaniumCXXABI(cgm);
+
+  case TargetCXXABI::AppleARM64:
+    // The general Itanium ABI will do until we implement something that
+    // requires special handling.
+    assert(!cir::MissingFeatures::cxxabiAppleARM64CXXABI());
+    return new CIRGenItaniumCXXABI(cgm);
+
+  default:
+    llvm_unreachable("bad or NYI ABI kind");
+  }
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 9da7fcc0f89d5..0ec5dcec99281 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -55,14 +55,6 @@ static CIRGenCXXABI *createCXXABI(CIRGenModule &cgm) {
   llvm_unreachable("invalid C++ ABI kind");
 }
 
-namespace clang::CIRGen {
-// TODO(cir): Implement target-specific CIRGenCXXABIs
-CIRGenCXXABI *CreateCIRGenItaniumCXXABI(CIRGenModule &cgm) {
-  assert(!cir::MissingFeatures::targetSpecificCXXABI());
-  return new CIRGenCXXABI(cgm);
-}
-} // namespace clang::CIRGen
-
 CIRGenModule::CIRGenModule(mlir::MLIRContext &mlirContext,
                            clang::ASTContext &astContext,
                            const clang::CodeGenOptions &cgo,
diff --git a/clang/lib/CIR/CodeGen/CIRGenerator.cpp b/clang/lib/CIR/CodeGen/CIRGenerator.cpp
index 726da5b013264..512009affa806 100644
--- a/clang/lib/CIR/CodeGen/CIRGenerator.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenerator.cpp
@@ -28,8 +28,12 @@ void CIRGenerator::anchor() {}
 CIRGenerator::CIRGenerator(clang::DiagnosticsEngine &diags,
                            llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs,
                            const CodeGenOptions &cgo)
-    : diags(diags), fs(std::move(vfs)), codeGenOpts{cgo} {}
-CIRGenerator::~CIRGenerator() = default;
+    : diags(diags), fs(std::move(vfs)), codeGenOpts{cgo},
+      handlingTopLevelDecls{0} {}
+CIRGenerator::~CIRGenerator() {
+  // There should normally not be any leftover inline method definitions.
+  assert(deferredInlineMemberFuncDefs.empty() || diags.hasErrorOccurred());
+}
 
 void CIRGenerator::Initialize(ASTContext &astContext) {
   using namespace llvm;
@@ -57,12 +61,50 @@ bool CIRGenerator::HandleTopLevelDecl(DeclGroupRef group) {
   if (diags.hasUnrecoverableErrorOccurred())
     return true;
 
+  HandlingTopLevelDeclRAII handlingDecl(*this);
+
   for (Decl *decl : group)
     cgm->emitTopLevelDecl(decl);
 
   return true;
 }
 
+void CIRGenerator::HandleInlineFunctionDefinition(FunctionDecl *d) {
+  if (diags.hasErrorOccurred())
+    return;
+
+  assert(d->doesThisDeclarationHaveABody());
+
+  // We may want to emit this definition. However, that decision might be
+  // based on computing the linkage, and we have to defer that in case we are
+  // inside of something that will chagne the method's final linkage, e.g.
+  //   typedef struct {
+  //     void bar();
+  //     void foo() { bar(); }
+  //   } A;
+  deferredInlineMemberFuncDefs.push_back(d);
+
+  // Provide some coverage mapping even for methods that aren't emitted.
+  // Don't do this for templated classes though, as they may not be
+  // instantiable.
+  assert(!cir::MissingFeatures::coverageMapping());
+}
+
+void CIRGenerator::emitDeferredDecls() {
+  if (deferredInlineMemberFuncDefs.empty())
+    return;
+
+  // Emit any deferred inline method definitions. Note that more deferred
+  // methods may be added during this loop, since ASTConsumer callbacks can be
+  // invoked if AST inspection results in declarations being added. Therefore,
+  // we use an index to loop over the deferredInlineMemberFuncDefs rather than
+  // a range.
+  HandlingTopLevelDeclRAII handlingDecls(*this);
+  for (unsigned i = 0; i != deferredInlineMemberFuncDefs.size(); ++i)
+    cgm->emitTopLevelDecl(deferredInlineMemberFuncDefs[i]);
+  deferredInlineMemberFuncDefs.clear();
+}
+
 void CIRGenerator::CompleteTentativeDefinition(VarDecl *d) {
   if (diags.hasErrorOccurred())
     return;
diff --git a/clang/lib/CIR/CodeGen/CMakeLists.txt b/clang/lib/CIR/CodeGen/CMakeLists.txt
index 4c0aa9a3d0a64..734f3cdca399a 100644
--- a/clang/lib/CIR/CodeGen/CMakeLists.txt
+++ b/clang/lib/CIR/CodeGen/CMakeLists.txt
@@ -19,6 +19,7 @@ add_clang_library(clangCIR
   CIRGenExprConstant.cpp
   CIRGenExprScalar.cpp
   CIRGenFunction.cpp
+  CIRGenItaniumCXXABI.cpp
   CIRGenModule.cpp
   CIRGenOpenACCClause.cpp
   CIRGenRecordLayoutBuilder.cpp
diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
index 48ae2a05df700..c19312ae245dd 100644
--- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
+++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
@@ -84,6 +84,10 @@ class CIRGenConsumer : public clang::ASTConsumer {
     return true;
   }
 
+  void HandleInlineFunctionDefinition(FunctionDecl *D) override {
+    Gen->HandleInlineFunctionDefinition(D);
+  }
+
   void HandleTranslationUnit(ASTContext &C) override {
     Gen->HandleTranslationUnit(C);
 
diff --git a/clang/test/CIR/CodeGen/inline-cxx-func.cpp b/clang/test/CIR/CodeGen/inline-cxx-func.cpp
new file mode 100644
index 0000000000000..43019369b21c6
--- /dev/null
+++ b/clang/test/CIR/CodeGen/inline-cxx-func.cpp
@@ -0,0 +1,72 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+struct S {
+  i...
[truncated]

struct S {
int Member;

int InlineFunc() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we have an int InlineFunc2 that shows that it is NOT emitted, because it is not used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, at the moment unused inline functions are emitted. This will need to wait until the code for deferring definitions until first use is upstreamed.

return Member;
}

int OutlineFunc();
Copy link
Collaborator

Choose a reason for hiding this comment

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

This isn't really necessary, right? Unless you want to provide an out-of-line definition to show that it gets emitted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, you're right. This is an artifact from the original test case you sent me.

@andykaylor andykaylor merged commit 19dcec9 into llvm:main Jun 3, 2025
11 checks passed
@andykaylor andykaylor deleted the cir-this-expr branch June 3, 2025 00:13
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jun 3, 2025

LLVM Buildbot has detected a new failure on builder clang-aarch64-sve2-vla running on linaro-g4-02 while building clang at step 7 "ninja check 1".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/198/builds/4960

Here is the relevant piece of the build log for the reference
Step 7 (ninja check 1) failure: stage 1 checked (failure)
******************** TEST 'DataFlowSanitizer-aarch64 :: release_shadow_space.c' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
/home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/./bin/clang  -fsanitize=dataflow   -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/llvm/compiler-rt/test/dfsan/release_shadow_space.c -o /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/runtimes/runtimes-bins/compiler-rt/test/dfsan/AARCH64Config/Output/release_shadow_space.c.tmp &&  /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/runtimes/runtimes-bins/compiler-rt/test/dfsan/AARCH64Config/Output/release_shadow_space.c.tmp # RUN: at line 1
+ /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/./bin/clang -fsanitize=dataflow -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/llvm/compiler-rt/test/dfsan/release_shadow_space.c -o /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/runtimes/runtimes-bins/compiler-rt/test/dfsan/AARCH64Config/Output/release_shadow_space.c.tmp
+ /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/runtimes/runtimes-bins/compiler-rt/test/dfsan/AARCH64Config/Output/release_shadow_space.c.tmp
RSS at start: 2840, after mmap: 105240, after mmap+set label: 207644, after fixed map: 2844, after another mmap+set label: 207644, after munmap: 2844
/home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/./bin/clang  -fsanitize=dataflow   -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta   /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/llvm/compiler-rt/test/dfsan/release_shadow_space.c -o /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/runtimes/runtimes-bins/compiler-rt/test/dfsan/AARCH64Config/Output/release_shadow_space.c.tmp &&  /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/runtimes/runtimes-bins/compiler-rt/test/dfsan/AARCH64Config/Output/release_shadow_space.c.tmp # RUN: at line 2
+ /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/./bin/clang -fsanitize=dataflow -Wthread-safety -Wthread-safety-reference -Wthread-safety-beta /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/llvm/compiler-rt/test/dfsan/release_shadow_space.c -o /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/runtimes/runtimes-bins/compiler-rt/test/dfsan/AARCH64Config/Output/release_shadow_space.c.tmp
+ /home/tcwg-buildbot/worker/clang-aarch64-sve2-vla/stage1/runtimes/runtimes-bins/compiler-rt/test/dfsan/AARCH64Config/Output/release_shadow_space.c.tmp
DataflowSanitizer: CHECK failed: sanitizer_libc.cpp:299 "((size)) <= ((1ULL << (40)))" (0xffffe00006400000, 0x10000000000) (tid=1767609)

--

********************


@llvm-ci
Copy link
Collaborator

llvm-ci commented Jun 3, 2025

LLVM Buildbot has detected a new failure on builder clang-ppc64-aix running on aix-ppc64 while building clang at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/64/builds/4002

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'lit :: timeout-hang.py' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 13
not env -u FILECHECK_OPTS "/home/llvm/llvm-external-buildbots/workers/env/bin/python3.11" /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/timeout-hang/run-nonexistent.txt  --timeout=1 --param external=0 | "/home/llvm/llvm-external-buildbots/workers/env/bin/python3.11" /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/utils/lit/tests/timeout-hang.py 1
# executed command: not env -u FILECHECK_OPTS /home/llvm/llvm-external-buildbots/workers/env/bin/python3.11 /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/llvm-project/llvm/utils/lit/lit.py -j1 --order=lexical Inputs/timeout-hang/run-nonexistent.txt --timeout=1 --param external=0
# .---command stderr------------
# | lit.py: /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/llvm-project/llvm/utils/lit/lit/main.py:73: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 1 seconds was requested on the command line. Forcing timeout to be 1 seconds.
# `-----------------------------
# executed command: /home/llvm/llvm-external-buildbots/workers/env/bin/python3.11 /home/llvm/llvm-external-buildbots/workers/aix-ppc64/clang-ppc64-aix/build/utils/lit/tests/timeout-hang.py 1
# .---command stdout------------
# | Testing took as long or longer than timeout
# `-----------------------------
# error: command failed with exit status: 1

--

********************


sallto pushed a commit to sallto/llvm-project that referenced this pull request Jun 3, 2025
This change upstreams the code to support emitting inline C++ function
definitions, including the ASTConsumer handler for inline definitions
and the code to load the 'this' pointer.

This necessitates introducing the Itanium CXXABI class. No other CXXABI
subclasses are supported at this time. The Itanium CXXABI is used for
AppleARM64, which will require its own handler for a few special cases
(such as array cookies) later.
rorth pushed a commit to rorth/llvm-project that referenced this pull request Jun 11, 2025
This change upstreams the code to support emitting inline C++ function
definitions, including the ASTConsumer handler for inline definitions
and the code to load the 'this' pointer.

This necessitates introducing the Itanium CXXABI class. No other CXXABI
subclasses are supported at this time. The Itanium CXXABI is used for
AppleARM64, which will require its own handler for a few special cases
(such as array cookies) later.
DhruvSrivastavaX pushed a commit to DhruvSrivastavaX/lldb-for-aix that referenced this pull request Jun 12, 2025
This change upstreams the code to support emitting inline C++ function
definitions, including the ASTConsumer handler for inline definitions
and the code to load the 'this' pointer.

This necessitates introducing the Itanium CXXABI class. No other CXXABI
subclasses are supported at this time. The Itanium CXXABI is used for
AppleARM64, which will require its own handler for a few special cases
(such as array cookies) later.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants