[llvm] [RISCV] Extend legal addressing modes to support 26-bit addi/Xqcilo offsets (PR #204734)

Garvit Gupta via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 25 05:55:22 PDT 2026


https://github.com/quic-garvgupt updated https://github.com/llvm/llvm-project/pull/204734

>From 5ad338f3ee05e34390574af45d5cee288c8fd5cc Mon Sep 17 00:00:00 2001
From: Garvit Gupta <garvgupt at qti.qualcomm.com>
Date: Thu, 18 Jun 2026 05:36:25 -0700
Subject: [PATCH 1/3] [RISCV] Add pre-commit tests for 26-bit immediate/offset
 legality

Add pre-commit tests capturing the current codegen for large (26-bit)
immediates and offsets under the Qualcomm uC Large Immediate Arithmetic
(Xqcilia) and Large Offset Load Store (Xqcilo) extensions. These exercise the
pre-ISel passes that query RISCVTargetLowering::isLegalAddImmediate and
isLegalAddressingMode:
---
 llvm/test/CodeGen/RISCV/xqcilo-split-gep.ll   | 56 +++++++++++++++++++
 .../Transforms/CodeGenPrepare/RISCV/xqcilo.ll | 44 +++++++++++++++
 .../ConstantHoisting/RISCV/xqcilia.ll         | 28 ++++++++++
 3 files changed, 128 insertions(+)
 create mode 100644 llvm/test/CodeGen/RISCV/xqcilo-split-gep.ll
 create mode 100644 llvm/test/Transforms/CodeGenPrepare/RISCV/xqcilo.ll
 create mode 100644 llvm/test/Transforms/ConstantHoisting/RISCV/xqcilia.ll

diff --git a/llvm/test/CodeGen/RISCV/xqcilo-split-gep.ll b/llvm/test/CodeGen/RISCV/xqcilo-split-gep.ll
new file mode 100644
index 0000000000000..c9414cf235e32
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xqcilo-split-gep.ll
@@ -0,0 +1,56 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+
+; CodeGenPrepare's splitLargeGEPOffsets queries
+; RISCVTargetLowering::isLegalAddressingMode to decide whether a large constant
+; GEP offset must be split into a separate base address. Currently, with Xqcilo
+; enabled, the offset is reported as not foldable, so the base address is
+; computed separately and each access uses a plain lw with a small offset. A
+; follow-up extends isLegalAddressingMode to accept 26-bit offsets, after which
+; each access folds its full offset into a single qc.e.lw.
+
+; RUN: llc < %s -mtriple=riscv32 | FileCheck %s -check-prefix=RV32I
+; RUN: llc < %s -mtriple=riscv32 -mattr=+xqcilo | FileCheck %s -check-prefix=RV32XQCILO
+
+define i32 @split_large_geps(ptr %p, i1 %c) {
+; RV32I-LABEL: split_large_geps:
+; RV32I:       # %bb.0: # %entry
+; RV32I-NEXT:    andi a1, a1, 1
+; RV32I-NEXT:    lui a2, 5
+; RV32I-NEXT:    add a0, a0, a2
+; RV32I-NEXT:    beqz a1, .LBB0_2
+; RV32I-NEXT:  # %bb.1: # %a
+; RV32I-NEXT:    lw a0, -480(a0)
+; RV32I-NEXT:    ret
+; RV32I-NEXT:  .LBB0_2: # %b
+; RV32I-NEXT:    lw a0, -476(a0)
+; RV32I-NEXT:    ret
+;
+; RV32XQCILO-LABEL: split_large_geps:
+; RV32XQCILO:       # %bb.0: # %entry
+; RV32XQCILO-NEXT:    andi a1, a1, 1
+; RV32XQCILO-NEXT:    lui a2, 5
+; RV32XQCILO-NEXT:    add a0, a0, a2
+; RV32XQCILO-NEXT:    beqz a1, .LBB0_2
+; RV32XQCILO-NEXT:  # %bb.1: # %a
+; RV32XQCILO-NEXT:    lw a0, -480(a0)
+; RV32XQCILO-NEXT:    ret
+; RV32XQCILO-NEXT:  .LBB0_2: # %b
+; RV32XQCILO-NEXT:    lw a0, -476(a0)
+; RV32XQCILO-NEXT:    ret
+entry:
+  br i1 %c, label %a, label %b
+
+a:
+  %g0 = getelementptr i8, ptr %p, i32 20000
+  %v0 = load i32, ptr %g0
+  br label %merge
+
+b:
+  %g1 = getelementptr i8, ptr %p, i32 20004
+  %v1 = load i32, ptr %g1
+  br label %merge
+
+merge:
+  %r = phi i32 [ %v0, %a ], [ %v1, %b ]
+  ret i32 %r
+}
diff --git a/llvm/test/Transforms/CodeGenPrepare/RISCV/xqcilo.ll b/llvm/test/Transforms/CodeGenPrepare/RISCV/xqcilo.ll
new file mode 100644
index 0000000000000..52af6eaf0fa69
--- /dev/null
+++ b/llvm/test/Transforms/CodeGenPrepare/RISCV/xqcilo.ll
@@ -0,0 +1,44 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes='require<profile-summary>,function(codegenprepare)' \
+; RUN:   -mtriple=riscv32 -mattr=+xqcilo < %s | FileCheck %s --check-prefix=XQCILO
+
+; Two loads in separate blocks share a base pointer with large (26-bit) byte
+; offsets. CodeGenPrepare's splitLargeGEPOffsets queries
+; RISCVTargetLowering::isLegalAddressingMode to decide whether the offsets can
+; be folded into the addressing mode. isLegalAddressingMode only accepts 12-bit
+; offsets, so the large offset is split out into a separate base address
+; (%splitgep) and the second access uses a small offset relative to it.
+define i32 @split_large_geps(ptr %p, i1 %c) {
+; XQCILO-LABEL: define i32 @split_large_geps(
+; XQCILO-SAME: ptr [[P:%.*]], i1 [[C:%.*]]) #[[ATTR0:[0-9]+]] {
+; XQCILO-NEXT:  [[ENTRY:.*:]]
+; XQCILO-NEXT:    [[SPLITGEP:%.*]] = getelementptr i8, ptr [[P]], i32 20000
+; XQCILO-NEXT:    br i1 [[C]], label %[[A:.*]], label %[[B:.*]]
+; XQCILO:       [[A]]:
+; XQCILO-NEXT:    [[V0:%.*]] = load i32, ptr [[SPLITGEP]], align 4
+; XQCILO-NEXT:    br label %[[MERGE:.*]]
+; XQCILO:       [[B]]:
+; XQCILO-NEXT:    [[TMP0:%.*]] = getelementptr i8, ptr [[SPLITGEP]], i32 4
+; XQCILO-NEXT:    [[V1:%.*]] = load i32, ptr [[TMP0]], align 4
+; XQCILO-NEXT:    br label %[[MERGE]]
+; XQCILO:       [[MERGE]]:
+; XQCILO-NEXT:    [[R:%.*]] = phi i32 [ [[V0]], %[[A]] ], [ [[V1]], %[[B]] ]
+; XQCILO-NEXT:    ret i32 [[R]]
+;
+entry:
+  br i1 %c, label %a, label %b
+
+a:
+  %g0 = getelementptr i8, ptr %p, i32 20000
+  %v0 = load i32, ptr %g0
+  br label %merge
+
+b:
+  %g1 = getelementptr i8, ptr %p, i32 20004
+  %v1 = load i32, ptr %g1
+  br label %merge
+
+merge:
+  %r = phi i32 [ %v0, %a ], [ %v1, %b ]
+  ret i32 %r
+}
diff --git a/llvm/test/Transforms/ConstantHoisting/RISCV/xqcilia.ll b/llvm/test/Transforms/ConstantHoisting/RISCV/xqcilia.ll
new file mode 100644
index 0000000000000..f6e76867a63b0
--- /dev/null
+++ b/llvm/test/Transforms/ConstantHoisting/RISCV/xqcilia.ll
@@ -0,0 +1,28 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -mtriple=riscv32 -mattr=+xqcilia -passes=consthoist -S < %s \
+; RUN:   | FileCheck %s --check-prefix=XQCILIA
+
+; A 26-bit constant used several times. RISCVTargetLowering::isLegalAddImmediate
+; only accepts 12-bit immediates, so constant hoisting (which queries it via
+; getIntImmCostInst) considers the constant expensive and hoists it into a
+; register shared by all the adds.
+define void @hoist_simm26(i32 %a, i32 %b, i32 %c, ptr %p, ptr %q, ptr %r) {
+; XQCILIA-LABEL: define void @hoist_simm26(
+; XQCILIA-SAME: i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]], ptr [[P:%.*]], ptr [[Q:%.*]], ptr [[R:%.*]]) #[[ATTR0:[0-9]+]] {
+; XQCILIA-NEXT:    [[CONST:%.*]] = bitcast i32 32766 to i32
+; XQCILIA-NEXT:    [[TMP1:%.*]] = add i32 [[A]], [[CONST]]
+; XQCILIA-NEXT:    [[TMP2:%.*]] = add i32 [[B]], [[CONST]]
+; XQCILIA-NEXT:    [[TMP3:%.*]] = add i32 [[C]], [[CONST]]
+; XQCILIA-NEXT:    store i32 [[TMP1]], ptr [[P]], align 4
+; XQCILIA-NEXT:    store i32 [[TMP2]], ptr [[Q]], align 4
+; XQCILIA-NEXT:    store i32 [[TMP3]], ptr [[R]], align 4
+; XQCILIA-NEXT:    ret void
+;
+  %1 = add i32 %a, 32766
+  %2 = add i32 %b, 32766
+  %3 = add i32 %c, 32766
+  store i32 %1, ptr %p
+  store i32 %2, ptr %q
+  store i32 %3, ptr %r
+  ret void
+}

>From 7f6d41cb89a0199112b6bfed0069f731af1f2f20 Mon Sep 17 00:00:00 2001
From: Garvit Gupta <garvgupt at qti.qualcomm.com>
Date: Thu, 18 Jun 2026 05:57:18 -0700
Subject: [PATCH 2/3] [RISCV] Extend addressing-mode and add-immediate legality
 to 26-bit immediates

Extend `RISCVTargetLowering::isLegalAddImmediate` and `isLegalAddressingMode` to
accept 26-bit signed immediates/offsets when the corresponding Qualcomm uC
extensions are enabled:

These callbacks are queried by some pre-ISel passes. Adding support for the wider
range lets those passes keep large immediates inline and large offsets folded
into addressing modes:
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp      | 14 ++++++++++++--
 llvm/test/CodeGen/RISCV/xqcilo-split-gep.ll      |  6 ++----
 .../Transforms/CodeGenPrepare/RISCV/xqcilo.ll    | 10 +++++-----
 .../Transforms/ConstantHoisting/RISCV/xqcilia.ll | 16 ++++++++--------
 4 files changed, 27 insertions(+), 19 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 7a2b9611683c6..6847145ba97dc 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -2463,9 +2463,15 @@ bool RISCVTargetLowering::isLegalAddressingMode(const DataLayout &DL,
   if (Subtarget.hasVInstructions() && isa<VectorType>(Ty))
     return AM.HasBaseReg && AM.Scale == 0 && !AM.BaseOffs;
 
-  // Require a 12-bit signed offset.
-  if (!isInt<12>(AM.BaseOffs))
+  // The Xqcilo extension provides load/store instructions with a 26-bit signed
+  // offset.
+  if (Subtarget.hasVendorXqcilo()) {
+    if (!isInt<26>(AM.BaseOffs))
+      return false;
+  } else if (!isInt<12>(AM.BaseOffs)) {
+    // Otherwise require a 12-bit signed offset.
     return false;
+  }
 
   switch (AM.Scale) {
   case 0: // "r+i" or just "i", depending on HasBaseReg.
@@ -2486,6 +2492,10 @@ bool RISCVTargetLowering::isLegalICmpImmediate(int64_t Imm) const {
 }
 
 bool RISCVTargetLowering::isLegalAddImmediate(int64_t Imm) const {
+  // The Xqcilia extension provides add-immediate instructions with a 26-bit
+  // signed immediate.
+  if (Subtarget.hasVendorXqcilia())
+    return isInt<26>(Imm);
   return isInt<12>(Imm);
 }
 
diff --git a/llvm/test/CodeGen/RISCV/xqcilo-split-gep.ll b/llvm/test/CodeGen/RISCV/xqcilo-split-gep.ll
index c9414cf235e32..1d96ad4d13665 100644
--- a/llvm/test/CodeGen/RISCV/xqcilo-split-gep.ll
+++ b/llvm/test/CodeGen/RISCV/xqcilo-split-gep.ll
@@ -28,14 +28,12 @@ define i32 @split_large_geps(ptr %p, i1 %c) {
 ; RV32XQCILO-LABEL: split_large_geps:
 ; RV32XQCILO:       # %bb.0: # %entry
 ; RV32XQCILO-NEXT:    andi a1, a1, 1
-; RV32XQCILO-NEXT:    lui a2, 5
-; RV32XQCILO-NEXT:    add a0, a0, a2
 ; RV32XQCILO-NEXT:    beqz a1, .LBB0_2
 ; RV32XQCILO-NEXT:  # %bb.1: # %a
-; RV32XQCILO-NEXT:    lw a0, -480(a0)
+; RV32XQCILO-NEXT:    qc.e.lw a0, 20000(a0)
 ; RV32XQCILO-NEXT:    ret
 ; RV32XQCILO-NEXT:  .LBB0_2: # %b
-; RV32XQCILO-NEXT:    lw a0, -476(a0)
+; RV32XQCILO-NEXT:    qc.e.lw a0, 20004(a0)
 ; RV32XQCILO-NEXT:    ret
 entry:
   br i1 %c, label %a, label %b
diff --git a/llvm/test/Transforms/CodeGenPrepare/RISCV/xqcilo.ll b/llvm/test/Transforms/CodeGenPrepare/RISCV/xqcilo.ll
index 52af6eaf0fa69..e33865e209685 100644
--- a/llvm/test/Transforms/CodeGenPrepare/RISCV/xqcilo.ll
+++ b/llvm/test/Transforms/CodeGenPrepare/RISCV/xqcilo.ll
@@ -5,20 +5,20 @@
 ; Two loads in separate blocks share a base pointer with large (26-bit) byte
 ; offsets. CodeGenPrepare's splitLargeGEPOffsets queries
 ; RISCVTargetLowering::isLegalAddressingMode to decide whether the offsets can
-; be folded into the addressing mode. isLegalAddressingMode only accepts 12-bit
-; offsets, so the large offset is split out into a separate base address
-; (%splitgep) and the second access uses a small offset relative to it.
+; be folded into the addressing mode. With Xqcilo, isLegalAddressingMode now
+; accepts 26-bit offsets, so the GEPs are left intact and each access keeps its
+; full offset instead of being split out into a separate base address.
 define i32 @split_large_geps(ptr %p, i1 %c) {
 ; XQCILO-LABEL: define i32 @split_large_geps(
 ; XQCILO-SAME: ptr [[P:%.*]], i1 [[C:%.*]]) #[[ATTR0:[0-9]+]] {
 ; XQCILO-NEXT:  [[ENTRY:.*:]]
-; XQCILO-NEXT:    [[SPLITGEP:%.*]] = getelementptr i8, ptr [[P]], i32 20000
 ; XQCILO-NEXT:    br i1 [[C]], label %[[A:.*]], label %[[B:.*]]
 ; XQCILO:       [[A]]:
+; XQCILO-NEXT:    [[SPLITGEP:%.*]] = getelementptr i8, ptr [[P]], i32 20000
 ; XQCILO-NEXT:    [[V0:%.*]] = load i32, ptr [[SPLITGEP]], align 4
 ; XQCILO-NEXT:    br label %[[MERGE:.*]]
 ; XQCILO:       [[B]]:
-; XQCILO-NEXT:    [[TMP0:%.*]] = getelementptr i8, ptr [[SPLITGEP]], i32 4
+; XQCILO-NEXT:    [[TMP0:%.*]] = getelementptr i8, ptr [[P]], i32 20004
 ; XQCILO-NEXT:    [[V1:%.*]] = load i32, ptr [[TMP0]], align 4
 ; XQCILO-NEXT:    br label %[[MERGE]]
 ; XQCILO:       [[MERGE]]:
diff --git a/llvm/test/Transforms/ConstantHoisting/RISCV/xqcilia.ll b/llvm/test/Transforms/ConstantHoisting/RISCV/xqcilia.ll
index f6e76867a63b0..8e4bb42355a1d 100644
--- a/llvm/test/Transforms/ConstantHoisting/RISCV/xqcilia.ll
+++ b/llvm/test/Transforms/ConstantHoisting/RISCV/xqcilia.ll
@@ -2,17 +2,17 @@
 ; RUN: opt -mtriple=riscv32 -mattr=+xqcilia -passes=consthoist -S < %s \
 ; RUN:   | FileCheck %s --check-prefix=XQCILIA
 
-; A 26-bit constant used several times. RISCVTargetLowering::isLegalAddImmediate
-; only accepts 12-bit immediates, so constant hoisting (which queries it via
-; getIntImmCostInst) considers the constant expensive and hoists it into a
-; register shared by all the adds.
+; A 26-bit constant used several times. With Xqcilia,
+; RISCVTargetLowering::isLegalAddImmediate now accepts 26-bit immediates, so
+; constant hoisting (which queries it via getIntImmCostInst) considers the
+; constant free and leaves it inline in each add instead of hoisting it into a
+; shared register.
 define void @hoist_simm26(i32 %a, i32 %b, i32 %c, ptr %p, ptr %q, ptr %r) {
 ; XQCILIA-LABEL: define void @hoist_simm26(
 ; XQCILIA-SAME: i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]], ptr [[P:%.*]], ptr [[Q:%.*]], ptr [[R:%.*]]) #[[ATTR0:[0-9]+]] {
-; XQCILIA-NEXT:    [[CONST:%.*]] = bitcast i32 32766 to i32
-; XQCILIA-NEXT:    [[TMP1:%.*]] = add i32 [[A]], [[CONST]]
-; XQCILIA-NEXT:    [[TMP2:%.*]] = add i32 [[B]], [[CONST]]
-; XQCILIA-NEXT:    [[TMP3:%.*]] = add i32 [[C]], [[CONST]]
+; XQCILIA-NEXT:    [[TMP1:%.*]] = add i32 [[A]], 32766
+; XQCILIA-NEXT:    [[TMP2:%.*]] = add i32 [[B]], 32766
+; XQCILIA-NEXT:    [[TMP3:%.*]] = add i32 [[C]], 32766
 ; XQCILIA-NEXT:    store i32 [[TMP1]], ptr [[P]], align 4
 ; XQCILIA-NEXT:    store i32 [[TMP2]], ptr [[Q]], align 4
 ; XQCILIA-NEXT:    store i32 [[TMP3]], ptr [[R]], align 4

>From f89b6f55090c5cbed19f7336a6e93d394db7e357 Mon Sep 17 00:00:00 2001
From: Garvit Gupta <garvgupt at qti.qualcomm.com>
Date: Thu, 25 Jun 2026 05:38:15 -0700
Subject: [PATCH 3/3] [RISCV] Fix infinite loop due to hardcoded isInt<12>
 check in transformAddImmMulImm

The generic DAGCombiner fold add(mul(add(A, CA), CM), CB) -> add(mul(A, CM), CA*CM+CB)
is gated on isLegalAddImmediate(CA*CM+CB). The RISC-V combine
transformAddImmMulImm does the inverse split
add(mul x, c0), c1) -> add(mul(add x, c1/c0), c0), c1%c0)
and previously bailed out only when c1 was a 12-bit immediate. For a
merged constant in (simm12, simm26] (e.g. 2049), the generic fold would merge
and transformAddImmMulImm would split it straight back, looping forever. The
fix makes transformAddImmMulImm bail when c1 is a legal add immediate
(isLegalAddImmediate), so the two folds agree and the combiner
terminates.
---
 llvm/lib/Target/RISCV/RISCVISelLowering.cpp   |  8 +++-
 .../RISCV/xqcilia-addimm-mulimm-loop.ll       | 45 +++++++++++++++++++
 2 files changed, 52 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/CodeGen/RISCV/xqcilia-addimm-mulimm-loop.ll

diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 6847145ba97dc..e61c4f4f3be22 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -16460,7 +16460,13 @@ static SDValue transformAddImmMulImm(SDNode *N, SelectionDAG &DAG,
   int64_t C0 = N0C->getSExtValue();
   int64_t C1 = N1C->getSExtValue();
   int64_t CA, CB;
-  if (C0 == -1 || C0 == 0 || C0 == 1 || isInt<12>(C1))
+  // If C1 already fits in an add immediate, there is nothing to split out: the
+  // (add (mul x, c0), c1) form is already canonical/cheap. Splitting it would
+  // fight the generic DAGCombiner fold add(mul(add(A, CA), CM), CB) ->
+  // add(mul(A, CM), CM*CA+CB) (which is gated on isLegalAddImmediate) and cause
+  // an infinite loop.
+  if (C0 == -1 || C0 == 0 || C0 == 1 ||
+      Subtarget.getTargetLowering()->isLegalAddImmediate(C1))
     return SDValue();
   // Search for proper CA (non-zero) and CB that both are simm12.
   if ((C1 / C0) != 0 && isInt<12>(C1 / C0) && isInt<12>(C1 % C0) &&
diff --git a/llvm/test/CodeGen/RISCV/xqcilia-addimm-mulimm-loop.ll b/llvm/test/CodeGen/RISCV/xqcilia-addimm-mulimm-loop.ll
new file mode 100644
index 0000000000000..239bd6f890d9b
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xqcilia-addimm-mulimm-loop.ll
@@ -0,0 +1,45 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; Regression test for an infinite DAGCombine loop that occurred once
+; isLegalAddImmediate was extended to accept 26-bit immediates (Xqcilia).
+;
+; RUN: llc -mtriple=riscv32 -mattr=+xqcilia -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s --check-prefix=RV32XQCILIA
+
+; (a + 1024) * 2 + 1 = 2*a + 2049. 2049 is simm26 but not simm12.
+define i32 @add_mul_add_simm26(i32 %a) {
+; RV32XQCILIA-LABEL: add_mul_add_simm26:
+; RV32XQCILIA:       # %bb.0:
+; RV32XQCILIA-NEXT:    slli a0, a0, 1
+; RV32XQCILIA-NEXT:    qc.e.addi a0, a0, 2049
+; RV32XQCILIA-NEXT:    ret
+  %add1 = add i32 %a, 1024
+  %mul = mul i32 %add1, 2
+  %add2 = add i32 %mul, 1
+  ret i32 %add2
+}
+
+; A constant past 26 bits (still i32) is materialized by qc.e.addai; it must not
+; be split by transformAddImmMulImm (no loop).
+define i32 @mul_add_simm32(i32 %a) {
+; RV32XQCILIA-LABEL: mul_add_simm32:
+; RV32XQCILIA:       # %bb.0:
+; RV32XQCILIA-NEXT:    slli a1, a0, 1
+; RV32XQCILIA-NEXT:    add a0, a0, a1
+; RV32XQCILIA-NEXT:    qc.e.addai a0, 70000000
+; RV32XQCILIA-NEXT:    ret
+  %mul = mul i32 %a, 3
+  %add = add i32 %mul, 70000000
+  ret i32 %add
+}
+
+; Direct (add (mul x, c0), c1) shape with c1 in the (simm12, simm26] window.
+define i32 @mul_add_simm26(i32 %a) {
+; RV32XQCILIA-LABEL: mul_add_simm26:
+; RV32XQCILIA:       # %bb.0:
+; RV32XQCILIA-NEXT:    slli a0, a0, 1
+; RV32XQCILIA-NEXT:    qc.e.addi a0, a0, 2049
+; RV32XQCILIA-NEXT:    ret
+  %mul = mul i32 %a, 2
+  %add = add i32 %mul, 2049
+  ret i32 %add
+}



More information about the llvm-commits mailing list