[llvm] [RISCV] Rewrite ADD to SUB to reuse a materialized negated constant (PR #221027)

Pengcheng Wang via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 9 22:47:18 PDT 2026


https://github.com/wangpc-pp updated https://github.com/llvm/llvm-project/pull/221027

>From f521f98866f88d041abeb145c2a477d5932b252a Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Fri, 4 Sep 2026 03:42:58 +0800
Subject: [PATCH 01/11] [RISCV] Add precommit test for reusing materialized
 negated constants

Adds tests for llvm#220555 where an ADD needs a large constant whose
negation is already materialized elsewhere (e.g. the 0x0101010101010101
constant feeding a multiply). Today the negated constant is built a second
time instead of reusing the existing register via SUB.

Assisted-by: TRAE CLI (Opus 4.8)
Co-authored-by: TRAE CLI <traecli at bytedance.com>
---
 llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll | 60 ++++++++++++++++++++-
 1 file changed, 58 insertions(+), 2 deletions(-)

diff --git a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
index a69b13e0ca450..fe1d327a00617 100644
--- a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
+++ b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
@@ -1,6 +1,6 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
-; RUN: llc -mtriple=riscv64 < %s | FileCheck %s --check-prefixes=CHECK,NOZBS
-; RUN: llc -mtriple=riscv64 -mattr=+zbs < %s | FileCheck %s --check-prefixes=CHECK,ZBS
+; RUN: llc -mtriple=riscv64 -mattr=+m,+zbb < %s | FileCheck %s --check-prefixes=CHECK,NOZBS
+; RUN: llc -mtriple=riscv64 -mattr=+m,+zbb,+zbs < %s | FileCheck %s --check-prefixes=CHECK,ZBS
 
 define i64 @add_b31(i64 %x) {
 ; NOZBS-LABEL: add_b31:
@@ -92,3 +92,59 @@ define i64 @add_i64_min(i64 %x) {
   %a = add i64 %x, -9223372036854775808
   ret i64 %a
 }
+
+; (add X, C) can reuse a register that already holds -C by rewriting to
+; (sub X, -C). Here -C = 0x0101010101010101 is materialized for the multiply
+; and the addition reuses it instead of building the constant a second time.
+; The two operands are independent to keep the x*C - C -> C*(x-1) fold from
+; firing.
+define i64 @reuse_mul_splat_const(i64 %x, i64 %y) {
+; CHECK-LABEL: reuse_mul_splat_const:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 4112
+; CHECK-NEXT:    addi a2, a2, 257
+; CHECK-NEXT:    slli a3, a2, 32
+; CHECK-NEXT:    add a2, a2, a3
+; CHECK-NEXT:    mul a0, a0, a2
+; CHECK-NEXT:    lui a2, 1044464
+; CHECK-NEXT:    addi a2, a2, -257
+; CHECK-NEXT:    slli a3, a2, 32
+; CHECK-NEXT:    add a2, a2, a3
+; CHECK-NEXT:    add a1, a1, a2
+; CHECK-NEXT:    add a0, a1, a0
+; CHECK-NEXT:    ret
+  %mul = mul i64 %x, 72340172838076673
+  %add = add i64 %y, -72340172838076673
+  %res = add i64 %mul, %add
+  ret i64 %res
+}
+
+; Same reuse when the constant also feeds a longer and/xor chain (llvm#220555).
+define i64 @reuse_neg_const_chain(i64 %ctrl, i8 zeroext %h) {
+; CHECK-LABEL: reuse_neg_const_chain:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 4112
+; CHECK-NEXT:    addi a2, a2, 257
+; CHECK-NEXT:    slli a3, a2, 32
+; CHECK-NEXT:    add a2, a2, a3
+; CHECK-NEXT:    mul a1, a1, a2
+; CHECK-NEXT:    lui a2, 1044464
+; CHECK-NEXT:    addi a2, a2, -257
+; CHECK-NEXT:    lui a3, %hi(.LCPI7_0)
+; CHECK-NEXT:    slli a4, a2, 32
+; CHECK-NEXT:    ld a3, %lo(.LCPI7_0)(a3)
+; CHECK-NEXT:    xor a0, a1, a0
+; CHECK-NEXT:    add a2, a2, a4
+; CHECK-NEXT:    add a2, a0, a2
+; CHECK-NEXT:    andn a0, a2, a0
+; CHECK-NEXT:    and a0, a0, a3
+; CHECK-NEXT:    ret
+  %conv = zext i8 %h to i64
+  %mul = mul nuw i64 %conv, 72340172838076673
+  %xor = xor i64 %mul, %ctrl
+  %sub = add i64 %xor, -72340172838076673
+  %not = and i64 %xor, -9187201950435737472
+  %and = xor i64 %not, -9187201950435737472
+  %and1 = and i64 %and, %sub
+  ret i64 %and1
+}

>From e423615c9c365cc3188aee839c8a31393cdfd59f Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Fri, 4 Sep 2026 03:49:41 +0800
Subject: [PATCH 02/11] [RISCV] Rewrite ADD to SUB to reuse a materialized
 negated constant

When an ADD needs a large constant whose negation is already present in the
DAG, rewrite (add X, C) into (sub X, -C) so both share one materialization
of -C instead of building it twice.

selectNegImm already does a related rewrite, but only when -C is strictly
cheaper than C and during instruction selection, where the sibling constant
may already have been selected into instructions. Do it in
PreprocessISelDAG instead, before selection, while both are still
ConstantSDNodes that getConstant can unique together. The rewrite only
fires when -C is no more expensive than C and a node holding -C already
exists, so it never adds a materialization.

Fixes https://github.com/llvm/llvm-project/issues/220555

Assisted-by: TRAE CLI (Opus 4.8)
Co-authored-by: TRAE CLI <traecli at bytedance.com>
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 41 +++++++++++++++++++++
 llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll | 14 ++-----
 2 files changed, 44 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index d263d0320839b..dbbd6127922e7 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -128,6 +128,47 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
                                TrueMask, VLMAX);
       break;
     }
+    case ISD::ADD: {
+      // Turn (add X, C) into (sub X, -C) when a constant node holding -C
+      // already exists in the DAG, so both share one materialization. Do this
+      // before selection, while both are still ConstantSDNodes: by selection
+      // time -C may already have been selected into instructions.
+      //
+      // ADD is commutative, but getNode canonicalizes constants to the RHS, so
+      // the constant is always operand 1.
+      auto *N1C = dyn_cast<ConstantSDNode>(N->getOperand(1));
+      if (!N1C)
+        break;
+      MVT VT = N->getSimpleValueType(0);
+      if (VT != Subtarget->getXLenVT())
+        break;
+      int64_t Imm = N1C->getSExtValue();
+      // Only worthwhile for wide constants: values that fit in 32 bits take at
+      // most two instructions to materialize, matching the threshold used by
+      // selectNegImm. Skip INT64_MIN too, whose negation is itself.
+      if (isInt<32>(Imm) || Imm == INT64_MIN)
+        break;
+      // Reusing a register is free, so require only that -C is no more
+      // expensive to materialize than C.
+      int OrigCost = RISCVMatInt::getIntMatCost(APInt(64, Imm), 64, *Subtarget,
+                                                /*CompressionCost=*/true);
+      int NegCost = RISCVMatInt::getIntMatCost(APInt(64, -Imm), 64, *Subtarget,
+                                               /*CompressionCost=*/true);
+      if (NegCost > OrigCost)
+        break;
+      // Only rewrite when -C already exists, else this just moves the cost.
+      bool HasNegConst = any_of(CurDAG->allnodes(), [&](const SDNode &Node) {
+        auto *C = dyn_cast<ConstantSDNode>(&Node);
+        return C && C->getSimpleValueType(0) == VT && C->getSExtValue() == -Imm;
+      });
+      if (!HasNegConst)
+        break;
+      SDLoc DL(N);
+      // getConstant uniques onto the existing -C node, so it is shared.
+      Result = CurDAG->getNode(ISD::SUB, DL, VT, N->getOperand(0),
+                               CurDAG->getConstant(-Imm, DL, VT));
+      break;
+    }
     }
 
     if (Result) {
diff --git a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
index fe1d327a00617..4246fde36f0be 100644
--- a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
+++ b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
@@ -106,11 +106,7 @@ define i64 @reuse_mul_splat_const(i64 %x, i64 %y) {
 ; CHECK-NEXT:    slli a3, a2, 32
 ; CHECK-NEXT:    add a2, a2, a3
 ; CHECK-NEXT:    mul a0, a0, a2
-; CHECK-NEXT:    lui a2, 1044464
-; CHECK-NEXT:    addi a2, a2, -257
-; CHECK-NEXT:    slli a3, a2, 32
-; CHECK-NEXT:    add a2, a2, a3
-; CHECK-NEXT:    add a1, a1, a2
+; CHECK-NEXT:    sub a1, a1, a2
 ; CHECK-NEXT:    add a0, a1, a0
 ; CHECK-NEXT:    ret
   %mul = mul i64 %x, 72340172838076673
@@ -128,15 +124,11 @@ define i64 @reuse_neg_const_chain(i64 %ctrl, i8 zeroext %h) {
 ; CHECK-NEXT:    slli a3, a2, 32
 ; CHECK-NEXT:    add a2, a2, a3
 ; CHECK-NEXT:    mul a1, a1, a2
-; CHECK-NEXT:    lui a2, 1044464
-; CHECK-NEXT:    addi a2, a2, -257
 ; CHECK-NEXT:    lui a3, %hi(.LCPI7_0)
-; CHECK-NEXT:    slli a4, a2, 32
 ; CHECK-NEXT:    ld a3, %lo(.LCPI7_0)(a3)
 ; CHECK-NEXT:    xor a0, a1, a0
-; CHECK-NEXT:    add a2, a2, a4
-; CHECK-NEXT:    add a2, a0, a2
-; CHECK-NEXT:    andn a0, a2, a0
+; CHECK-NEXT:    sub a1, a0, a2
+; CHECK-NEXT:    andn a0, a1, a0
 ; CHECK-NEXT:    and a0, a0, a3
 ; CHECK-NEXT:    ret
   %conv = zext i8 %h to i64

>From c1aa9c53a9f7e9db79be69e1bd84c5680e80bfd7 Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Fri, 4 Sep 2026 12:18:04 +0800
Subject: [PATCH 03/11] [RISCV] Add test for reusing a constant added and
 subtracted on other values

Precommit test for the case Craig Topper raised in PR #221027: a constant is
anchored by a multiply and appears both added to one value and subtracted
(add of -C) from another. Only C should be materialized, but the ADD->SUB
rewrite currently also flips the add of C to (sub X, -C), materializing -C as
well.

Assisted-by: TRAE CLI (Opus 4.8)
---
 llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll | 30 +++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
index 4246fde36f0be..e419930ab74a1 100644
--- a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
+++ b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
@@ -140,3 +140,33 @@ define i64 @reuse_neg_const_chain(i64 %ctrl, i8 zeroext %h) {
   %and1 = and i64 %and, %sub
   ret i64 %and1
 }
+
+; C is anchored by the multiply, and it is added on one operand and subtracted
+; (via add of -C) on another. Only C needs to be materialized: the add of C
+; reuses it directly and the add of -C should become (sub X, C). It must not
+; instead flip the add of C to (sub X, -C), which would materialize -C too.
+define i64 @add_pos_and_neg_const(i64 %x, i64 %a, i64 %b) {
+; CHECK-LABEL: add_pos_and_neg_const:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a3, 4112
+; CHECK-NEXT:    addi a3, a3, 257
+; CHECK-NEXT:    slli a4, a3, 32
+; CHECK-NEXT:    add a3, a3, a4
+; CHECK-NEXT:    mul a0, a0, a3
+; CHECK-NEXT:    lui a4, 1044464
+; CHECK-NEXT:    addi a4, a4, -257
+; CHECK-NEXT:    slli a5, a4, 32
+; CHECK-NEXT:    sub a1, a1, a4
+; CHECK-NEXT:    sub a1, a1, a5
+; CHECK-NEXT:    sub a2, a2, a3
+; CHECK-NEXT:    xor a1, a1, a2
+; CHECK-NEXT:    xor a0, a0, a1
+; CHECK-NEXT:    ret
+  %mul = mul i64 %x, 72340172838076673
+  %pa = add i64 %a, 72340172838076673
+  %na = add i64 %b, -72340172838076673
+  %o1 = xor i64 %mul, %pa
+  %o2 = xor i64 %o1, %na
+  ret i64 %o2
+}
+

>From 0de89c9e36e4f48adbb5bdde436d514c811628ee Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Fri, 4 Sep 2026 12:19:29 +0800
Subject: [PATCH 04/11] [RISCV] Only reuse a negated constant anchored outside
 an ADD

The ADD->SUB rewrite reused any existing -C constant node, but if -C was
only used by other ADDs, those could equally be rewritten to reuse C, and
forcing -C here left both C and -C materialized (raised by Craig Topper in
PR #221027).

Require that -C has at least one non-ADD user, so it is materialized
regardless of this transform and the rewrite only shares an existing value.

Assisted-by: TRAE CLI (Opus 4.8)
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 11 +++++++++--
 llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll |  6 +-----
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index dbbd6127922e7..62d641dff5bb7 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -156,10 +156,17 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
                                                /*CompressionCost=*/true);
       if (NegCost > OrigCost)
         break;
-      // Only rewrite when -C already exists, else this just moves the cost.
+      // Only rewrite when -C is anchored by a use that is not itself an ADD, so
+      // -C is materialized regardless of this transform. If -C were only used
+      // by other ADDs, each of those could be rewritten to use C instead, and
+      // forcing -C here would leave both C and -C materialized.
       bool HasNegConst = any_of(CurDAG->allnodes(), [&](const SDNode &Node) {
         auto *C = dyn_cast<ConstantSDNode>(&Node);
-        return C && C->getSimpleValueType(0) == VT && C->getSExtValue() == -Imm;
+        if (!C || C->getSimpleValueType(0) != VT || C->getSExtValue() != -Imm)
+          return false;
+        return any_of(Node.users(), [](const SDNode *U) {
+          return U->getOpcode() != ISD::ADD;
+        });
       });
       if (!HasNegConst)
         break;
diff --git a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
index e419930ab74a1..ada85b2b722aa 100644
--- a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
+++ b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
@@ -153,11 +153,7 @@ define i64 @add_pos_and_neg_const(i64 %x, i64 %a, i64 %b) {
 ; CHECK-NEXT:    slli a4, a3, 32
 ; CHECK-NEXT:    add a3, a3, a4
 ; CHECK-NEXT:    mul a0, a0, a3
-; CHECK-NEXT:    lui a4, 1044464
-; CHECK-NEXT:    addi a4, a4, -257
-; CHECK-NEXT:    slli a5, a4, 32
-; CHECK-NEXT:    sub a1, a1, a4
-; CHECK-NEXT:    sub a1, a1, a5
+; CHECK-NEXT:    add a1, a1, a3
 ; CHECK-NEXT:    sub a2, a2, a3
 ; CHECK-NEXT:    xor a1, a1, a2
 ; CHECK-NEXT:    xor a0, a0, a1

>From a9679dd5d7eaa0bbdbe346f382bdb2277893dbcb Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Mon, 7 Sep 2026 12:24:07 +0800
Subject: [PATCH 05/11] [RISCV] Add test for a constant added and subtracted
 with no other anchor

A constant is added on one value and subtracted (add of -C) on another with
nothing else forcing either constant. Only one of C/-C need be materialized,
but the anchor requirement added earlier skips the rewrite entirely, so both
are built.

Assisted-by: TRAE CLI (Opus 4.8)
---
 llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll | 25 +++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
index ada85b2b722aa..3d3a81bd63e9f 100644
--- a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
+++ b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
@@ -166,3 +166,28 @@ define i64 @add_pos_and_neg_const(i64 %x, i64 %a, i64 %b) {
   ret i64 %o2
 }
 
+; C is added on one value and subtracted (add of -C) on another, with no other
+; use anchoring either constant. Only one of C/-C should be materialized: one
+; ADD keeps C and the other becomes (sub X, C).
+define i64 @add_pos_and_neg_const_no_anchor(i64 %a, i64 %b) {
+; CHECK-LABEL: add_pos_and_neg_const_no_anchor:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 4112
+; CHECK-NEXT:    addi a2, a2, 257
+; CHECK-NEXT:    lui a3, 1044464
+; CHECK-NEXT:    add a0, a0, a2
+; CHECK-NEXT:    slli a2, a2, 32
+; CHECK-NEXT:    addi a3, a3, -257
+; CHECK-NEXT:    add a1, a1, a3
+; CHECK-NEXT:    slli a3, a3, 32
+; CHECK-NEXT:    add a0, a0, a2
+; CHECK-NEXT:    add a1, a1, a3
+; CHECK-NEXT:    xor a0, a0, a1
+; CHECK-NEXT:    ret
+  %pa = add i64 %a, 72340172838076673
+  %na = add i64 %b, -72340172838076673
+  %o = xor i64 %pa, %na
+  ret i64 %o
+}
+
+

>From c702026ec0a4d1f4ef0a5112edc085120109930b Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Mon, 7 Sep 2026 12:25:30 +0800
Subject: [PATCH 06/11] [RISCV] Materialize only one of a
 constant/negated-constant pair

The anchor requirement stopped the ADD->SUB rewrite from creating a second
constant, but it also gave up on the case where a constant is added on one
value and subtracted (add of -C) on another with nothing else materializing
either: both C and -C were then built.

Instead of only checking whether -C is anchored, look at both C and -C and
pick a single survivor: reuse whichever is materialized anyway, or, if
neither is, the cheaper constant, breaking ties towards the positive value so
both ADDs of a pair agree. The ADD keeping the survivor is left alone and the
other is rewritten to SUB, so exactly one constant is materialized.

Assisted-by: TRAE CLI (Opus 4.8)
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 59 ++++++++++++++-------
 llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll | 10 ++--
 2 files changed, 44 insertions(+), 25 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 62d641dff5bb7..0a4ed0deb0418 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -148,27 +148,50 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
       // selectNegImm. Skip INT64_MIN too, whose negation is itself.
       if (isInt<32>(Imm) || Imm == INT64_MIN)
         break;
-      // Reusing a register is free, so require only that -C is no more
-      // expensive to materialize than C.
-      int OrigCost = RISCVMatInt::getIntMatCost(APInt(64, Imm), 64, *Subtarget,
-                                                /*CompressionCost=*/true);
-      int NegCost = RISCVMatInt::getIntMatCost(APInt(64, -Imm), 64, *Subtarget,
-                                               /*CompressionCost=*/true);
-      if (NegCost > OrigCost)
-        break;
-      // Only rewrite when -C is anchored by a use that is not itself an ADD, so
-      // -C is materialized regardless of this transform. If -C were only used
-      // by other ADDs, each of those could be rewritten to use C instead, and
-      // forcing -C here would leave both C and -C materialized.
-      bool HasNegConst = any_of(CurDAG->allnodes(), [&](const SDNode &Node) {
+      // Look for existing constant nodes for Imm and -Imm, and whether either
+      // has a user other than an ADD, i.e. is materialized regardless of this
+      // fold.
+      bool NegExists = false, NegAnchored = false, PosAnchored = false;
+      for (const SDNode &Node : CurDAG->allnodes()) {
         auto *C = dyn_cast<ConstantSDNode>(&Node);
-        if (!C || C->getSimpleValueType(0) != VT || C->getSExtValue() != -Imm)
-          return false;
-        return any_of(Node.users(), [](const SDNode *U) {
+        if (!C || C->getSimpleValueType(0) != VT)
+          continue;
+        int64_t V = C->getSExtValue();
+        if (V != Imm && V != -Imm)
+          continue;
+        bool NonAddUser = any_of(Node.users(), [](const SDNode *U) {
           return U->getOpcode() != ISD::ADD;
         });
-      });
-      if (!HasNegConst)
+        if (V == -Imm) {
+          NegExists = true;
+          NegAnchored |= NonAddUser;
+        } else {
+          PosAnchored |= NonAddUser;
+        }
+      }
+      // Reuse is only free if -Imm is already in the DAG.
+      if (!NegExists)
+        break;
+      // Pick which of Imm/-Imm should be the surviving constant, so exactly
+      // one of the pair is materialized and any ADDs of the other reuse it:
+      //  - if -Imm is materialized anyway, reuse it (rewrite to SUB);
+      //  - else if Imm is materialized anyway, keep the ADD so it reuses Imm;
+      //  - else keep the cheaper constant, breaking ties towards the positive
+      //    value so both ADDs of a C/-C pair agree on the survivor.
+      bool Rewrite;
+      if (NegAnchored)
+        Rewrite = true;
+      else if (PosAnchored)
+        Rewrite = false;
+      else {
+        int PosCost = RISCVMatInt::getIntMatCost(APInt(64, Imm), 64, *Subtarget,
+                                                 /*CompressionCost=*/true);
+        int NegCost =
+            RISCVMatInt::getIntMatCost(APInt(64, -Imm), 64, *Subtarget,
+                                       /*CompressionCost=*/true);
+        Rewrite = NegCost != PosCost ? NegCost < PosCost : Imm < 0;
+      }
+      if (!Rewrite)
         break;
       SDLoc DL(N);
       // getConstant uniques onto the existing -C node, so it is shared.
diff --git a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
index 3d3a81bd63e9f..9a0e1dcbad3f1 100644
--- a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
+++ b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
@@ -174,14 +174,10 @@ define i64 @add_pos_and_neg_const_no_anchor(i64 %a, i64 %b) {
 ; CHECK:       # %bb.0:
 ; CHECK-NEXT:    lui a2, 4112
 ; CHECK-NEXT:    addi a2, a2, 257
-; CHECK-NEXT:    lui a3, 1044464
-; CHECK-NEXT:    add a0, a0, a2
-; CHECK-NEXT:    slli a2, a2, 32
-; CHECK-NEXT:    addi a3, a3, -257
-; CHECK-NEXT:    add a1, a1, a3
-; CHECK-NEXT:    slli a3, a3, 32
+; CHECK-NEXT:    slli a3, a2, 32
+; CHECK-NEXT:    add a2, a2, a3
 ; CHECK-NEXT:    add a0, a0, a2
-; CHECK-NEXT:    add a1, a1, a3
+; CHECK-NEXT:    sub a1, a1, a2
 ; CHECK-NEXT:    xor a0, a0, a1
 ; CHECK-NEXT:    ret
   %pa = add i64 %a, 72340172838076673

>From f9dbc3c04b0b21db98e91accbbefb042b9ea6f2e Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Tue, 8 Sep 2026 11:40:37 +0800
Subject: [PATCH 07/11] [RISCV] Add test for add of a constant when both
 constants are anchored

Assisted-by: TRAE CLI (Opus 4.8)
---
 llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll | 29 +++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
index 9a0e1dcbad3f1..8b8edf203f86f 100644
--- a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
+++ b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
@@ -186,4 +186,33 @@ define i64 @add_pos_and_neg_const_no_anchor(i64 %a, i64 %b) {
   ret i64 %o
 }
 
+; Both C and -C are materialized anyway (each anchors a multiply), and C is
+; also added on a third value. Rewriting that add to (sub X, -C) removes no
+; constant, so it should be left as an ADD, which is also more compressible.
+define i64 @add_both_const_anchored(i64 %x, i64 %y, i64 %a) {
+; CHECK-LABEL: add_both_const_anchored:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a3, 4112
+; CHECK-NEXT:    lui a4, 1044464
+; CHECK-NEXT:    addi a3, a3, 257
+; CHECK-NEXT:    addi a4, a4, -257
+; CHECK-NEXT:    slli a5, a3, 32
+; CHECK-NEXT:    slli a6, a4, 32
+; CHECK-NEXT:    add a3, a3, a5
+; CHECK-NEXT:    add a4, a4, a6
+; CHECK-NEXT:    mul a0, a0, a3
+; CHECK-NEXT:    mul a1, a1, a4
+; CHECK-NEXT:    sub a2, a2, a4
+; CHECK-NEXT:    xor a0, a0, a1
+; CHECK-NEXT:    xor a0, a0, a2
+; CHECK-NEXT:    ret
+  %m1 = mul i64 %x, 72340172838076673
+  %m2 = mul i64 %y, -72340172838076673
+  %pa = add i64 %a, 72340172838076673
+  %o1 = xor i64 %m1, %m2
+  %o2 = xor i64 %o1, %pa
+  ret i64 %o2
+}
+
+
 

>From c12190d8568e151f09c3476aba7c78f80658754f Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Tue, 8 Sep 2026 11:42:37 +0800
Subject: [PATCH 08/11] [RISCV] Prefer keeping the ADD when the positive
 constant is anchored

Give the "positive constant already materialized" case priority over the
negated one: if Imm is materialized regardless, keep the ADD so it reuses
Imm rather than rewriting to a SUB of -Imm, which removes no constant and
is less compressible than an ADD.

Also tighten the scan: check the positive side directly on N1C, and stop
at the first constant node for -Imm, so the non-ADD-user test runs at
most twice.

Assisted-by: TRAE CLI (Opus 4.8)
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 45 ++++++++++-----------
 llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll |  2 +-
 2 files changed, 23 insertions(+), 24 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 0a4ed0deb0418..4b39c6a2b58c2 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -148,41 +148,40 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
       // selectNegImm. Skip INT64_MIN too, whose negation is itself.
       if (isInt<32>(Imm) || Imm == INT64_MIN)
         break;
-      // Look for existing constant nodes for Imm and -Imm, and whether either
-      // has a user other than an ADD, i.e. is materialized regardless of this
-      // fold.
-      bool NegExists = false, NegAnchored = false, PosAnchored = false;
-      for (const SDNode &Node : CurDAG->allnodes()) {
-        auto *C = dyn_cast<ConstantSDNode>(&Node);
-        if (!C || C->getSimpleValueType(0) != VT)
-          continue;
-        int64_t V = C->getSExtValue();
-        if (V != Imm && V != -Imm)
-          continue;
-        bool NonAddUser = any_of(Node.users(), [](const SDNode *U) {
+      // A constant is anchored if it has a user other than an ADD, i.e. it is
+      // materialized regardless of this fold. N1C is the (unique) node for Imm,
+      // so the positive side needs no search.
+      auto IsAnchored = [](const SDNode *C) {
+        return any_of(C->users(), [](const SDNode *U) {
           return U->getOpcode() != ISD::ADD;
         });
-        if (V == -Imm) {
-          NegExists = true;
-          NegAnchored |= NonAddUser;
-        } else {
-          PosAnchored |= NonAddUser;
+      };
+      bool PosAnchored = IsAnchored(N1C);
+      // Find the (unique) constant node for -Imm, if any.
+      const SDNode *NegC = nullptr;
+      for (const SDNode &Node : CurDAG->allnodes()) {
+        auto *C = dyn_cast<ConstantSDNode>(&Node);
+        if (C && C->getSimpleValueType(0) == VT && C->getSExtValue() == -Imm) {
+          NegC = &Node;
+          break;
         }
       }
       // Reuse is only free if -Imm is already in the DAG.
-      if (!NegExists)
+      if (!NegC)
         break;
       // Pick which of Imm/-Imm should be the surviving constant, so exactly
       // one of the pair is materialized and any ADDs of the other reuse it:
-      //  - if -Imm is materialized anyway, reuse it (rewrite to SUB);
-      //  - else if Imm is materialized anyway, keep the ADD so it reuses Imm;
+      //  - if Imm is materialized anyway, keep the ADD so it reuses Imm (an ADD
+      //    is also more compressible than a SUB, so prefer it when both are
+      //    anchored and a rewrite would not remove a constant);
+      //  - else if -Imm is materialized anyway, reuse it (rewrite to SUB);
       //  - else keep the cheaper constant, breaking ties towards the positive
       //    value so both ADDs of a C/-C pair agree on the survivor.
       bool Rewrite;
-      if (NegAnchored)
-        Rewrite = true;
-      else if (PosAnchored)
+      if (PosAnchored)
         Rewrite = false;
+      else if (IsAnchored(NegC))
+        Rewrite = true;
       else {
         int PosCost = RISCVMatInt::getIntMatCost(APInt(64, Imm), 64, *Subtarget,
                                                  /*CompressionCost=*/true);
diff --git a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
index 8b8edf203f86f..46baee3dc95e8 100644
--- a/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
+++ b/llvm/test/CodeGen/RISCV/add-imm64-to-sub.ll
@@ -202,7 +202,7 @@ define i64 @add_both_const_anchored(i64 %x, i64 %y, i64 %a) {
 ; CHECK-NEXT:    add a4, a4, a6
 ; CHECK-NEXT:    mul a0, a0, a3
 ; CHECK-NEXT:    mul a1, a1, a4
-; CHECK-NEXT:    sub a2, a2, a4
+; CHECK-NEXT:    add a2, a2, a3
 ; CHECK-NEXT:    xor a0, a0, a1
 ; CHECK-NEXT:    xor a0, a0, a2
 ; CHECK-NEXT:    ret

>From ee5abd9950ee0b2c1ccde8444082bae4fa1cfb89 Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Tue, 8 Sep 2026 19:45:29 +0800
Subject: [PATCH 09/11] [RISCV] Break early when the positive constant is
 anchored

---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 4b39c6a2b58c2..a55c022c5d2de 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -156,7 +156,11 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
           return U->getOpcode() != ISD::ADD;
         });
       };
-      bool PosAnchored = IsAnchored(N1C);
+      // If Imm is materialized anyway, keep the ADD so it reuses Imm; an ADD is
+      // also more compressible than a SUB. This also lets us skip the search
+      // for -Imm below.
+      if (IsAnchored(N1C))
+        break;
       // Find the (unique) constant node for -Imm, if any.
       const SDNode *NegC = nullptr;
       for (const SDNode &Node : CurDAG->allnodes()) {
@@ -171,16 +175,11 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
         break;
       // Pick which of Imm/-Imm should be the surviving constant, so exactly
       // one of the pair is materialized and any ADDs of the other reuse it:
-      //  - if Imm is materialized anyway, keep the ADD so it reuses Imm (an ADD
-      //    is also more compressible than a SUB, so prefer it when both are
-      //    anchored and a rewrite would not remove a constant);
-      //  - else if -Imm is materialized anyway, reuse it (rewrite to SUB);
+      //  - if -Imm is materialized anyway, reuse it (rewrite to SUB);
       //  - else keep the cheaper constant, breaking ties towards the positive
       //    value so both ADDs of a C/-C pair agree on the survivor.
       bool Rewrite;
-      if (PosAnchored)
-        Rewrite = false;
-      else if (IsAnchored(NegC))
+      if (IsAnchored(NegC))
         Rewrite = true;
       else {
         int PosCost = RISCVMatInt::getIntMatCost(APInt(64, Imm), 64, *Subtarget,

>From 097e6300f9775ebcd62de264c408501cd0a11e0e Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Wed, 9 Sep 2026 15:25:56 +0800
Subject: [PATCH 10/11] [RISCV] Assert the ADD constant is not a wide
 TargetConstant

And add braces.
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index a55c022c5d2de..02806baae05f5 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -148,6 +148,12 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
       // selectNegImm. Skip INT64_MIN too, whose negation is itself.
       if (isInt<32>(Imm) || Imm == INT64_MIN)
         break;
+      // dyn_cast<ConstantSDNode> also matches TargetConstant, which is encoded
+      // into the instruction rather than materialized. No TargetConstant is
+      // this wide (the largest are intrinsic IDs, which fit in 32 bits), so the
+      // reasoning below about materializing/reusing the constant is sound.
+      assert(N1C->getOpcode() == ISD::Constant &&
+             "Unexpected wide TargetConstant");
       // A constant is anchored if it has a user other than an ADD, i.e. it is
       // materialized regardless of this fold. N1C is the (unique) node for Imm,
       // so the positive side needs no search.
@@ -179,9 +185,9 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
       //  - else keep the cheaper constant, breaking ties towards the positive
       //    value so both ADDs of a C/-C pair agree on the survivor.
       bool Rewrite;
-      if (IsAnchored(NegC))
+      if (IsAnchored(NegC)) {
         Rewrite = true;
-      else {
+      } else {
         int PosCost = RISCVMatInt::getIntMatCost(APInt(64, Imm), 64, *Subtarget,
                                                  /*CompressionCost=*/true);
         int NegCost =

>From 074a25f5fed3dfbe5c0ac7244a5626316616bb5e Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Thu, 10 Sep 2026 12:04:17 +0800
Subject: [PATCH 11/11] Move the TargetConstant assert to the constant that is
 reused

---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 02806baae05f5..5f90ce87f84b2 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -148,12 +148,6 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
       // selectNegImm. Skip INT64_MIN too, whose negation is itself.
       if (isInt<32>(Imm) || Imm == INT64_MIN)
         break;
-      // dyn_cast<ConstantSDNode> also matches TargetConstant, which is encoded
-      // into the instruction rather than materialized. No TargetConstant is
-      // this wide (the largest are intrinsic IDs, which fit in 32 bits), so the
-      // reasoning below about materializing/reusing the constant is sound.
-      assert(N1C->getOpcode() == ISD::Constant &&
-             "Unexpected wide TargetConstant");
       // A constant is anchored if it has a user other than an ADD, i.e. it is
       // materialized regardless of this fold. N1C is the (unique) node for Imm,
       // so the positive side needs no search.
@@ -179,6 +173,12 @@ void RISCVDAGToDAGISel::PreprocessISelDAG() {
       // Reuse is only free if -Imm is already in the DAG.
       if (!NegC)
         break;
+      // dyn_cast<ConstantSDNode> also matches TargetConstant, which is encoded
+      // into the instruction rather than materialized, so reusing it would not
+      // remove a materialization. No TargetConstant is this wide (the largest
+      // are intrinsic IDs, which fit in 32 bits), so assert it is a Constant.
+      assert(NegC->getOpcode() == ISD::Constant &&
+             "Unexpected wide TargetConstant");
       // Pick which of Imm/-Imm should be the surviving constant, so exactly
       // one of the pair is materialized and any ADDs of the other reuse it:
       //  - if -Imm is materialized anyway, reuse it (rewrite to SUB);



More information about the llvm-commits mailing list