[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