[Mlir-commits] [mlir] [MLIR][LLVMIR] Add support for the full form of global_{ctor, dtor} (PR #133176)

Bruno Cardoso Lopes llvmlistbot at llvm.org
Wed Mar 26 15:40:13 PDT 2025


https://github.com/bcardosolopes created https://github.com/llvm/llvm-project/pull/133176

Currently only ctor/dtor list and their priorities are supported. This PR adds support for the missing data field.

Few implementation notes:
- The assembly printer has a fixed form because previous `attr_dict` will sort the dict by key name, making global_dtor and global_ctor differ in the order of printed arguments.
- LLVM's `ptr null` is being converted to `#llvm.zero` otherwise we'd have to create a region to use the default operation conversion from `ptr null`, which is silly given that the field only support null or a symbol.

>From 00c38d4a6abfb8714fbf7912c023af6b444db7d9 Mon Sep 17 00:00:00 2001
From: Bruno Cardoso Lopes <bruno.cardoso at gmail.com>
Date: Wed, 26 Mar 2025 15:04:14 -0700
Subject: [PATCH] [MLIR][LLVMIR] Add support for the full form of
 global_ctor/dtor

Currently only ctor/dtor list and priorities are supported. This
PR adds support for the data field.

Few implementation notes:
- The assembly printer has a fixed form now because just `attr_dict` will
sort the dict by key name, making dtor and ctor differ in the order of arguments.
- LLVM's `ptr null` is being converted to `#llvm.zero` otherwise we'd have to
create a region to use the operation version of `ptr null`, which is silly given
that the field only support null or an actual symbol.
---
 mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td   | 50 ++++++++++++-------
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp    | 35 ++++++++++---
 mlir/lib/Target/LLVMIR/ModuleImport.cpp       | 15 ++++--
 mlir/test/Dialect/LLVMIR/global.mlir          | 16 +++---
 mlir/test/Dialect/LLVMIR/invalid.mlir         | 21 +++++---
 .../Target/LLVMIR/Import/global-variables.ll  | 17 +++++--
 .../Target/LLVMIR/Import/import-failure.ll    | 11 ----
 mlir/test/Target/LLVMIR/llvmir.mlir           |  8 +--
 .../test/mlir-runner/global-constructors.mlir |  4 +-
 9 files changed, 111 insertions(+), 66 deletions(-)

diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index 5e1ab32e0a53d..2830718d93685 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -1438,24 +1438,24 @@ def LLVM_GlobalOp : LLVM_Op<"mlir.global",
 
 def LLVM_GlobalCtorsOp : LLVM_Op<"mlir.global_ctors", [
                            DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
-  let arguments = (ins FlatSymbolRefArrayAttr
-                   : $ctors, I32ArrayAttr
-                   : $priorities);
+  let arguments = (ins FlatSymbolRefArrayAttr:$ctors,
+                       I32ArrayAttr:$priorities,
+                       ArrayAttr:$data);
   let summary = "LLVM dialect global_ctors.";
   let description = [{
-    Specifies a list of constructor functions and priorities. The functions
-    referenced by this array will be called in ascending order of priority (i.e.
-    lowest first) when the module is loaded. The order of functions with the
-    same priority is not defined. This operation is translated to LLVM's
-    global_ctors global variable. The initializer functions are run at load
-    time. The `data` field present in LLVM's global_ctors variable is not
-    modeled here.
+    Specifies a list of constructor functions, priorities and associated data.
+    The functions referenced by this array will be called in ascending order
+    of priority (i.e. lowest first) when the module is loaded. The order of
+    functions with the same priority is not defined. This operation is
+    translated to LLVM's global_ctors global variable. The initializer
+    functions are run at load time. However, if the associated data is not `#llvm.zero`,
+    functions only run if the data is not discarded.
 
     Examples:
 
     ```mlir
-    llvm.mlir.global_ctors {@ctor}
-
+    llvm.mlir.global_ctors {ctors = [@ctor], data = [#llvm.zero],
+                            priorities = [0]}
     llvm.func @ctor() {
       ...
       llvm.return
@@ -1463,7 +1463,12 @@ def LLVM_GlobalCtorsOp : LLVM_Op<"mlir.global_ctors", [
     ```
 
   }];
-  let assemblyFormat = "attr-dict";
+  let assemblyFormat = [{
+    `ctors` `=` $ctors
+    `,` `priorities` `=` $priorities
+    `,` `data` `=` $data
+    attr-dict
+  }];
   let hasVerifier = 1;
 }
 
@@ -1471,7 +1476,8 @@ def LLVM_GlobalDtorsOp : LLVM_Op<"mlir.global_dtors", [
                            DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
   let arguments = (ins
     FlatSymbolRefArrayAttr:$dtors,
-    I32ArrayAttr:$priorities
+    I32ArrayAttr:$priorities,
+    ArrayAttr:$data
   );
   let summary = "LLVM dialect global_dtors.";
   let description = [{
@@ -1479,8 +1485,9 @@ def LLVM_GlobalDtorsOp : LLVM_Op<"mlir.global_dtors", [
     referenced by this array will be called in descending order of priority (i.e.
     highest first) when the module is unloaded. The order of functions with the
     same priority is not defined. This operation is translated to LLVM's
-    global_dtors global variable. The `data` field present in LLVM's
-    global_dtors variable is not modeled here.
+    global_dtors global variable. The destruction functions are run at load time.
+    However, if the associated data is not `#llvm.zero`, functions only run if the data
+    is not discarded.
 
     Examples:
 
@@ -1488,11 +1495,16 @@ def LLVM_GlobalDtorsOp : LLVM_Op<"mlir.global_dtors", [
     llvm.func @dtor() {
       llvm.return
     }
-    llvm.mlir.global_dtors {@dtor}
+    llvm.mlir.global_dtors {dtors = [@dtor], data = [#llvm.zero],
+                            priorities = [0]}
     ```
-
   }];
-  let assemblyFormat = "attr-dict";
+  let assemblyFormat = [{
+    `dtors` `=` $dtors
+    `,` `priorities` `=` $priorities
+    `,` `data` `=` $data
+    attr-dict
+  }];
   let hasVerifier = 1;
 }
 
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 18a70cc64628f..b038628762c16 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -2508,6 +2508,17 @@ LogicalResult GlobalOp::verifyRegions() {
 // LLVM::GlobalCtorsOp
 //===----------------------------------------------------------------------===//
 
+LogicalResult checkGlobalXtorData(Operation *op, ArrayAttr data) {
+  if (data.empty())
+    return success();
+
+  if (llvm::all_of(data.getAsRange<Attribute>(), [](Attribute v) {
+        return isa<FlatSymbolRefAttr>(v) || isa<ZeroAttr>(v);
+      }))
+    return success();
+  return op->emitError("data element must be symbol or #llvm.zero");
+}
+
 LogicalResult
 GlobalCtorsOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   for (Attribute ctor : getCtors()) {
@@ -2519,10 +2530,14 @@ GlobalCtorsOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
 }
 
 LogicalResult GlobalCtorsOp::verify() {
-  if (getCtors().size() != getPriorities().size())
-    return emitError(
-        "mismatch between the number of ctors and the number of priorities");
-  return success();
+  if (checkGlobalXtorData(*this, getData()).failed())
+    return failure();
+
+  if (getCtors().size() == getPriorities().size() &&
+      getCtors().size() == getData().size())
+    return success();
+  return emitError(
+      "ctors, priorities and data must have the same number of elements");
 }
 
 //===----------------------------------------------------------------------===//
@@ -2540,10 +2555,14 @@ GlobalDtorsOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
 }
 
 LogicalResult GlobalDtorsOp::verify() {
-  if (getDtors().size() != getPriorities().size())
-    return emitError(
-        "mismatch between the number of dtors and the number of priorities");
-  return success();
+  if (checkGlobalXtorData(*this, getData()).failed())
+    return failure();
+
+  if (getDtors().size() == getPriorities().size() &&
+      getDtors().size() == getData().size())
+    return success();
+  return emitError(
+      "dtors, priorities and data must have the same number of elements");
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index 00b16c86157e9..c0711f7dded71 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -1115,6 +1115,7 @@ ModuleImport::convertGlobalCtorsAndDtors(llvm::GlobalVariable *globalVar) {
 
   SmallVector<Attribute> funcs;
   SmallVector<int32_t> priorities;
+  SmallVector<Attribute> dataList;
   for (llvm::Value *operand : initializer->operands()) {
     auto *aggregate = dyn_cast<llvm::ConstantAggregate>(operand);
     if (!aggregate || aggregate->getNumOperands() != 3)
@@ -1126,12 +1127,18 @@ ModuleImport::convertGlobalCtorsAndDtors(llvm::GlobalVariable *globalVar) {
     if (!priority || !func || !data)
       return failure();
 
-    // GlobalCtorsOps and GlobalDtorsOps do not support non-null data fields.
-    if (!data->isNullValue())
+    auto *gv = dyn_cast_or_null<llvm::GlobalValue>(data);
+    Attribute dataAttr;
+    if (gv)
+      dataAttr = FlatSymbolRefAttr::get(context, gv->getName());
+    else if (data->isNullValue())
+      dataAttr = ZeroAttr::get(context);
+    else
       return failure();
 
     funcs.push_back(FlatSymbolRefAttr::get(context, func->getName()));
     priorities.push_back(priority->getValue().getZExtValue());
+    dataList.push_back(dataAttr);
   }
 
   // Insert the global after the last one or at the start of the module.
@@ -1140,12 +1147,12 @@ ModuleImport::convertGlobalCtorsAndDtors(llvm::GlobalVariable *globalVar) {
   if (globalVar->getName() == getGlobalCtorsVarName()) {
     globalInsertionOp = builder.create<LLVM::GlobalCtorsOp>(
         mlirModule.getLoc(), builder.getArrayAttr(funcs),
-        builder.getI32ArrayAttr(priorities));
+        builder.getI32ArrayAttr(priorities), builder.getArrayAttr(dataList));
     return success();
   }
   globalInsertionOp = builder.create<LLVM::GlobalDtorsOp>(
       mlirModule.getLoc(), builder.getArrayAttr(funcs),
-      builder.getI32ArrayAttr(priorities));
+      builder.getI32ArrayAttr(priorities), builder.getArrayAttr(dataList));
   return success();
 }
 
diff --git a/mlir/test/Dialect/LLVMIR/global.mlir b/mlir/test/Dialect/LLVMIR/global.mlir
index bd3584de9a405..193ab7987a2b6 100644
--- a/mlir/test/Dialect/LLVMIR/global.mlir
+++ b/mlir/test/Dialect/LLVMIR/global.mlir
@@ -228,16 +228,16 @@ llvm.func @ctor() {
   llvm.return
 }
 
-// CHECK: llvm.mlir.global_ctors {ctors = [@ctor], priorities = [0 : i32]}
-llvm.mlir.global_ctors { ctors = [@ctor], priorities = [0 : i32]}
+// CHECK: llvm.mlir.global_ctors ctors = [@ctor], priorities = [0 : i32], data = [#llvm.zero]
+llvm.mlir.global_ctors ctors = [@ctor], priorities = [0 : i32], data = [#llvm.zero]
 
 // -----
 
-// CHECK: llvm.mlir.global_ctors {ctors = [], priorities = []}
-llvm.mlir.global_ctors {ctors = [], priorities = []}
+// CHECK: llvm.mlir.global_ctors ctors = [], priorities = [], data = []
+llvm.mlir.global_ctors ctors = [], priorities = [], data = []
 
-// CHECK: llvm.mlir.global_dtors {dtors = [], priorities = []}
-llvm.mlir.global_dtors {dtors = [], priorities = []}
+// CHECK: llvm.mlir.global_dtors dtors = [], priorities = [], data = []
+llvm.mlir.global_dtors dtors = [], priorities = [], data = []
 
 // -----
 
@@ -245,8 +245,8 @@ llvm.func @dtor() {
   llvm.return
 }
 
-// CHECK: llvm.mlir.global_dtors {dtors = [@dtor], priorities = [0 : i32]}
-llvm.mlir.global_dtors { dtors = [@dtor], priorities = [0 : i32]}
+// CHECK: llvm.mlir.global_dtors dtors = [@dtor], priorities = [0 : i32], data = [#llvm.zero]
+llvm.mlir.global_dtors dtors = [@dtor], priorities = [0 : i32], data = [#llvm.zero]
 
 // -----
 
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index 6d3d3937b651c..d739dfce5b6da 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -9,8 +9,8 @@ llvm.func @ctor() {
   llvm.return
 }
 
-// expected-error at +1{{mismatch between the number of ctors and the number of priorities}}
-llvm.mlir.global_ctors {ctors = [@ctor], priorities = []}
+// expected-error at +1{{ctors, priorities and data must have the same number of elements}}
+llvm.mlir.global_ctors ctors = [@ctor], priorities = [], data = [#llvm.zero]
 
 // -----
 
@@ -18,20 +18,29 @@ llvm.func @dtor() {
   llvm.return
 }
 
-// expected-error at +1{{mismatch between the number of dtors and the number of priorities}}
-llvm.mlir.global_dtors {dtors = [@dtor], priorities = [0 : i32, 32767 : i32]}
+// expected-error at +1{{dtors, priorities and data must have the same number of elements}}
+llvm.mlir.global_dtors dtors = [@dtor], priorities = [0 : i32, 32767 : i32], data = [#llvm.zero]
 
 // -----
 
 // expected-error at +1{{'ctor' does not reference a valid LLVM function}}
-llvm.mlir.global_ctors {ctors = [@ctor], priorities = [0 : i32]}
+llvm.mlir.global_ctors ctors = [@ctor], priorities = [0 : i32], data = [#llvm.zero]
 
 // -----
 
 llvm.func @dtor()
 
 // expected-error at +1{{'dtor' does not have a definition}}
-llvm.mlir.global_dtors {dtors = [@dtor], priorities = [0 : i32]}
+llvm.mlir.global_dtors dtors = [@dtor], priorities = [0 : i32], data = [#llvm.zero]
+
+// -----
+
+llvm.func @dtor() {
+  llvm.return
+}
+
+// expected-error at +1{{data element must be symbol or #llvm.zero}}
+llvm.mlir.global_dtors dtors = [@dtor], priorities = [0 : i32], data = [0 : i32]
 
 ////////////////////////////////////////////////////////////////////////////////
 
diff --git a/mlir/test/Target/LLVMIR/Import/global-variables.ll b/mlir/test/Target/LLVMIR/Import/global-variables.ll
index b809c93d772f5..b8bbdbab2e2ca 100644
--- a/mlir/test/Target/LLVMIR/Import/global-variables.ll
+++ b/mlir/test/Target/LLVMIR/Import/global-variables.ll
@@ -241,8 +241,8 @@
 
 ; // -----
 
-; CHECK: llvm.mlir.global_ctors {ctors = [@foo, @bar], priorities = [0 : i32, 42 : i32]}
-; CHECK: llvm.mlir.global_dtors {dtors = [@foo], priorities = [0 : i32]}
+; CHECK: llvm.mlir.global_ctors ctors = [@foo, @bar], priorities = [0 : i32, 42 : i32], data = [#llvm.zero, #llvm.zero]
+; CHECK: llvm.mlir.global_dtors dtors = [@foo], priorities = [0 : i32], data = [#llvm.zero]
 @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @foo, ptr null }, { i32, ptr, ptr } { i32 42, ptr @bar, ptr null }]
 @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @foo, ptr null }]
 
@@ -256,14 +256,23 @@ define void @bar() {
 
 ; // -----
 
-; CHECK: llvm.mlir.global_ctors {ctors = [], priorities = []}
+; CHECK: llvm.mlir.global_ctors ctors = [], priorities = [], data = []
 @llvm.global_ctors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
 
-; CHECK: llvm.mlir.global_dtors {dtors = [], priorities = []}
+; CHECK: llvm.mlir.global_dtors dtors = [], priorities = [], data = []
 @llvm.global_dtors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
 
 ; // -----
 
+; llvm.mlir.global_dtors dtors = [@foo], priorities = [0 : i32], data = [@foo]
+ at llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @foo, ptr @foo }]
+
+define void @foo() {
+  ret void
+}
+
+; // -----
+
 ; Visibility attribute.
 
 ; CHECK: llvm.mlir.global external hidden constant @hidden("string")
diff --git a/mlir/test/Target/LLVMIR/Import/import-failure.ll b/mlir/test/Target/LLVMIR/Import/import-failure.ll
index 4ef67b7190aab..d3ea3a510d7f8 100644
--- a/mlir/test/Target/LLVMIR/Import/import-failure.ll
+++ b/mlir/test/Target/LLVMIR/Import/import-failure.ll
@@ -64,17 +64,6 @@ define void @unsupported_argument(i64 %arg1) {
 
 ; // -----
 
-; global_dtors with non-null data fields cannot be represented in MLIR.
-; CHECK:      <unknown>
-; CHECK-SAME: error: unhandled global variable: @llvm.global_dtors
- at llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @foo, ptr @foo }]
-
-define void @foo() {
-  ret void
-}
-
-; // -----
-
 ; CHECK:      import-failure.ll
 ; CHECK-SAME: error: unsupported TBAA node format: !{{.*}} = !{!{{.*}}, i64 1, !"omnipotent char"}
 define dso_local void @tbaa(ptr %0) {
diff --git a/mlir/test/Target/LLVMIR/llvmir.mlir b/mlir/test/Target/LLVMIR/llvmir.mlir
index 2476b1b15faaa..3e60b7cbaf63b 100644
--- a/mlir/test/Target/LLVMIR/llvmir.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir.mlir
@@ -1851,7 +1851,7 @@ llvm.mlir.global linkonce @take_self_address() : !llvm.struct<(i32, !llvm.ptr)>
 // -----
 
 // CHECK: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @foo, ptr null }]
-llvm.mlir.global_ctors { ctors = [@foo], priorities = [0 : i32]}
+llvm.mlir.global_ctors ctors = [@foo], priorities = [0 : i32], data = [#llvm.zero]
 
 llvm.func @foo() {
   llvm.return
@@ -1860,15 +1860,15 @@ llvm.func @foo() {
 // -----
 
 // CHECK: @llvm.global_ctors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
-llvm.mlir.global_ctors {ctors = [], priorities = []}
+llvm.mlir.global_ctors ctors = [], priorities = [], data = []
 
 // CHECK: @llvm.global_dtors = appending global [0 x { i32, ptr, ptr }] zeroinitializer
-llvm.mlir.global_dtors {dtors = [], priorities = []}
+llvm.mlir.global_dtors dtors = [], priorities = [], data = []
 
 // -----
 
 // 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]}
+llvm.mlir.global_dtors dtors = [@foo], priorities = [0 : i32], data = [#llvm.zero]
 
 llvm.func @foo() {
   llvm.return
diff --git a/mlir/test/mlir-runner/global-constructors.mlir b/mlir/test/mlir-runner/global-constructors.mlir
index 593d6aa4945ef..8b19d9873b8b0 100644
--- a/mlir/test/mlir-runner/global-constructors.mlir
+++ b/mlir/test/mlir-runner/global-constructors.mlir
@@ -7,8 +7,8 @@
 module {
   llvm.func @printNewline()
   llvm.func @printI64(i64)
-  llvm.mlir.global_ctors {ctors = [@ctor], priorities = [0 : i32]}
-  llvm.mlir.global_dtors {dtors = [@dtor], priorities = [0 : i32]}
+  llvm.mlir.global_ctors ctors = [@ctor], priorities = [0 : i32], data = [#llvm.zero]
+  llvm.mlir.global_dtors dtors = [@dtor], priorities = [0 : i32], data = [#llvm.zero]
   llvm.func @ctor() {
     %0 = llvm.mlir.constant(1 : i64) : i64
     llvm.call @printI64(%0) : (i64) -> ()



More information about the Mlir-commits mailing list