Skip to content

[CIR] Defer emitting function definitions #142862

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 5, 2025

Conversation

andykaylor
Copy link
Contributor

This change implements deferring function definition emission until first use.

This change implements deferring function definition emission until first
use.
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Jun 4, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 4, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clangir

Author: Andy Kaylor (andykaylor)

Changes

This change implements deferring function definition emission until first use.


Full diff: https://github.com/llvm/llvm-project/pull/142862.diff

2 Files Affected:

  • (modified) clang/lib/CIR/CodeGen/CIRGenModule.cpp (+53-9)
  • (added) clang/test/CIR/CodeGen/deferred-fn-defs.cpp (+38)
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index eb291de8a3cc9..54c9fcf629e3d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -213,13 +213,18 @@ CIRGenModule::getAddrOfGlobal(GlobalDecl gd, ForDefinition_t isForDefinition) {
   }
 
   if (isa<CXXMethodDecl>(d)) {
-    errorNYI(d->getSourceRange(), "getAddrOfGlobal: C++ method decl");
-    return nullptr;
+    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)) {
-    errorNYI(d->getSourceRange(), "getAddrOfGlobal: function decl");
-    return nullptr;
+    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)
@@ -1275,11 +1280,6 @@ bool CIRGenModule::mustBeEmitted(const ValueDecl *global) {
         vd->getType().isConstQualified())))
     return true;
 
-  // TODO(cir): We do want to defer function decls, but it's not implemented.
-  assert(!cir::MissingFeatures::deferredFuncDecls());
-  if (isa<FunctionDecl>(global))
-    return true;
-
   return getASTContext().DeclMustBeEmitted(global);
 }
 
@@ -1523,6 +1523,50 @@ cir::FuncOp CIRGenModule::getOrCreateCIRFunction(
   cir::FuncOp funcOp = createCIRFunction(
       invalidLoc ? theModule->getLoc() : getLoc(funcDecl->getSourceRange()),
       mangledName, mlir::cast<cir::FuncType>(funcType), funcDecl);
+
+  if (!dontDefer) {
+    // 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))
+      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;
 }
 
diff --git a/clang/test/CIR/CodeGen/deferred-fn-defs.cpp b/clang/test/CIR/CodeGen/deferred-fn-defs.cpp
new file mode 100644
index 0000000000000..e7088ba35eca7
--- /dev/null
+++ b/clang/test/CIR/CodeGen/deferred-fn-defs.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR --implicit-check-not=externNotCalled \
+// RUN:   --implicit-check-not=internalNotCalled --implicit-check-not=inlineNotCalled
+
+extern int externCalled();
+extern int externNotCalled();
+
+namespace {
+  int internalCalled() { return 1; }
+  int internalNotCalled() { return 2; }
+}
+
+struct S {
+  int inlineCalled() { return 3; }
+  int inlineNotCalled() { return 4; }
+};
+
+void use() {
+  S s;
+  externCalled();
+  internalCalled();
+  s.inlineCalled();
+}
+
+// CIR: cir.func{{.*}} @_Z12externCalledv
+// This shouldn't have a body.
+// CIR-NOT: cir.return
+
+// CIR: cir.func{{.*}} @_ZN12_GLOBAL__N_114internalCalledEv
+// CIR:   %[[ONE:.*]] = cir.const #cir.int<1>
+// CIR:   cir.store %[[ONE]], %[[RET_ADDR:.*]]
+
+// CIR: cir.func{{.*}} @_ZN1S12inlineCalledEv
+// CIR:   %[[THIS:.*]] = cir.alloca !cir.ptr<!rec_S>, !cir.ptr<!cir.ptr<!rec_S>>, ["this", init]
+// CIR:   %[[THREE:.*]] = cir.const #cir.int<3>
+// CIR:   cir.store %[[THREE]], %[[RET_ADDR:.*]]
+
+// CIR: cir.func{{.*}} @_Z3usev()

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

Looks good, minor nit

@@ -1523,6 +1523,50 @@ cir::FuncOp CIRGenModule::getOrCreateCIRFunction(
cir::FuncOp funcOp = createCIRFunction(
invalidLoc ? theModule->getLoc() : getLoc(funcDecl->getSourceRange()),
mangledName, mlir::cast<cir::FuncType>(funcType), funcDecl);

if (!dontDefer) {
Copy link
Member

Choose a reason for hiding this comment

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

In order to diminish levels of indentation, should this return early otherwise?

if (dontDefer)
  return funcOp
// rest of code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's an assertion below this in the incubator and classic codegen (which I might as well add now), but it's easily moved to the early return.

It seems to me that dontDefer is a somewhat misleading name. If it is true, it means that we aren't ready to move this from the deferredDecls list to the declsToEmit list yet. Nothing gets deferred here either way. It really should be something like dontMoveToEmitList which is really closer to dontStopDeferring. The current name dates back to OGCG. I think any renaming can wait for a future change. I just thought I'd mention it here.

Copy link

github-actions bot commented Jun 5, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@andykaylor andykaylor merged commit af54790 into llvm:main Jun 5, 2025
11 checks passed
@andykaylor andykaylor deleted the defer-fn-defs branch June 5, 2025 20:09
erichkeane added a commit that referenced this pull request Jun 6, 2025
The patch #142998 crossed in the air with #142862.  This resulted in 2
of the tests from the former to not have the inlined function emitted.
This patch adds an additional function to force these to be emitted.
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