[llvm] [GVN] Assign unique VNs to calls with operand bundles (PR #211784)

Momchil Velikov via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 25 02:33:11 PDT 2026


https://github.com/momchil-velikov updated https://github.com/llvm/llvm-project/pull/211784

>From 5e6949365fafa0b7146e623772018b35c644b363 Mon Sep 17 00:00:00 2001
From: Momchil Velikov <momchil.velikov at arm.com>
Date: Wed, 15 Jul 2026 15:41:09 +0100
Subject: [PATCH] [GVN] Assign unique VNs to calls with operand bundles

Call instructions with operand bundles may be assigned the same value number,
even if operand bundles differ. The GVN may eliminate one of the calls in favour
of another and drop one of the operand bundles.

Work around this by assigning unique value numbers to calls with operand
bundles.
---
 llvm/lib/Transforms/Scalar/GVN.cpp            |  8 +++
 .../GVN/operand-bundle-unique-vn.ll           | 70 +++++++++++++++++++
 2 files changed, 78 insertions(+)
 create mode 100644 llvm/test/Transforms/GVN/operand-bundle-unique-vn.ll

diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index ddd8e62f393ee..bd3fceb1ff3d2 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -509,6 +509,14 @@ uint32_t GVNPass::ValueTable::lookupOrAddCall(CallInst *C) {
     return NextValueNumber++;
   }
 
+  // Conservatively assign unique value numbers to calls with operand bundles.
+  // TODO: Bundle names could be included in the value numbering expression to
+  // allow combining calls with identical bundles.
+  if (C->hasOperandBundles()) {
+    ValueNumbering[C] = NextValueNumber;
+    return NextValueNumber++;
+  }
+
   if (AA->doesNotAccessMemory(C)) {
     Expression Exp = createExpr(C);
     uint32_t E = assignExpNewValueNum(Exp).first;
diff --git a/llvm/test/Transforms/GVN/operand-bundle-unique-vn.ll b/llvm/test/Transforms/GVN/operand-bundle-unique-vn.ll
new file mode 100644
index 0000000000000..b79512d84e93c
--- /dev/null
+++ b/llvm/test/Transforms/GVN/operand-bundle-unique-vn.ll
@@ -0,0 +1,70 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S  -p gvn < %s | FileCheck %s
+
+
+; Check that the calls are not CSEd because they have different operand bundle
+; tags.
+define i32 @no_drop_bundle(ptr %p) {
+; CHECK-LABEL: define i32 @no_drop_bundle(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[U:%.*]] = call i32 @g(i1 true) #[[ATTR1:[0-9]+]] [ "foo"(ptr [[P]]) ]
+; CHECK-NEXT:    [[V:%.*]] = call i32 @g(i1 true) #[[ATTR1]] [ "bar"(ptr [[P]]) ]
+; CHECK-NEXT:    [[W:%.*]] = add i32 [[U]], [[V]]
+; CHECK-NEXT:    ret i32 [[W]]
+;
+  %u = call i32 @g(i1 true) memory(none) ["foo"(ptr %p)]
+  %v = call i32 @g(i1 true) memory(none) ["bar"(ptr %p)]
+  %w = add i32 %u, %v
+  ret i32 %w
+}
+
+; Check that the calls are not CSEd because they have different operand bundle
+; arguments.
+define i32 @diff_args(ptr %p, ptr %q) {
+; CHECK-LABEL: define i32 @diff_args(
+; CHECK-SAME: ptr [[P:%.*]], ptr [[Q:%.*]]) {
+; CHECK-NEXT:    [[U:%.*]] = call i32 @g(i1 true) #[[ATTR1]] [ "foo"(ptr [[P]]) ]
+; CHECK-NEXT:    [[V:%.*]] = call i32 @g(i1 true) #[[ATTR1]] [ "foo"(ptr [[Q]]) ]
+; CHECK-NEXT:    [[W:%.*]] = add i32 [[U]], [[V]]
+; CHECK-NEXT:    ret i32 [[W]]
+;
+  %u = call i32 @g(i1 true) memory(none) ["foo"(ptr %p)]
+  %v = call i32 @g(i1 true) memory(none) ["foo"(ptr %q)]
+  %w = add i32 %u, %v
+  ret i32 %w
+}
+
+; Check that the calls are not CSEd because they access memory in unknown ways.
+define i32 @mem_access(ptr %p) {
+; CHECK-LABEL: define i32 @mem_access(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[U:%.*]] = call i32 @g(i1 true) [ "foo"(ptr [[P]]) ]
+; CHECK-NEXT:    [[V:%.*]] = call i32 @g(i1 true) [ "foo"(ptr [[P]]) ]
+; CHECK-NEXT:    [[W:%.*]] = add i32 [[U]], [[V]]
+; CHECK-NEXT:    ret i32 [[W]]
+;
+  %u = call i32 @g(i1 true) ["foo"(ptr %p)]
+  %v = call i32 @g(i1 true) ["foo"(ptr %p)]
+  %w = add i32 %u, %v
+  ret i32 %w
+}
+
+; Check that the calls are CSEd because of the conservative treatment of operand
+; bundles.
+; TODO: Perhaps this can be made more precise by including the bundle name in
+; the value numbering expression.
+define i32 @no_mem_access(ptr %p) {
+; CHECK-LABEL: define i32 @no_mem_access(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[U:%.*]] = call i32 @g(i1 true) #[[ATTR1]] [ "align"(ptr [[P]], i32 8) ]
+; CHECK-NEXT:    [[V:%.*]] = call i32 @g(i1 true) #[[ATTR1]] [ "align"(ptr [[P]], i32 8) ]
+; CHECK-NEXT:    [[W:%.*]] = add i32 [[U]], [[V]]
+; CHECK-NEXT:    ret i32 [[W]]
+;
+  %u = call i32 @g(i1 true) memory(none) ["align"(ptr %p, i32 8)]
+  %v = call i32 @g(i1 true) memory(none) ["align"(ptr %p, i32 8)]
+  %w = add i32 %u, %v
+  ret i32 %w
+}
+
+declare void @g(i1) nounwind willreturn



More information about the llvm-commits mailing list