Skip to content

[MLIR][LLVMIR] Add support for empty global ctor/dtor lists #128969

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
Feb 28, 2025

Conversation

bcardosolopes
Copy link
Member

@bcardosolopes bcardosolopes commented Feb 26, 2025

LLVM IR emitted in from C++ may contain @llvm.global_ctors = appending global [0 x { i32, ptr, ptr }] zeroinitializer. Before this PR, if we try to roundtrip code like this from the importer, we'll end up with nothing in place.

Note that llvm::appendToGlobalCtors ignores empty lists and this PR uses the same approach as llvm-as, which doesn't use the utilities from llvm/lib/Transforms/Utils/ModuleUtils.cpp in order to build this - it calls into creating a global variable from scratch.

@llvmbot
Copy link
Member

llvmbot commented Feb 26, 2025

@llvm/pr-subscribers-mlir-llvm

Author: Bruno Cardoso Lopes (bcardosolopes)

Changes

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

5 Files Affected:

  • (modified) mlir/lib/Target/LLVMIR/ModuleImport.cpp (+24-17)
  • (modified) mlir/lib/Target/LLVMIR/ModuleTranslation.cpp (+31-10)
  • (modified) mlir/test/Dialect/LLVMIR/global.mlir (+8)
  • (modified) mlir/test/Target/LLVMIR/Import/global-variables.ll (+8)
  • (modified) mlir/test/Target/LLVMIR/llvmir.mlir (+8)
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index 8445e609c2244..edaacb883f0c2 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -1071,32 +1071,39 @@ LogicalResult
 ModuleImport::convertGlobalCtorsAndDtors(llvm::GlobalVariable *globalVar) {
   if (!globalVar->hasInitializer() || !globalVar->hasAppendingLinkage())
     return failure();
-  auto *initializer =
-      dyn_cast<llvm::ConstantArray>(globalVar->getInitializer());
-  if (!initializer)
+  llvm::Constant *initializer = globalVar->getInitializer();
+
+  bool knownInit = isa<llvm::ConstantArray>(initializer) ||
+                   isa<llvm::ConstantAggregateZero>(initializer);
+  if (!knownInit)
     return failure();
 
   SmallVector<Attribute> funcs;
   SmallVector<int32_t> priorities;
-  for (llvm::Value *operand : initializer->operands()) {
-    auto *aggregate = dyn_cast<llvm::ConstantAggregate>(operand);
-    if (!aggregate || aggregate->getNumOperands() != 3)
-      return failure();
+  if (auto caInit = dyn_cast<llvm::ConstantArray>(initializer)) {
+    for (llvm::Value *operand : initializer->operands()) {
+      auto *aggregate = dyn_cast<llvm::ConstantAggregate>(operand);
+      if (!aggregate || aggregate->getNumOperands() != 3)
+        return failure();
 
-    auto *priority = dyn_cast<llvm::ConstantInt>(aggregate->getOperand(0));
-    auto *func = dyn_cast<llvm::Function>(aggregate->getOperand(1));
-    auto *data = dyn_cast<llvm::Constant>(aggregate->getOperand(2));
-    if (!priority || !func || !data)
-      return failure();
+      auto *priority = dyn_cast<llvm::ConstantInt>(aggregate->getOperand(0));
+      auto *func = dyn_cast<llvm::Function>(aggregate->getOperand(1));
+      auto *data = dyn_cast<llvm::Constant>(aggregate->getOperand(2));
+      if (!priority || !func || !data)
+        return failure();
 
-    // GlobalCtorsOps and GlobalDtorsOps do not support non-null data fields.
-    if (!data->isNullValue())
-      return failure();
+      // GlobalCtorsOps and GlobalDtorsOps do not support non-null data fields.
+      if (!data->isNullValue())
+        return failure();
 
-    funcs.push_back(FlatSymbolRefAttr::get(context, func->getName()));
-    priorities.push_back(priority->getValue().getZExtValue());
+      funcs.push_back(FlatSymbolRefAttr::get(context, func->getName()));
+      priorities.push_back(priority->getValue().getZExtValue());
+    }
   }
 
+  // Note: no action needed for ConstantAggregateZero, which implies empty
+  // ctor/dtor lists.
+
   // Insert the global after the last one or at the start of the module.
   OpBuilder::InsertionGuard guard = setGlobalInsertionPoint();
 
diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
index 5cd841ee2df91..c6bfb9566a4e1 100644
--- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
@@ -1258,16 +1258,37 @@ LogicalResult ModuleTranslation::convertGlobalsAndAliases() {
     auto dtorOp = dyn_cast<GlobalDtorsOp>(op);
     if (!ctorOp && !dtorOp)
       continue;
-    auto range = ctorOp ? llvm::zip(ctorOp.getCtors(), ctorOp.getPriorities())
-                        : llvm::zip(dtorOp.getDtors(), dtorOp.getPriorities());
-    auto appendGlobalFn =
-        ctorOp ? llvm::appendToGlobalCtors : llvm::appendToGlobalDtors;
-    for (auto symbolAndPriority : range) {
-      llvm::Function *f = lookupFunction(
-          cast<FlatSymbolRefAttr>(std::get<0>(symbolAndPriority)).getValue());
-      appendGlobalFn(*llvmModule, f,
-                     cast<IntegerAttr>(std::get<1>(symbolAndPriority)).getInt(),
-                     /*Data=*/nullptr);
+
+    // The empty / zero initialized version of llvm.global_(c|d)tors cannot be
+    // handled by appendGlobalFn logic below, which just ignores empty (c|d)tor
+    // lists. Make sure it gets emitted.
+    if ((ctorOp && ctorOp.getCtors().empty()) ||
+        (dtorOp && dtorOp.getDtors().empty())) {
+      llvm::IRBuilder<llvm::TargetFolder> builder(
+          llvmModule->getContext(),
+          llvm::TargetFolder(llvmModule->getDataLayout()));
+      llvm::Type *eltTy = llvm::StructType::get(
+          builder.getInt32Ty(), builder.getPtrTy(), builder.getPtrTy());
+      llvm::ArrayType *at = llvm::ArrayType::get(eltTy, 0);
+      llvm::Constant *zeroInit = llvm::Constant::getNullValue(at);
+      (void)new llvm::GlobalVariable(
+          *llvmModule, zeroInit->getType(), false,
+          llvm::GlobalValue::AppendingLinkage, zeroInit,
+          ctorOp ? "llvm.global_ctors" : "llvm.global_dtors");
+    } else {
+      auto range = ctorOp
+                       ? llvm::zip(ctorOp.getCtors(), ctorOp.getPriorities())
+                       : llvm::zip(dtorOp.getDtors(), dtorOp.getPriorities());
+      auto appendGlobalFn =
+          ctorOp ? llvm::appendToGlobalCtors : llvm::appendToGlobalDtors;
+      for (auto symbolAndPriority : range) {
+        llvm::Function *f = lookupFunction(
+            cast<FlatSymbolRefAttr>(std::get<0>(symbolAndPriority)).getValue());
+        appendGlobalFn(
+            *llvmModule, f,
+            cast<IntegerAttr>(std::get<1>(symbolAndPriority)).getInt(),
+            /*Data=*/nullptr);
+      }
     }
   }
 
diff --git a/mlir/test/Dialect/LLVMIR/global.mlir b/mlir/test/Dialect/LLVMIR/global.mlir
index 79d1cafabfbed..bd3584de9a405 100644
--- a/mlir/test/Dialect/LLVMIR/global.mlir
+++ b/mlir/test/Dialect/LLVMIR/global.mlir
@@ -233,6 +233,14 @@ llvm.mlir.global_ctors { ctors = [@ctor], priorities = [0 : i32]}
 
 // -----
 
+// CHECK: llvm.mlir.global_ctors {ctors = [], priorities = []}
+llvm.mlir.global_ctors {ctors = [], priorities = []}
+
+// CHECK: llvm.mlir.global_dtors {dtors = [], priorities = []}
+llvm.mlir.global_dtors {dtors = [], priorities = []}
+
+// -----
+
 llvm.func @dtor() {
   llvm.return
 }
diff --git a/mlir/test/Target/LLVMIR/Import/global-variables.ll b/mlir/test/Target/LLVMIR/Import/global-variables.ll
index fbeda4cd42af8..b809c93d772f5 100644
--- a/mlir/test/Target/LLVMIR/Import/global-variables.ll
+++ b/mlir/test/Target/LLVMIR/Import/global-variables.ll
@@ -256,6 +256,14 @@ define void @bar() {
 
 ; // -----
 
+; CHECK: llvm.mlir.global_ctors {ctors = [], priorities = []}
+@llvm.global_ctors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
+
+; CHECK: llvm.mlir.global_dtors {dtors = [], priorities = []}
+@llvm.global_dtors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
+
+; // -----
+
 ; Visibility attribute.
 
 ; CHECK: llvm.mlir.global external hidden constant @hidden("string")
diff --git a/mlir/test/Target/LLVMIR/llvmir.mlir b/mlir/test/Target/LLVMIR/llvmir.mlir
index 7f9a3ba79d724..db2e08742dbca 100644
--- a/mlir/test/Target/LLVMIR/llvmir.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir.mlir
@@ -1859,6 +1859,14 @@ llvm.func @foo() {
 
 // -----
 
+// CHECK: @llvm.global_ctors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
+llvm.mlir.global_ctors {ctors = [], priorities = []}
+
+// CHECK: @llvm.global_dtors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
+llvm.mlir.global_dtors {dtors = [], priorities = []}
+
+// -----
+
 // CHECK: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @foo, ptr null }]
 llvm.mlir.global_dtors { dtors = [@foo], priorities = [0 : i32]}
 

@llvmbot
Copy link
Member

llvmbot commented Feb 26, 2025

@llvm/pr-subscribers-mlir

Author: Bruno Cardoso Lopes (bcardosolopes)

Changes

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

5 Files Affected:

  • (modified) mlir/lib/Target/LLVMIR/ModuleImport.cpp (+24-17)
  • (modified) mlir/lib/Target/LLVMIR/ModuleTranslation.cpp (+31-10)
  • (modified) mlir/test/Dialect/LLVMIR/global.mlir (+8)
  • (modified) mlir/test/Target/LLVMIR/Import/global-variables.ll (+8)
  • (modified) mlir/test/Target/LLVMIR/llvmir.mlir (+8)
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index 8445e609c2244..edaacb883f0c2 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -1071,32 +1071,39 @@ LogicalResult
 ModuleImport::convertGlobalCtorsAndDtors(llvm::GlobalVariable *globalVar) {
   if (!globalVar->hasInitializer() || !globalVar->hasAppendingLinkage())
     return failure();
-  auto *initializer =
-      dyn_cast<llvm::ConstantArray>(globalVar->getInitializer());
-  if (!initializer)
+  llvm::Constant *initializer = globalVar->getInitializer();
+
+  bool knownInit = isa<llvm::ConstantArray>(initializer) ||
+                   isa<llvm::ConstantAggregateZero>(initializer);
+  if (!knownInit)
     return failure();
 
   SmallVector<Attribute> funcs;
   SmallVector<int32_t> priorities;
-  for (llvm::Value *operand : initializer->operands()) {
-    auto *aggregate = dyn_cast<llvm::ConstantAggregate>(operand);
-    if (!aggregate || aggregate->getNumOperands() != 3)
-      return failure();
+  if (auto caInit = dyn_cast<llvm::ConstantArray>(initializer)) {
+    for (llvm::Value *operand : initializer->operands()) {
+      auto *aggregate = dyn_cast<llvm::ConstantAggregate>(operand);
+      if (!aggregate || aggregate->getNumOperands() != 3)
+        return failure();
 
-    auto *priority = dyn_cast<llvm::ConstantInt>(aggregate->getOperand(0));
-    auto *func = dyn_cast<llvm::Function>(aggregate->getOperand(1));
-    auto *data = dyn_cast<llvm::Constant>(aggregate->getOperand(2));
-    if (!priority || !func || !data)
-      return failure();
+      auto *priority = dyn_cast<llvm::ConstantInt>(aggregate->getOperand(0));
+      auto *func = dyn_cast<llvm::Function>(aggregate->getOperand(1));
+      auto *data = dyn_cast<llvm::Constant>(aggregate->getOperand(2));
+      if (!priority || !func || !data)
+        return failure();
 
-    // GlobalCtorsOps and GlobalDtorsOps do not support non-null data fields.
-    if (!data->isNullValue())
-      return failure();
+      // GlobalCtorsOps and GlobalDtorsOps do not support non-null data fields.
+      if (!data->isNullValue())
+        return failure();
 
-    funcs.push_back(FlatSymbolRefAttr::get(context, func->getName()));
-    priorities.push_back(priority->getValue().getZExtValue());
+      funcs.push_back(FlatSymbolRefAttr::get(context, func->getName()));
+      priorities.push_back(priority->getValue().getZExtValue());
+    }
   }
 
+  // Note: no action needed for ConstantAggregateZero, which implies empty
+  // ctor/dtor lists.
+
   // Insert the global after the last one or at the start of the module.
   OpBuilder::InsertionGuard guard = setGlobalInsertionPoint();
 
diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
index 5cd841ee2df91..c6bfb9566a4e1 100644
--- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
@@ -1258,16 +1258,37 @@ LogicalResult ModuleTranslation::convertGlobalsAndAliases() {
     auto dtorOp = dyn_cast<GlobalDtorsOp>(op);
     if (!ctorOp && !dtorOp)
       continue;
-    auto range = ctorOp ? llvm::zip(ctorOp.getCtors(), ctorOp.getPriorities())
-                        : llvm::zip(dtorOp.getDtors(), dtorOp.getPriorities());
-    auto appendGlobalFn =
-        ctorOp ? llvm::appendToGlobalCtors : llvm::appendToGlobalDtors;
-    for (auto symbolAndPriority : range) {
-      llvm::Function *f = lookupFunction(
-          cast<FlatSymbolRefAttr>(std::get<0>(symbolAndPriority)).getValue());
-      appendGlobalFn(*llvmModule, f,
-                     cast<IntegerAttr>(std::get<1>(symbolAndPriority)).getInt(),
-                     /*Data=*/nullptr);
+
+    // The empty / zero initialized version of llvm.global_(c|d)tors cannot be
+    // handled by appendGlobalFn logic below, which just ignores empty (c|d)tor
+    // lists. Make sure it gets emitted.
+    if ((ctorOp && ctorOp.getCtors().empty()) ||
+        (dtorOp && dtorOp.getDtors().empty())) {
+      llvm::IRBuilder<llvm::TargetFolder> builder(
+          llvmModule->getContext(),
+          llvm::TargetFolder(llvmModule->getDataLayout()));
+      llvm::Type *eltTy = llvm::StructType::get(
+          builder.getInt32Ty(), builder.getPtrTy(), builder.getPtrTy());
+      llvm::ArrayType *at = llvm::ArrayType::get(eltTy, 0);
+      llvm::Constant *zeroInit = llvm::Constant::getNullValue(at);
+      (void)new llvm::GlobalVariable(
+          *llvmModule, zeroInit->getType(), false,
+          llvm::GlobalValue::AppendingLinkage, zeroInit,
+          ctorOp ? "llvm.global_ctors" : "llvm.global_dtors");
+    } else {
+      auto range = ctorOp
+                       ? llvm::zip(ctorOp.getCtors(), ctorOp.getPriorities())
+                       : llvm::zip(dtorOp.getDtors(), dtorOp.getPriorities());
+      auto appendGlobalFn =
+          ctorOp ? llvm::appendToGlobalCtors : llvm::appendToGlobalDtors;
+      for (auto symbolAndPriority : range) {
+        llvm::Function *f = lookupFunction(
+            cast<FlatSymbolRefAttr>(std::get<0>(symbolAndPriority)).getValue());
+        appendGlobalFn(
+            *llvmModule, f,
+            cast<IntegerAttr>(std::get<1>(symbolAndPriority)).getInt(),
+            /*Data=*/nullptr);
+      }
     }
   }
 
diff --git a/mlir/test/Dialect/LLVMIR/global.mlir b/mlir/test/Dialect/LLVMIR/global.mlir
index 79d1cafabfbed..bd3584de9a405 100644
--- a/mlir/test/Dialect/LLVMIR/global.mlir
+++ b/mlir/test/Dialect/LLVMIR/global.mlir
@@ -233,6 +233,14 @@ llvm.mlir.global_ctors { ctors = [@ctor], priorities = [0 : i32]}
 
 // -----
 
+// CHECK: llvm.mlir.global_ctors {ctors = [], priorities = []}
+llvm.mlir.global_ctors {ctors = [], priorities = []}
+
+// CHECK: llvm.mlir.global_dtors {dtors = [], priorities = []}
+llvm.mlir.global_dtors {dtors = [], priorities = []}
+
+// -----
+
 llvm.func @dtor() {
   llvm.return
 }
diff --git a/mlir/test/Target/LLVMIR/Import/global-variables.ll b/mlir/test/Target/LLVMIR/Import/global-variables.ll
index fbeda4cd42af8..b809c93d772f5 100644
--- a/mlir/test/Target/LLVMIR/Import/global-variables.ll
+++ b/mlir/test/Target/LLVMIR/Import/global-variables.ll
@@ -256,6 +256,14 @@ define void @bar() {
 
 ; // -----
 
+; CHECK: llvm.mlir.global_ctors {ctors = [], priorities = []}
+@llvm.global_ctors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
+
+; CHECK: llvm.mlir.global_dtors {dtors = [], priorities = []}
+@llvm.global_dtors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
+
+; // -----
+
 ; Visibility attribute.
 
 ; CHECK: llvm.mlir.global external hidden constant @hidden("string")
diff --git a/mlir/test/Target/LLVMIR/llvmir.mlir b/mlir/test/Target/LLVMIR/llvmir.mlir
index 7f9a3ba79d724..db2e08742dbca 100644
--- a/mlir/test/Target/LLVMIR/llvmir.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir.mlir
@@ -1859,6 +1859,14 @@ llvm.func @foo() {
 
 // -----
 
+// CHECK: @llvm.global_ctors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
+llvm.mlir.global_ctors {ctors = [], priorities = []}
+
+// CHECK: @llvm.global_dtors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
+llvm.mlir.global_dtors {dtors = [], priorities = []}
+
+// -----
+
 // CHECK: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @foo, ptr null }]
 llvm.mlir.global_dtors { dtors = [@foo], priorities = [0 : i32]}
 

@Dinistro
Copy link
Contributor

Can you expand on why we should not just drop empty decls?

Copy link
Contributor

@Dinistro Dinistro left a comment

Choose a reason for hiding this comment

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

As stated before, can you give an explanation why this is even desired to import? Given that llvm::appendToGlobalCtors ignores empty lists, this seems like only half way supported.

@bcardosolopes
Copy link
Member Author

Can you expand on why we should not just drop empty decls?

LLVM IR emitted in our C++ codebase contains @llvm.global_ctors = appending global [0 x { i32, ptr, ptr }] zeroinitializer, I could go dig clang codegen to find out why this gets emitted but it feels orthogonal here - seems rather silly that if try to roundtrip code like this from the importer, we'll end up with nothing in place.

If you think this should be represented differently (i.e. not by empty lists), please let me know what approach you'd like to see.

@bcardosolopes
Copy link
Member Author

Given that llvm::appendToGlobalCtors ignores empty lists, this seems like only half way supported.

FWIW, llvm-as doesn't use the utilities from llvm/lib/Transforms/Utils/ModuleUtils.cpp in order to build this, it calls into creating a global variable from scratch (which handles this case nicely)

Copy link
Contributor

@gysit gysit left a comment

Choose a reason for hiding this comment

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

LGTM from my end.

I added some optional comments, feel free to apply if you think they make sense.

@bcardosolopes
Copy link
Member Author

@Dinistro let me know your thoughts!

Copy link
Contributor

@Dinistro Dinistro left a comment

Choose a reason for hiding this comment

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

Fine for me. Make sure to include parts of this explanation in the commit description.
LGTM!

@bcardosolopes bcardosolopes merged commit af2dd15 into llvm:main Feb 28, 2025
11 checks passed
cheezeburglar pushed a commit to cheezeburglar/llvm-project that referenced this pull request Feb 28, 2025
)

LLVM IR emitted in from C++ may contain `@llvm.global_ctors = appending
global [0 x { i32, ptr, ptr }] zeroinitializer`. Before this PR, if we
try to roundtrip code like this from the importer, we'll end up with
nothing in place.

Note that `llvm::appendToGlobalCtors` ignores empty lists and this PR
uses the same approach as `llvm-as`, which doesn't use the utilities
from `llvm/lib/Transforms/Utils/ModuleUtils.cpp` in order to build this
- it calls into creating a global variable from scratch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants