[Mlir-commits] [mlir] [mlir][conversion] Pass DataLayoutAnalysis to LowerToLLVMOptions in `ArithToLLVM`, `ControlFlowToLLVM`, and `VectorToLLVM` passes (PR #206380)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sun Jun 28 17:04:59 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir
Author: Federico Bruzzone (FedericoBruzzone)
<details>
<summary>Changes</summary>
These three conversion passes constructed `LowerToLLVMOptions` without a `DataLayout`, causing the index bitwidth to be hard-wired to 64 even when the module declares a narrower index via `dlti.dl_spec`.
```cpp
// Before (all three passes):
LowerToLLVMOptions options(&getContext());
LLVMTypeConverter converter(&getContext(), options);
// After:
const auto &dataLayoutAnalysis = getAnalysis<DataLayoutAnalysis>();
LowerToLLVMOptions options(&getContext(),
dataLayoutAnalysis.getAtOrAbove(getOperation()));
LLVMTypeConverter converter(&getContext(), options, &dataLayoutAnalysis);
```
As a side-effect, this makes `enable-gep-inbounds-nuw` on `convert-vector-to-llvm` meaningful on 32-bit modules: LLVM SCEV can now form clean i32 AddRecs from the annotated multiply, allowing LSR to replace per-iteration index recomputation with pointer induction.
---
Full diff: https://github.com/llvm/llvm-project/pull/206380.diff
9 Files Affected:
- (modified) mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp (+5-2)
- (modified) mlir/lib/Conversion/ArithToLLVM/CMakeLists.txt (+2)
- (modified) mlir/lib/Conversion/ControlFlowToLLVM/CMakeLists.txt (+1)
- (modified) mlir/lib/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.cpp (+5-2)
- (modified) mlir/lib/Conversion/VectorToLLVM/CMakeLists.txt (+2)
- (modified) mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp (+8-2)
- (added) mlir/test/Conversion/ArithToLLVM/arith-to-llvm-32bit-index.mlir (+74)
- (added) mlir/test/Conversion/ControlFlowToLLVM/branch-32bit-index.mlir (+65)
- (added) mlir/test/Conversion/VectorToLLVM/vector-load-store-32bit-index-to-llvm.mlir (+76)
``````````diff
diff --git a/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp b/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp
index 6bf0fe85bb62f..64f6358dd938d 100644
--- a/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp
+++ b/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp
@@ -8,6 +8,7 @@
#include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h"
+#include "mlir/Analysis/DataLayoutAnalysis.h"
#include "mlir/Conversion/ArithCommon/AttrToLLVMConverter.h"
#include "mlir/Conversion/ConvertToLLVM/ToLLVMInterface.h"
#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
@@ -710,11 +711,13 @@ struct ArithToLLVMConversionPass
LLVMConversionTarget target(getContext());
RewritePatternSet patterns(&getContext());
- LowerToLLVMOptions options(&getContext());
+ const auto &dataLayoutAnalysis = getAnalysis<DataLayoutAnalysis>();
+ LowerToLLVMOptions options(&getContext(),
+ dataLayoutAnalysis.getAtOrAbove(getOperation()));
if (indexBitwidth != kDeriveIndexBitwidthFromDataLayout)
options.overrideIndexBitwidth(indexBitwidth);
- LLVMTypeConverter converter(&getContext(), options);
+ LLVMTypeConverter converter(&getContext(), options, &dataLayoutAnalysis);
arith::populateCeilFloorDivExpandOpsPatterns(patterns);
arith::populateArithToLLVMConversionPatterns(converter, patterns);
diff --git a/mlir/lib/Conversion/ArithToLLVM/CMakeLists.txt b/mlir/lib/Conversion/ArithToLLVM/CMakeLists.txt
index 0a0e25e18b47a..8fdfa991a35d3 100644
--- a/mlir/lib/Conversion/ArithToLLVM/CMakeLists.txt
+++ b/mlir/lib/Conversion/ArithToLLVM/CMakeLists.txt
@@ -14,6 +14,8 @@ add_mlir_conversion_library(MLIRArithToLLVM
MLIRArithAttrToLLVMConversion
MLIRArithDialect
MLIRArithTransforms
+ MLIRAnalysis
+ MLIRDataLayoutInterfaces
MLIRLLVMCommonConversion
MLIRLLVMDialect
)
diff --git a/mlir/lib/Conversion/ControlFlowToLLVM/CMakeLists.txt b/mlir/lib/Conversion/ControlFlowToLLVM/CMakeLists.txt
index dcc78c8367a43..b36c85165d234 100644
--- a/mlir/lib/Conversion/ControlFlowToLLVM/CMakeLists.txt
+++ b/mlir/lib/Conversion/ControlFlowToLLVM/CMakeLists.txt
@@ -13,6 +13,7 @@ add_mlir_conversion_library(MLIRControlFlowToLLVM
LINK_LIBS PUBLIC
MLIRAnalysis
+ MLIRDataLayoutInterfaces
MLIRControlFlowDialect
MLIRLLVMCommonConversion
MLIRLLVMDialect
diff --git a/mlir/lib/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.cpp b/mlir/lib/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.cpp
index fef78a46d69fc..e3d26cb68859b 100644
--- a/mlir/lib/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.cpp
+++ b/mlir/lib/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.cpp
@@ -13,6 +13,7 @@
#include "mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h"
+#include "mlir/Analysis/DataLayoutAnalysis.h"
#include "mlir/Conversion/ConvertToLLVM/ToLLVMInterface.h"
#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
#include "mlir/Conversion/LLVMCommon/Pattern.h"
@@ -273,11 +274,13 @@ struct ConvertControlFlowToLLVM
ctx->getLoadedDialect<cf::ControlFlowDialect>();
});
- LowerToLLVMOptions options(ctx);
+ const auto &dataLayoutAnalysis = getAnalysis<DataLayoutAnalysis>();
+ LowerToLLVMOptions options(ctx,
+ dataLayoutAnalysis.getAtOrAbove(getOperation()));
if (indexBitwidth != kDeriveIndexBitwidthFromDataLayout)
options.overrideIndexBitwidth(indexBitwidth);
- LLVMTypeConverter converter(ctx, options);
+ LLVMTypeConverter converter(ctx, options, &dataLayoutAnalysis);
RewritePatternSet patterns(ctx);
mlir::cf::populateControlFlowToLLVMConversionPatterns(converter, patterns);
mlir::cf::populateAssertToLLVMConversionPattern(converter, patterns);
diff --git a/mlir/lib/Conversion/VectorToLLVM/CMakeLists.txt b/mlir/lib/Conversion/VectorToLLVM/CMakeLists.txt
index 0d700ea65eb4e..ea1a1de75f9bf 100644
--- a/mlir/lib/Conversion/VectorToLLVM/CMakeLists.txt
+++ b/mlir/lib/Conversion/VectorToLLVM/CMakeLists.txt
@@ -38,6 +38,8 @@ add_mlir_conversion_library(MLIRVectorToLLVMPass
MLIRArmNeonTransforms
MLIRArmSVEDialect
MLIRArmSVETransforms
+ MLIRAnalysis
+ MLIRDataLayoutInterfaces
MLIRX86Dialect
MLIRX86Transforms
)
diff --git a/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp b/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp
index 2b358d312dcfe..d80769ec4dc0b 100644
--- a/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp
+++ b/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.cpp
@@ -8,6 +8,7 @@
#include "mlir/Conversion/VectorToLLVM/ConvertVectorToLLVMPass.h"
+#include "mlir/Analysis/DataLayoutAnalysis.h"
#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
@@ -108,8 +109,13 @@ void ConvertVectorToLLVMPass::runOnOperation() {
}
// Convert to the LLVM IR dialect.
- LowerToLLVMOptions options(&getContext());
- LLVMTypeConverter converter(&getContext(), options);
+ // Use the module's data layout so that the index bitwidth is derived from the
+ // target (e.g. i32 on 32-bit targets) rather than hard-wired to i64, which
+ // mirrors what FinalizeMemRefToLLVMConversionPass does.
+ const auto &dataLayoutAnalysis = getAnalysis<DataLayoutAnalysis>();
+ LowerToLLVMOptions options(&getContext(),
+ dataLayoutAnalysis.getAtOrAbove(getOperation()));
+ LLVMTypeConverter converter(&getContext(), options, &dataLayoutAnalysis);
RewritePatternSet patterns(&getContext());
populateVectorTransferLoweringPatterns(patterns);
populateVectorToLLVMConversionPatterns(
diff --git a/mlir/test/Conversion/ArithToLLVM/arith-to-llvm-32bit-index.mlir b/mlir/test/Conversion/ArithToLLVM/arith-to-llvm-32bit-index.mlir
new file mode 100644
index 0000000000000..85b0324388b8a
--- /dev/null
+++ b/mlir/test/Conversion/ArithToLLVM/arith-to-llvm-32bit-index.mlir
@@ -0,0 +1,74 @@
+// RUN: mlir-opt %s --convert-arith-to-llvm -split-input-file | FileCheck %s
+
+// Verify that ArithToLLVMConversionPass respects the module's data layout
+// when deriving the index type. When the module declares a 32-bit index via
+// dlti.dl_spec, index constants and index arithmetic ops must be emitted as
+// i32 instead of the default i64.
+
+// -----
+
+// 32-bit data layout: arith.constant with index type -> i32 constant.
+
+module attributes { dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<index, 32>> } {
+
+func.func @constant_index_32bit() -> index {
+ %c0 = arith.constant 0 : index
+ return %c0 : index
+}
+
+}
+
+// CHECK-LABEL: func @constant_index_32bit
+// CHECK: llvm.mlir.constant(0 : index) : i32
+
+// -----
+
+// 32-bit data layout: arith.cmpi on index type -> icmp on i32.
+
+module attributes { dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<index, 32>> } {
+
+func.func @cmpi_index_32bit(%a: index, %b: index) -> i1 {
+ %cmp = arith.cmpi slt, %a, %b : index
+ return %cmp : i1
+}
+
+}
+
+// CHECK-LABEL: func @cmpi_index_32bit
+// CHECK: builtin.unrealized_conversion_cast %{{.*}} : index to i32
+// CHECK: builtin.unrealized_conversion_cast %{{.*}} : index to i32
+// CHECK: llvm.icmp "slt" %{{.*}}, %{{.*}} : i32
+
+// -----
+
+// 32-bit data layout: arith.addi on index type -> add on i32.
+
+module attributes { dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<index, 32>> } {
+
+func.func @addi_index_32bit(%a: index, %b: index) -> index {
+ %add = arith.addi %a, %b : index
+ return %add : index
+}
+
+}
+
+// CHECK-LABEL: func @addi_index_32bit
+// CHECK: builtin.unrealized_conversion_cast %{{.*}} : index to i32
+// CHECK: builtin.unrealized_conversion_cast %{{.*}} : index to i32
+// CHECK: llvm.add %{{.*}}, %{{.*}} : i32
+
+// -----
+
+// Without dlti.dl_spec the default index width (i64) is preserved.
+
+module {
+
+func.func @constant_index_default() -> index {
+ %c0 = arith.constant 0 : index
+ return %c0 : index
+}
+
+}
+
+// CHECK-LABEL: func @constant_index_default
+// CHECK: llvm.mlir.constant(0 : index) : i64
diff --git a/mlir/test/Conversion/ControlFlowToLLVM/branch-32bit-index.mlir b/mlir/test/Conversion/ControlFlowToLLVM/branch-32bit-index.mlir
new file mode 100644
index 0000000000000..fcb32bc86a69d
--- /dev/null
+++ b/mlir/test/Conversion/ControlFlowToLLVM/branch-32bit-index.mlir
@@ -0,0 +1,65 @@
+// RUN: mlir-opt %s --convert-cf-to-llvm -split-input-file | FileCheck %s
+
+// Verify that ConvertControlFlowToLLVMPass respects the module's data layout
+// when deriving the index type for block arguments. When the module declares
+// a 32-bit index via dlti.dl_spec, cf.br and cf.cond_br must pass index-typed
+// block arguments as i32 instead of the default i64.
+
+// -----
+
+// 32-bit data layout: cf.br with index block argument -> i32 branch arg.
+
+module attributes { dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<index, 32>> } {
+
+func.func @cf_br_index_32bit(%arg0: index) -> index {
+ cf.br ^bb1(%arg0 : index)
+^bb1(%a: index):
+ return %a : index
+}
+
+}
+
+// CHECK-LABEL: func @cf_br_index_32bit
+// CHECK: builtin.unrealized_conversion_cast %{{.*}} : index to i32
+// CHECK: llvm.br ^{{.*}}(%{{.*}} : i32)
+// CHECK: ^{{.*}}(%{{.*}}: i32):
+
+// -----
+
+// 32-bit data layout: cf.cond_br with index block arguments -> i32 branch args.
+
+module attributes { dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<index, 32>> } {
+
+func.func @cf_cond_br_index_32bit(%cond: i1, %a: index, %b: index) -> index {
+ cf.cond_br %cond, ^bb1(%a : index), ^bb2(%b : index)
+^bb1(%x: index):
+ return %x : index
+^bb2(%y: index):
+ return %y : index
+}
+
+}
+
+// CHECK-LABEL: func @cf_cond_br_index_32bit
+// CHECK: builtin.unrealized_conversion_cast %{{.*}} : index to i32
+// CHECK: builtin.unrealized_conversion_cast %{{.*}} : index to i32
+// CHECK: llvm.cond_br %{{.*}}, ^{{.*}}(%{{.*}} : i32), ^{{.*}}(%{{.*}} : i32)
+
+// -----
+
+// Without dlti.dl_spec the default index width (i64) is preserved.
+
+module {
+
+func.func @cf_br_index_default(%arg0: index) -> index {
+ cf.br ^bb1(%arg0 : index)
+^bb1(%a: index):
+ return %a : index
+}
+
+}
+
+// CHECK-LABEL: func @cf_br_index_default
+// CHECK: builtin.unrealized_conversion_cast %{{.*}} : index to i64
+// CHECK: llvm.br ^{{.*}}(%{{.*}} : i64)
+// CHECK: ^{{.*}}(%{{.*}}: i64):
diff --git a/mlir/test/Conversion/VectorToLLVM/vector-load-store-32bit-index-to-llvm.mlir b/mlir/test/Conversion/VectorToLLVM/vector-load-store-32bit-index-to-llvm.mlir
new file mode 100644
index 0000000000000..29496be37322f
--- /dev/null
+++ b/mlir/test/Conversion/VectorToLLVM/vector-load-store-32bit-index-to-llvm.mlir
@@ -0,0 +1,76 @@
+// RUN: mlir-opt %s -convert-vector-to-llvm -split-input-file | FileCheck %s --check-prefixes=ALL,DEFAULT
+// RUN: mlir-opt %s -convert-vector-to-llvm='enable-gep-inbounds-nuw=1' -split-input-file | FileCheck %s --check-prefixes=ALL,INBOUNDS
+
+// Verify that ConvertVectorToLLVMPass respects the module's data layout when
+// deriving the index type. When the module declares a 32-bit index (via
+// dlti.dl_spec), GEP arithmetic should be emitted in i32 instead of the
+// default i64. This also makes enable-gep-inbounds-nuw meaningful on 32-bit
+// targets: the flag emits nsw/nuw on the *narrow* i32 multiply, allowing SCEV
+// to form a clean 32-bit AddRec and enabling LSR to use direct pointer
+// induction instead of per-iteration sign/zero extension.
+
+// -----
+
+// 32-bit data layout: index -> i32 in GEP arithmetic.
+
+module attributes { dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<index, 32>> } {
+
+func.func @load_32bit_index(%memref : memref<200x100xf32>, %i : index, %j : index) -> vector<8xf32> {
+ %0 = vector.load %memref[%i, %j] : memref<200x100xf32>, vector<8xf32>
+ return %0 : vector<8xf32>
+}
+
+}
+
+// ALL-LABEL: func @load_32bit_index
+// ALL: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i32
+// ALL: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]]
+// ALL: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}}
+// DEFAULT: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i32) -> !llvm.ptr, f32
+// INBOUNDS: %[[GEP:.*]] = llvm.getelementptr inbounds|nuw %{{.*}}[%[[ADD]]] : (!llvm.ptr, i32) -> !llvm.ptr, f32
+// ALL: llvm.load %[[GEP]] {alignment = 4 : i64} : !llvm.ptr -> vector<8xf32>
+
+// -----
+
+// With enable-gep-inbounds-nuw, the narrow i32 multiply and add carry
+// nsw/nuw flags, enabling SCEV to form a clean 32-bit AddRec.
+
+module attributes { dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<index, 32>> } {
+
+func.func @load_32bit_index_nuw_mul(%memref : memref<200x100xf32>, %i : index, %j : index) -> vector<8xf32> {
+ %0 = vector.load %memref[%i, %j] : memref<200x100xf32>, vector<8xf32>
+ return %0 : vector<8xf32>
+}
+
+}
+
+// DEFAULT-LABEL: func @load_32bit_index_nuw_mul
+// DEFAULT: llvm.mul %{{.*}}, %{{.*}} : i32
+// DEFAULT: llvm.add %{{.*}}, %{{.*}} : i32
+// DEFAULT: llvm.getelementptr %{{.*}} : (!llvm.ptr, i32) -> !llvm.ptr, f32
+
+// INBOUNDS-LABEL: func @load_32bit_index_nuw_mul
+// INBOUNDS: llvm.mul %{{.*}}, %{{.*}} overflow<nsw, nuw> : i32
+// INBOUNDS: llvm.add %{{.*}}, %{{.*}} overflow<nsw, nuw> : i32
+// INBOUNDS: llvm.getelementptr inbounds|nuw %{{.*}} : (!llvm.ptr, i32) -> !llvm.ptr, f32
+
+// -----
+
+// Same test for store.
+
+module attributes { dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<index, 32>> } {
+
+func.func @store_32bit_index(%memref : memref<200x100xf32>, %i : index, %j : index, %val : vector<8xf32>) {
+ vector.store %val, %memref[%i, %j] : memref<200x100xf32>, vector<8xf32>
+ return
+}
+
+}
+
+// ALL-LABEL: func @store_32bit_index
+// ALL: %[[C100:.*]] = llvm.mlir.constant(100 : index) : i32
+// ALL: %[[MUL:.*]] = llvm.mul %{{.*}}, %[[C100]]
+// ALL: %[[ADD:.*]] = llvm.add %[[MUL]], %{{.*}}
+// DEFAULT: %[[GEP:.*]] = llvm.getelementptr %{{.*}}[%[[ADD]]] : (!llvm.ptr, i32) -> !llvm.ptr, f32
+// INBOUNDS: %[[GEP:.*]] = llvm.getelementptr inbounds|nuw %{{.*}}[%[[ADD]]] : (!llvm.ptr, i32) -> !llvm.ptr, f32
+// ALL: llvm.store %{{.*}}, %[[GEP]] {alignment = 4 : i64} : vector<8xf32>, !llvm.ptr
``````````
</details>
https://github.com/llvm/llvm-project/pull/206380
More information about the Mlir-commits
mailing list