[llvm] [LoongArch][Disassembler] Add symbolic operands for decoded immediates (PR #217971)

Rong Mantle Bao via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 24 03:13:44 PDT 2026


https://github.com/CSharperMantle updated https://github.com/llvm/llvm-project/pull/217971

>From 774f423d05480f3a9f3d2c4aab1f705e6103e363 Mon Sep 17 00:00:00 2001
From: Rong Bao <rong.bao at csmantle.top>
Date: Fri, 21 Aug 2026 22:30:30 +0800
Subject: [PATCH 1/2] [LoongArch] Introduce simm21_lsl2_br for PC-rel simm21
 branches

Originally, all of B{EQ,NE}Z, BC{EQ,NE}Z, and JISCR[01] use simm21_lsl2
immediate type. This is somewhat inaccurate since JISCR[01]'s simm21 is
relative to the scratch register, rather than PC.

Introduce a new type, simm21_lsl2_br, in the same style as
simm16_lsl2_br, and migrate B{EQ,NE}Z and BC{EQ,NE}Z to using it. This
can help distinguish semantics and prepares for the next patch in the
stack.
---
 llvm/lib/Target/LoongArch/LoongArchFloatInstrFormats.td  | 2 +-
 llvm/lib/Target/LoongArch/LoongArchInstrInfo.td          | 9 ++++++++-
 .../LoongArch/MCTargetDesc/LoongArchMCCodeEmitter.cpp    | 2 +-
 3 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Target/LoongArch/LoongArchFloatInstrFormats.td b/llvm/lib/Target/LoongArch/LoongArchFloatInstrFormats.td
index ce42236895c76..6de703eeb29ce 100644
--- a/llvm/lib/Target/LoongArch/LoongArchFloatInstrFormats.td
+++ b/llvm/lib/Target/LoongArch/LoongArchFloatInstrFormats.td
@@ -194,7 +194,7 @@ class FP_SEL<bits<32> op, RegisterClass rc = FPR32>
                 "$fd, $fj, $fk, $ca">;
 
 class FP_BRANCH<bits<32> opcode>
-    : FPFmtBR<opcode, (outs), (ins CFR:$cj, simm21_lsl2:$imm21),
+    : FPFmtBR<opcode, (outs), (ins CFR:$cj, simm21_lsl2_br:$imm21),
               "$cj, $imm21"> {
   let isBranch = 1;
   let isTerminator = 1;
diff --git a/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td b/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td
index 6aacfd8138ff5..80fdd640b0051 100644
--- a/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td
+++ b/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td
@@ -492,12 +492,19 @@ def simm20_pcaddi : SImm20Operand {
   let ParserMatchClass = SImmAsmOperand<20, "pcaddi">;
 }
 
+// Kept for JISCR[01] without PC-rel branching semantics
 def simm21_lsl2 : Operand<OtherVT> {
   let ParserMatchClass = SImmAsmOperand<21, "lsl2">;
   let EncoderMethod = "getImmOpValueAsr<2>";
   let DecoderMethod = "decodeSImmOperand<21, 2>";
 }
 
+def simm21_lsl2_br : Operand<OtherVT> {
+  let ParserMatchClass = SImmAsmOperand<21, "lsl2">;
+  let EncoderMethod = "getImmOpValueAsr<2>";
+  let DecoderMethod = "decodeSImmOperand<21, 2>";
+}
+
 def SImm26OperandB: AsmOperandClass {
   let Name = "SImm26OperandB";
   let PredicateMethod = "isSImm26Operand";
@@ -766,7 +773,7 @@ class BrCC_2RI16<bits<32> op>
   let isTerminator = 1;
 }
 class BrCCZ_1RI21<bits<32> op>
-    : Fmt1RI21<op, (outs), (ins GPR:$rj, simm21_lsl2:$imm21),
+    : Fmt1RI21<op, (outs), (ins GPR:$rj, simm21_lsl2_br:$imm21),
                "$rj, $imm21"> {
   let isBranch = 1;
   let isTerminator = 1;
diff --git a/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchMCCodeEmitter.cpp b/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchMCCodeEmitter.cpp
index 661cf8aae99e4..8146a15eba295 100644
--- a/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchMCCodeEmitter.cpp
+++ b/llvm/lib/Target/LoongArch/MCTargetDesc/LoongArchMCCodeEmitter.cpp
@@ -77,7 +77,7 @@ class LoongArchMCCodeEmitter : public MCCodeEmitter {
   /// The value returned is the value of the immediate shifted right
   //  arithmetically by N.
   /// Note that this function is dedicated to specific immediate types,
-  /// e.g. simm14_lsl2, simm16_lsl2, simm21_lsl2 and simm26_lsl2.
+  /// e.g. simm14_lsl2, simm16_lsl2, simm21_lsl2{,_br} and simm26_lsl2.
   template <unsigned N>
   unsigned getImmOpValueAsr(const MCInst &MI, unsigned OpNo,
                             SmallVectorImpl<MCFixup> &Fixups,

>From 37b7c17388717199e9669e3ea5bb2a5c9711bbb1 Mon Sep 17 00:00:00 2001
From: Rong Bao <rong.bao at csmantle.top>
Date: Mon, 11 May 2026 17:38:39 +0800
Subject: [PATCH 2/2] [LoongArch][Disassembler] Add symbolic operands for
 decoded immediates

Symbolize operands for decoded immediates when possible. This gives
LoongArchDisassembler non-raw immediate capabilities similar to that of
{X86,RISCV}Disassembler. See also #217550 for similar works on RISCV.

This is part of the preparatory work for a future port of BOLT to
LoongArch.

A new unit test is added for this new path.
---
 .../Disassembler/LoongArchDisassembler.cpp    |  20 +-
 .../Target/LoongArch/LoongArchInstrInfo.td    |   8 +-
 llvm/unittests/MC/LoongArch/CMakeLists.txt    |  13 ++
 .../LoongArch/LoongArchMCDisassemblerTest.cpp | 192 ++++++++++++++++++
 4 files changed, 226 insertions(+), 7 deletions(-)
 create mode 100644 llvm/unittests/MC/LoongArch/CMakeLists.txt
 create mode 100644 llvm/unittests/MC/LoongArch/LoongArchMCDisassemblerTest.cpp

diff --git a/llvm/lib/Target/LoongArch/Disassembler/LoongArchDisassembler.cpp b/llvm/lib/Target/LoongArch/Disassembler/LoongArchDisassembler.cpp
index 584b45b4111cd..987d50013632b 100644
--- a/llvm/lib/Target/LoongArch/Disassembler/LoongArchDisassembler.cpp
+++ b/llvm/lib/Target/LoongArch/Disassembler/LoongArchDisassembler.cpp
@@ -142,18 +142,30 @@ static DecodeStatus decodeUImmOperand(MCInst &Inst, uint64_t Imm,
                                       int64_t Address,
                                       const MCDisassembler *Decoder) {
   assert(isUInt<N>(Imm) && "Invalid immediate");
-  Inst.addOperand(MCOperand::createImm(Imm + P));
+  const int64_t DecodedImm = Imm + P;
+  if (!Decoder->tryAddingSymbolicOperand(Inst, DecodedImm, Address,
+                                         /*IsBranch=*/false, /*Offset=*/0,
+                                         /*OpSize=*/0, /*InstSize=*/4))
+    Inst.addOperand(MCOperand::createImm(DecodedImm));
   return MCDisassembler::Success;
 }
 
-template <unsigned N, unsigned S = 0>
+template <unsigned N, unsigned S = 0, bool IsPCRelBranch = false>
 static DecodeStatus decodeSImmOperand(MCInst &Inst, uint64_t Imm,
                                       int64_t Address,
                                       const MCDisassembler *Decoder) {
   assert(isUInt<N>(Imm) && "Invalid immediate");
   // Shift left Imm <S> bits, then sign-extend the number in the bottom <N+S>
   // bits.
-  Inst.addOperand(MCOperand::createImm(SignExtend64<N + S>(Imm << S)));
+  const int64_t DecodedImm = SignExtend64<N + S>(Imm << S);
+  const int64_t Value =
+      IsPCRelBranch ? static_cast<int64_t>(static_cast<uint64_t>(Address) +
+                                           static_cast<uint64_t>(DecodedImm))
+                    : DecodedImm;
+  if (!Decoder->tryAddingSymbolicOperand(Inst, Value, Address, IsPCRelBranch,
+                                         /*Offset=*/0, /*OpSize=*/0,
+                                         /*InstSize=*/4))
+    Inst.addOperand(MCOperand::createImm(DecodedImm));
   return MCDisassembler::Success;
 }
 
@@ -186,6 +198,8 @@ DecodeStatus LoongArchDisassembler::getInstruction(MCInst &MI, uint64_t &Size,
                                                    ArrayRef<uint8_t> Bytes,
                                                    uint64_t Address,
                                                    raw_ostream &CS) const {
+  CommentStream = &CS;
+
   uint32_t Insn;
   DecodeStatus Result;
 
diff --git a/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td b/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td
index 80fdd640b0051..c23fea425b6c9 100644
--- a/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td
+++ b/llvm/lib/Target/LoongArch/LoongArchInstrInfo.td
@@ -457,7 +457,7 @@ def simm16_lsl2 : Operand<GRLenVT>,
 def simm16_lsl2_br : Operand<OtherVT> {
   let ParserMatchClass = SImmAsmOperand<16, "lsl2">;
   let EncoderMethod = "getImmOpValueAsr<2>";
-  let DecoderMethod = "decodeSImmOperand<16, 2>";
+  let DecoderMethod = "decodeSImmOperand<16, 2, true>";
 }
 
 class SImm20Operand : Operand<GRLenVT> {
@@ -502,7 +502,7 @@ def simm21_lsl2 : Operand<OtherVT> {
 def simm21_lsl2_br : Operand<OtherVT> {
   let ParserMatchClass = SImmAsmOperand<21, "lsl2">;
   let EncoderMethod = "getImmOpValueAsr<2>";
-  let DecoderMethod = "decodeSImmOperand<21, 2>";
+  let DecoderMethod = "decodeSImmOperand<21, 2, true>";
 }
 
 def SImm26OperandB: AsmOperandClass {
@@ -517,7 +517,7 @@ def SImm26OperandB: AsmOperandClass {
 def simm26_b : Operand<OtherVT> {
   let ParserMatchClass = SImm26OperandB;
   let EncoderMethod = "getImmOpValueAsr<2>";
-  let DecoderMethod = "decodeSImmOperand<26, 2>";
+  let DecoderMethod = "decodeSImmOperand<26, 2, true>";
 }
 
 def SImm26OperandBL: AsmOperandClass {
@@ -532,7 +532,7 @@ def SImm26OperandBL: AsmOperandClass {
 def simm26_symbol : Operand<GRLenVT> {
   let ParserMatchClass = SImm26OperandBL;
   let EncoderMethod = "getImmOpValueAsr<2>";
-  let DecoderMethod = "decodeSImmOperand<26, 2>";
+  let DecoderMethod = "decodeSImmOperand<26, 2, true>";
 }
 
 // A 32-bit signed immediate with the lowest 16 bits zeroed, suitable for
diff --git a/llvm/unittests/MC/LoongArch/CMakeLists.txt b/llvm/unittests/MC/LoongArch/CMakeLists.txt
new file mode 100644
index 0000000000000..7b8e6902bcf77
--- /dev/null
+++ b/llvm/unittests/MC/LoongArch/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(LLVM_LINK_COMPONENTS
+  LoongArchDesc
+  LoongArchDisassembler
+  LoongArchInfo
+  MC
+  MCDisassembler
+  Support
+  TargetParser
+  )
+
+add_llvm_unittest(LoongArchMCTests
+  LoongArchMCDisassemblerTest.cpp
+  )
diff --git a/llvm/unittests/MC/LoongArch/LoongArchMCDisassemblerTest.cpp b/llvm/unittests/MC/LoongArch/LoongArchMCDisassemblerTest.cpp
new file mode 100644
index 0000000000000..deea1b4c0674d
--- /dev/null
+++ b/llvm/unittests/MC/LoongArch/LoongArchMCDisassemblerTest.cpp
@@ -0,0 +1,192 @@
+//===- LoongArchMCDisassemblerTest.cpp - LoongArch disassembler tests -----===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/MC/MCAsmInfo.h"
+#include "llvm/MC/MCContext.h"
+#include "llvm/MC/MCDisassembler/MCDisassembler.h"
+#include "llvm/MC/MCDisassembler/MCSymbolizer.h"
+#include "llvm/MC/MCExpr.h"
+#include "llvm/MC/MCInst.h"
+#include "llvm/MC/MCRegisterInfo.h"
+#include "llvm/MC/MCSubtargetInfo.h"
+#include "llvm/MC/MCTargetOptions.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Support/TargetSelect.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gtest/gtest.h"
+#include <array>
+#include <cassert>
+#include <memory>
+#include <string>
+
+using namespace llvm;
+
+namespace {
+
+struct Context {
+  const Triple TT{"loongarch64-unknown-linux-gnu"};
+  std::unique_ptr<MCRegisterInfo> MRI;
+  std::unique_ptr<MCAsmInfo> MAI;
+  std::unique_ptr<MCContext> Ctx;
+  std::unique_ptr<MCSubtargetInfo> STI;
+  std::unique_ptr<MCDisassembler> DisAsm;
+
+  Context() {
+    LLVMInitializeLoongArchTargetInfo();
+    LLVMInitializeLoongArchTargetMC();
+    LLVMInitializeLoongArchDisassembler();
+
+    // If we didn't build LoongArch, do not run the test.
+    std::string Error;
+    const Target *TheTarget = TargetRegistry::lookupTarget(TT, Error);
+    if (!TheTarget)
+      return;
+
+    MRI.reset(TheTarget->createMCRegInfo(TT));
+    MAI.reset(TheTarget->createMCAsmInfo(*MRI, TT, MCTargetOptions()));
+    STI.reset(TheTarget->createMCSubtargetInfo(TT, "", ""));
+    Ctx = std::make_unique<MCContext>(TT, *MAI, *MRI, *STI);
+    DisAsm.reset(TheTarget->createMCDisassembler(*STI, *Ctx));
+  }
+
+  operator MCContext &() { return *Ctx; }
+};
+
+Context &getContext() {
+  static Context Ctx;
+  return Ctx;
+}
+
+class LoongArchMCSymbolizerTest : public MCSymbolizer {
+public:
+  struct Call {
+    raw_ostream *CommentStream;
+    int64_t Value;
+    uint64_t Address;
+    bool IsBranch;
+    uint64_t Offset;
+    uint64_t OpSize;
+    uint64_t InstSize;
+  };
+
+  explicit LoongArchMCSymbolizerTest(MCContext &Ctx)
+      : MCSymbolizer(Ctx, nullptr) {}
+
+  SmallVector<Call, 1> Calls;
+  bool AddSymbolicOperand = false;
+
+  void reset(bool AddSymbol = false) {
+    Calls.clear();
+    AddSymbolicOperand = AddSymbol;
+  }
+
+  bool tryAddingSymbolicOperand(MCInst &Inst, raw_ostream &CommentStream,
+                                int64_t Value, uint64_t Address, bool IsBranch,
+                                uint64_t Offset, uint64_t OpSize,
+                                uint64_t InstSize) override {
+    Calls.push_back(
+        {&CommentStream, Value, Address, IsBranch, Offset, OpSize, InstSize});
+    if (!AddSymbolicOperand)
+      return false;
+
+    Inst.addOperand(MCOperand::createExpr(
+        MCSymbolRefExpr::create(Ctx.getOrCreateSymbol("symbol"), Ctx)));
+    return true;
+  }
+
+  void tryAddingPcLoadReferenceComment(raw_ostream &, int64_t,
+                                       uint64_t) override {}
+};
+
+struct ImmediateTestCase {
+  std::array<uint8_t, 4> Bytes;
+  int64_t SymbolicValue;
+  int64_t DecodedImmediate;
+  unsigned OperandIndex;
+  bool IsPCRelBranch;
+};
+
+} // namespace
+
+TEST(LoongArchDisassembler, ReportsDecodedImmediatesToSymbolizer) {
+  auto *Symbolizer = new LoongArchMCSymbolizerTest(getContext());
+  getContext().DisAsm->setSymbolizer(std::unique_ptr<MCSymbolizer>(Symbolizer));
+
+  constexpr uint64_t Address = 0x1000;
+  const ImmediateTestCase TestCases[] = {
+      // ori $a0, $a1, 0x345
+      {{0xa4, 0x14, 0x8d, 0x03}, 0x345, 0x345, 2, false},
+      // pcalau12i $a0, -1
+      {{0xe4, 0xff, 0xff, 0x1b}, -1, -1, 1, false},
+      // beq $a0, $a1, 8
+      {{0x85, 0x08, 0x00, 0x58}, 0x1008, 8, 2, true},
+      // beqz $a0, 8
+      {{0x80, 0x08, 0x00, 0x40}, 0x1008, 8, 1, true},
+      // bceqz $fcc0, 8
+      {{0x00, 0x08, 0x00, 0x48}, 0x1008, 8, 1, true},
+      // bcnez $fcc1, -4
+      {{0x3f, 0xfd, 0xff, 0x4b}, 0x0ffc, -4, 1, true},
+      // b 8
+      {{0x00, 0x08, 0x00, 0x50}, 0x1008, 8, 0, true},
+      // bl -4
+      {{0xff, 0xff, 0xff, 0x57}, 0x0ffc, -4, 0, true},
+      // jirl $ra, $a0, 8
+      {{0x81, 0x08, 0x00, 0x4c}, 8, 8, 2, false},
+  };
+
+  std::string CommentStorage;
+  raw_string_ostream CommentStream(CommentStorage);
+
+  for (const ImmediateTestCase &TestCase : TestCases) {
+    Symbolizer->reset();
+
+    MCInst Inst;
+    uint64_t InstSize;
+    const MCDisassembler::DecodeStatus Status =
+        getContext().DisAsm->getInstruction(Inst, InstSize, TestCase.Bytes,
+                                            Address, CommentStream);
+    ASSERT_EQ(Status, MCDisassembler::Success);
+    ASSERT_EQ(InstSize, 4u);
+
+    ASSERT_EQ(Symbolizer->Calls.size(), 1u);
+    const LoongArchMCSymbolizerTest::Call &Call = Symbolizer->Calls.front();
+    EXPECT_EQ(Call.CommentStream, &CommentStream);
+    EXPECT_EQ(Call.Value, TestCase.SymbolicValue);
+    EXPECT_EQ(Call.Address, Address);
+    EXPECT_EQ(Call.IsBranch, TestCase.IsPCRelBranch);
+    EXPECT_EQ(Call.Offset, 0u);
+    EXPECT_EQ(Call.OpSize, 0u);
+    EXPECT_EQ(Call.InstSize, 4u);
+
+    ASSERT_GT(Inst.getNumOperands(), TestCase.OperandIndex);
+    const MCOperand &Operand = Inst.getOperand(TestCase.OperandIndex);
+    ASSERT_TRUE(Operand.isImm());
+    EXPECT_EQ(Operand.getImm(), TestCase.DecodedImmediate);
+  }
+}
+
+TEST(LoongArchDisassembler, UsesSymbolicImmediateFromSymbolizer) {
+  auto *Symbolizer = new LoongArchMCSymbolizerTest(getContext());
+  getContext().DisAsm->setSymbolizer(std::unique_ptr<MCSymbolizer>(Symbolizer));
+  Symbolizer->reset(/*AddSymbol=*/true);
+
+  MCInst Inst;
+  uint64_t InstSize;
+  const MCDisassembler::DecodeStatus Status =
+      getContext().DisAsm->getInstruction(Inst, InstSize,
+                                          // pcalau12i $a0, 0
+                                          {0x04, 0x00, 0x00, 0x1a}, 0x1000,
+                                          nulls());
+
+  ASSERT_EQ(Status, MCDisassembler::Success);
+  ASSERT_EQ(InstSize, 4u);
+  ASSERT_EQ(Symbolizer->Calls.size(), 1u);
+  ASSERT_EQ(Inst.getNumOperands(), 2u);
+  EXPECT_TRUE(Inst.getOperand(1).isExpr());
+}



More information about the llvm-commits mailing list