[llvm] 394f309 - [Clang][LoongArch] Add inline asm support for constraints f/l/I/K

Weining Lu via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 25 18:17:39 PDT 2022


Author: Weining Lu
Date: 2022-09-26T08:49:58+08:00
New Revision: 394f30919a029331ebdfe02c180bd1586c0d9ace

URL: https://github.com/llvm/llvm-project/commit/394f30919a029331ebdfe02c180bd1586c0d9ace
DIFF: https://github.com/llvm/llvm-project/commit/394f30919a029331ebdfe02c180bd1586c0d9ace.diff

LOG: [Clang][LoongArch] Add inline asm support for constraints f/l/I/K

This patch adds support for constraints `f`, `l`, `I`, `K` according
to [1]. The remain constraints (`k`, `m`, `ZB`, `ZC`) will be added
later as they are a little more complex than the others.
f: A floating-point register (if available).
l: A signed 16-bit constant.
I: A signed 12-bit constant (for arithmetic instructions).
K: An unsigned 12-bit constant (for logic instructions).

For now, no need to support register alias (e.g. `$a0`) in llvm as
clang will correctly decode the usage of register name aliases into
their official names. And AFAIK, the not yet upstreamed `rustc` for
LoongArch will always use official register names (e.g. `$r4`).

[1] https://gcc.gnu.org/onlinedocs/gccint/Machine-Constraints.html

Differential Revision: https://reviews.llvm.org/D134157

Added: 
    clang/test/CodeGen/LoongArch/inline-asm-constraints-error.c
    clang/test/CodeGen/LoongArch/inline-asm-constraints.c
    clang/test/CodeGen/LoongArch/inline-asm-gcc-regs-error.c
    clang/test/CodeGen/LoongArch/inline-asm-gcc-regs.c
    llvm/test/CodeGen/LoongArch/inline-asm-clobbers.ll
    llvm/test/CodeGen/LoongArch/inline-asm-constraint-error.ll
    llvm/test/CodeGen/LoongArch/inline-asm-constraint-f.ll
    llvm/test/CodeGen/LoongArch/inline-asm-constraint.ll
    llvm/test/CodeGen/LoongArch/inline-asm-reg-names-error.ll
    llvm/test/CodeGen/LoongArch/inline-asm-reg-names-f-error.ll
    llvm/test/CodeGen/LoongArch/inline-asm-reg-names-f.ll
    llvm/test/CodeGen/LoongArch/inline-asm-reg-names.ll

Modified: 
    clang/lib/Basic/Targets/LoongArch.cpp
    llvm/lib/Target/LoongArch/LoongArchAsmPrinter.cpp
    llvm/lib/Target/LoongArch/LoongArchAsmPrinter.h
    llvm/lib/Target/LoongArch/LoongArchISelLowering.cpp
    llvm/lib/Target/LoongArch/LoongArchISelLowering.h

Removed: 
    


################################################################################
diff  --git a/clang/lib/Basic/Targets/LoongArch.cpp b/clang/lib/Basic/Targets/LoongArch.cpp
index d87d8e841c4b..cc93206dad68 100644
--- a/clang/lib/Basic/Targets/LoongArch.cpp
+++ b/clang/lib/Basic/Targets/LoongArch.cpp
@@ -21,20 +21,73 @@ using namespace clang;
 using namespace clang::targets;
 
 ArrayRef<const char *> LoongArchTargetInfo::getGCCRegNames() const {
-  // TODO: To be implemented in future.
-  return {};
+  static const char *const GCCRegNames[] = {
+      // General purpose registers.
+      "$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", "$r7", "$r8", "$r9",
+      "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", "$r16", "$r17", "$r18",
+      "$r19", "$r20", "$r21", "$r22", "$r23", "$r24", "$r25", "$r26", "$r27",
+      "$r28", "$r29", "$r30", "$r31",
+      // Floating point registers.
+      "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", "$f8", "$f9",
+      "$f10", "$f11", "$f12", "$f13", "$f14", "$f15", "$f16", "$f17", "$f18",
+      "$f19", "$f20", "$f21", "$f22", "$f23", "$f24", "$f25", "$f26", "$f27",
+      "$f28", "$f29", "$f30", "$f31"};
+  return llvm::makeArrayRef(GCCRegNames);
 }
 
 ArrayRef<TargetInfo::GCCRegAlias>
 LoongArchTargetInfo::getGCCRegAliases() const {
-  // TODO: To be implemented in future.
-  return {};
+  static const TargetInfo::GCCRegAlias GCCRegAliases[] = {
+      {{"$zero"}, "$r0"},       {{"$ra"}, "$r1"},    {{"$tp"}, "$r2"},
+      {{"$sp"}, "$r3"},         {{"$a0"}, "$r4"},    {{"$a1"}, "$r5"},
+      {{"$a2"}, "$r6"},         {{"$a3"}, "$r7"},    {{"$a4"}, "$r8"},
+      {{"$a5"}, "$r9"},         {{"$a6"}, "$r10"},   {{"$a7"}, "$r11"},
+      {{"$t0"}, "$r12"},        {{"$t1"}, "$r13"},   {{"$t2"}, "$r14"},
+      {{"$t3"}, "$r15"},        {{"$t4"}, "$r16"},   {{"$t5"}, "$r17"},
+      {{"$t6"}, "$r18"},        {{"$t7"}, "$r19"},   {{"$t8"}, "$r20"},
+      {{"$fp", "$s9"}, "$r22"}, {{"$s0"}, "$r23"},   {{"$s1"}, "$r24"},
+      {{"$s2"}, "$r25"},        {{"$s3"}, "$r26"},   {{"$s4"}, "$r27"},
+      {{"$s5"}, "$r28"},        {{"$s6"}, "$r29"},   {{"$s7"}, "$r30"},
+      {{"$s8"}, "$r31"},        {{"$fa0"}, "$f0"},   {{"$fa1"}, "$f1"},
+      {{"$fa2"}, "$f2"},        {{"$fa3"}, "$f3"},   {{"$fa4"}, "$f4"},
+      {{"$fa5"}, "$f5"},        {{"$fa6"}, "$f6"},   {{"$fa7"}, "$f7"},
+      {{"$ft0"}, "$f8"},        {{"$ft1"}, "$f9"},   {{"$ft2"}, "$f10"},
+      {{"$ft3"}, "$f11"},       {{"$ft4"}, "$f12"},  {{"$ft5"}, "$f13"},
+      {{"$ft6"}, "$f14"},       {{"$ft7"}, "$f15"},  {{"$ft8"}, "$f16"},
+      {{"$ft9"}, "$f17"},       {{"$ft10"}, "$f18"}, {{"$ft11"}, "$f19"},
+      {{"$ft12"}, "$f20"},      {{"$ft13"}, "$f21"}, {{"$ft14"}, "$f22"},
+      {{"$ft15"}, "$f23"},      {{"$fs0"}, "$f24"},  {{"$fs1"}, "$f25"},
+      {{"$fs2"}, "$f26"},       {{"$fs3"}, "$f27"},  {{"$fs4"}, "$f28"},
+      {{"$fs5"}, "$f29"},       {{"$fs6"}, "$f30"},  {{"$fs7"}, "$f31"},
+  };
+  return llvm::makeArrayRef(GCCRegAliases);
 }
 
 bool LoongArchTargetInfo::validateAsmConstraint(
     const char *&Name, TargetInfo::ConstraintInfo &Info) const {
-  // TODO: To be implemented in future.
-  return false;
+  // See the GCC definitions here:
+  // https://gcc.gnu.org/onlinedocs/gccint/Machine-Constraints.html
+  switch (*Name) {
+  // TODO: handle 'k', 'm', "ZB", "ZC".
+  default:
+    return false;
+  case 'f':
+    // A floating-point register (if available).
+    Info.setAllowsRegister();
+    return true;
+  case 'l':
+    // A signed 16-bit constant.
+    Info.setRequiresImmediate(-32768, 32767);
+    return true;
+  case 'I':
+    // A signed 12-bit constant (for arithmetic instructions).
+    Info.setRequiresImmediate(-2048, 2047);
+    return true;
+  case 'K':
+    // An unsigned 12-bit constant (for logic instructions).
+    Info.setRequiresImmediate(0, 4095);
+    return true;
+  }
 }
 
 void LoongArchTargetInfo::getTargetDefines(const LangOptions &Opts,

diff  --git a/clang/test/CodeGen/LoongArch/inline-asm-constraints-error.c b/clang/test/CodeGen/LoongArch/inline-asm-constraints-error.c
new file mode 100644
index 000000000000..b4430cf40e62
--- /dev/null
+++ b/clang/test/CodeGen/LoongArch/inline-asm-constraints-error.c
@@ -0,0 +1,23 @@
+// RUN: not %clang_cc1 -triple loongarch32 -O2 -emit-llvm %s 2>&1 -o - | FileCheck %s
+// RUN: not %clang_cc1 -triple loongarch64 -O2 -emit-llvm %s 2>&1 -o - | FileCheck %s
+
+void test_l(void) {
+// CHECK: :[[#@LINE+1]]:27: error: value '32768' out of range for constraint 'l'
+  asm volatile ("" :: "l"(32768));
+// CHECK: :[[#@LINE+1]]:27: error: value '-32769' out of range for constraint 'l'
+  asm volatile ("" :: "l"(-32769));
+}
+
+void test_I(void) {
+// CHECK: :[[#@LINE+1]]:27: error: value '2048' out of range for constraint 'I'
+  asm volatile ("" :: "I"(2048));
+// CHECK: :[[#@LINE+1]]:27: error: value '-2049' out of range for constraint 'I'
+  asm volatile ("" :: "I"(-2049));
+}
+
+void test_K(void) {
+// CHECK: :[[#@LINE+1]]:27: error: value '4096' out of range for constraint 'K'
+  asm volatile ("" :: "K"(4096));
+// CHECK: :[[#@LINE+1]]:27: error: value '-1' out of range for constraint 'K'
+  asm volatile ("" :: "K"(-1));
+}

diff  --git a/clang/test/CodeGen/LoongArch/inline-asm-constraints.c b/clang/test/CodeGen/LoongArch/inline-asm-constraints.c
new file mode 100644
index 000000000000..9c9c60e48493
--- /dev/null
+++ b/clang/test/CodeGen/LoongArch/inline-asm-constraints.c
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -triple loongarch32 -O2 -emit-llvm %s -o - | FileCheck %s
+// RUN: %clang_cc1 -triple loongarch64 -O2 -emit-llvm %s -o - | FileCheck %s
+
+/// Test LoongArch specific inline assembly constraints.
+
+float f;
+double d;
+void test_f(void) {
+// CHECK-LABEL: define{{.*}} void @test_f()
+// CHECK: [[FLT_ARG:%[a-zA-Z_0-9]+]] = load float, ptr @f
+// CHECK: call void asm sideeffect "", "f"(float [[FLT_ARG]])
+  asm volatile ("" :: "f"(f));
+// CHECK: [[FLT_ARG:%[a-zA-Z_0-9]+]] = load double, ptr @d
+// CHECK: call void asm sideeffect "", "f"(double [[FLT_ARG]])
+  asm volatile ("" :: "f"(d));
+}
+
+void test_l(void) {
+// CHECK-LABEL: define{{.*}} void @test_l()
+// CHECK: call void asm sideeffect "", "l"(i32 32767)
+  asm volatile ("" :: "l"(32767));
+// CHECK: call void asm sideeffect "", "l"(i32 -32768)
+  asm volatile ("" :: "l"(-32768));
+}
+
+void test_I(void) {
+// CHECK-LABEL: define{{.*}} void @test_I()
+// CHECK: call void asm sideeffect "", "I"(i32 2047)
+  asm volatile ("" :: "I"(2047));
+// CHECK: call void asm sideeffect "", "I"(i32 -2048)
+  asm volatile ("" :: "I"(-2048));
+}
+
+void test_K(void) {
+// CHECK-LABEL: define{{.*}} void @test_K()
+// CHECK: call void asm sideeffect "", "K"(i32 4095)
+  asm volatile ("" :: "K"(4095));
+// CHECK: call void asm sideeffect "", "K"(i32 0)
+  asm volatile ("" :: "K"(0));
+}

diff  --git a/clang/test/CodeGen/LoongArch/inline-asm-gcc-regs-error.c b/clang/test/CodeGen/LoongArch/inline-asm-gcc-regs-error.c
new file mode 100644
index 000000000000..39450c4657ae
--- /dev/null
+++ b/clang/test/CodeGen/LoongArch/inline-asm-gcc-regs-error.c
@@ -0,0 +1,22 @@
+// RUN: not %clang_cc1 -triple loongarch32 -emit-llvm %s 2>&1 -o - | FileCheck %s
+// RUN: not %clang_cc1 -triple loongarch64 -emit-llvm %s 2>&1 -o - | FileCheck %s
+
+void test(void) {
+// CHECK: :[[#@LINE+1]]:24: error: unknown register name '$r32' in asm
+  register int a0 asm ("$r32");
+// CHECK: :[[#@LINE+1]]:26: error: unknown register name '$f32' in asm
+  register float a1 asm ("$f32");
+// CHECK: :[[#@LINE+1]]:24: error: unknown register name '$foo' in asm
+  register int a2 asm ("$foo");
+
+/// Names not prefixed with '$' are invalid.
+
+// CHECK: :[[#@LINE+1]]:24: error: unknown register name 'r4' in asm
+  register int a3 asm ("r4");
+// CHECK: :[[#@LINE+1]]:24: error: unknown register name 'a0' in asm
+  register int a4 asm ("a0");
+// CHECK: :[[#@LINE+1]]:26: error: unknown register name 'f0' in asm
+  register float a5 asm ("f0");
+// CHECK: :[[#@LINE+1]]:26: error: unknown register name 'fa0' in asm
+  register float a6 asm ("fa0");
+}

diff  --git a/clang/test/CodeGen/LoongArch/inline-asm-gcc-regs.c b/clang/test/CodeGen/LoongArch/inline-asm-gcc-regs.c
new file mode 100644
index 000000000000..067645366443
--- /dev/null
+++ b/clang/test/CodeGen/LoongArch/inline-asm-gcc-regs.c
@@ -0,0 +1,102 @@
+// RUN: %clang_cc1 -triple loongarch32 -emit-llvm -O2 %s -o - | FileCheck %s
+// RUN: %clang_cc1 -triple loongarch64 -emit-llvm -O2 %s -o - | FileCheck %s
+
+/// Check GCC register names and alias can be used in register variable definition.
+
+// CHECK-LABEL: @test_r0
+// CHECK: call void asm sideeffect "", "{$r0}"(i32 undef)
+void test_r0() {
+    register int a asm ("$r0");
+    asm ("" :: "r" (a));
+}
+
+// CHECK-LABEL: @test_r12
+// CHECK: call void asm sideeffect "", "{$r12}"(i32 undef)
+void test_r12() {
+    register int a asm ("$r12");
+    asm ("" :: "r" (a));
+}
+
+// CHECK-LABEL: @test_r31
+// CHECK: call void asm sideeffect "", "{$r31}"(i32 undef)
+void test_r31() {
+    register int a asm ("$r31");
+    asm ("" :: "r" (a));
+}
+
+// CHECK-LABEL: @test_zero
+// CHECK: call void asm sideeffect "", "{$r0}"(i32 undef)
+void test_zero() {
+    register int a asm ("$zero");
+    asm ("" :: "r" (a));
+}
+
+// CHECK-LABEL: @test_a0
+// CHECK: call void asm sideeffect "", "{$r4}"(i32 undef)
+void test_a0() {
+    register int a asm ("$a0");
+    asm ("" :: "r" (a));
+}
+
+// CHECK-LABEL: @test_t1
+// CHECK: call void asm sideeffect "", "{$r13}"(i32 undef)
+void test_t1() {
+    register int a asm ("$t1");
+    asm ("" :: "r" (a));
+}
+
+// CHECK-LABEL: @test_fp
+// CHECK: call void asm sideeffect "", "{$r22}"(i32 undef)
+void test_fp() {
+    register int a asm ("$fp");
+    asm ("" :: "r" (a));
+}
+
+// CHECK-LABEL: @test_s2
+// CHECK: call void asm sideeffect "", "{$r25}"(i32 undef)
+void test_s2() {
+    register int a asm ("$s2");
+    asm ("" :: "r" (a));
+}
+
+// CHECK-LABEL: @test_f0
+// CHECK: call void asm sideeffect "", "{$f0}"(float undef)
+void test_f0() {
+    register float a asm ("$f0");
+    asm ("" :: "f" (a));
+}
+
+// CHECK-LABEL: @test_f14
+// CHECK: call void asm sideeffect "", "{$f14}"(float undef)
+void test_f14() {
+    register float a asm ("$f14");
+    asm ("" :: "f" (a));
+}
+
+// CHECK-LABEL: @test_f31
+// CHECK: call void asm sideeffect "", "{$f31}"(float undef)
+void test_f31() {
+    register float a asm ("$f31");
+    asm ("" :: "f" (a));
+}
+
+// CHECK-LABEL: @test_fa0
+// CHECK: call void asm sideeffect "", "{$f0}"(float undef)
+void test_fa0() {
+    register float a asm ("$fa0");
+    asm ("" :: "f" (a));
+}
+
+// CHECK-LABEL: @test_ft1
+// CHECK: call void asm sideeffect "", "{$f9}"(float undef)
+void test_ft1() {
+    register float a asm ("$ft1");
+    asm ("" :: "f" (a));
+}
+
+// CHECK-LABEL: @test_fs2
+// CHECK: call void asm sideeffect "", "{$f26}"(float undef)
+void test_fs2() {
+    register float a asm ("$fs2");
+    asm ("" :: "f" (a));
+}

diff  --git a/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.cpp b/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.cpp
index 1467d1757ff0..9a7fb73b736c 100644
--- a/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.cpp
+++ b/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.cpp
@@ -14,6 +14,7 @@
 #include "LoongArchAsmPrinter.h"
 #include "LoongArch.h"
 #include "LoongArchTargetMachine.h"
+#include "MCTargetDesc/LoongArchInstPrinter.h"
 #include "TargetInfo/LoongArchTargetInfo.h"
 #include "llvm/CodeGen/AsmPrinter.h"
 #include "llvm/MC/TargetRegistry.h"
@@ -39,6 +40,34 @@ void LoongArchAsmPrinter::emitInstruction(const MachineInstr *MI) {
     EmitToStreamer(*OutStreamer, TmpInst);
 }
 
+bool LoongArchAsmPrinter::PrintAsmOperand(const MachineInstr *MI, unsigned OpNo,
+                                          const char *ExtraCode,
+                                          raw_ostream &OS) {
+  // First try the generic code, which knows about modifiers like 'c' and 'n'.
+  if (!AsmPrinter::PrintAsmOperand(MI, OpNo, ExtraCode, OS))
+    return false;
+
+  // TODO: handle other extra codes if we have.
+  if (!ExtraCode) {
+    const MachineOperand &MO = MI->getOperand(OpNo);
+    switch (MO.getType()) {
+    case MachineOperand::MO_Immediate:
+      OS << MO.getImm();
+      return false;
+    case MachineOperand::MO_Register:
+      OS << '$' << LoongArchInstPrinter::getRegisterName(MO.getReg());
+      return false;
+    case MachineOperand::MO_GlobalAddress:
+      PrintSymbolOperand(MO, OS);
+      return false;
+    default:
+      llvm_unreachable("not implemented");
+    }
+  }
+
+  return true;
+}
+
 bool LoongArchAsmPrinter::runOnMachineFunction(MachineFunction &MF) {
   AsmPrinter::runOnMachineFunction(MF);
   return true;

diff  --git a/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.h b/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.h
index b51c19188051..941c86633fa1 100644
--- a/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.h
+++ b/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.h
@@ -36,6 +36,9 @@ class LLVM_LIBRARY_VISIBILITY LoongArchAsmPrinter : public AsmPrinter {
 
   void emitInstruction(const MachineInstr *MI) override;
 
+  bool PrintAsmOperand(const MachineInstr *MI, unsigned OpNo,
+                       const char *ExtraCode, raw_ostream &OS) override;
+
   // tblgen'erated function.
   bool emitPseudoExpansionLowering(MCStreamer &OutStreamer,
                                    const MachineInstr *MI);

diff  --git a/llvm/lib/Target/LoongArch/LoongArchISelLowering.cpp b/llvm/lib/Target/LoongArch/LoongArchISelLowering.cpp
index 800b9ddffbcf..d73f7f8da482 100644
--- a/llvm/lib/Target/LoongArch/LoongArchISelLowering.cpp
+++ b/llvm/lib/Target/LoongArch/LoongArchISelLowering.cpp
@@ -1907,3 +1907,139 @@ bool LoongArchTargetLowering::isFMAFasterThanFMulAndFAdd(
 
   return false;
 }
+
+//===----------------------------------------------------------------------===//
+//                           LoongArch Inline Assembly Support
+//===----------------------------------------------------------------------===//
+
+LoongArchTargetLowering::ConstraintType
+LoongArchTargetLowering::getConstraintType(StringRef Constraint) const {
+  // LoongArch specific constraints in GCC: config/loongarch/constraints.md
+  //
+  // 'f':  A floating-point register (if available).
+  // 'k':  A memory operand whose address is formed by a base register and
+  //       (optionally scaled) index register.
+  // 'l':  A signed 16-bit constant.
+  // 'm':  A memory operand whose address is formed by a base register and
+  //       offset that is suitable for use in instructions with the same
+  //       addressing mode as st.w and ld.w.
+  // 'I':  A signed 12-bit constant (for arithmetic instructions).
+  // 'K':  An unsigned 12-bit constant (for logic instructions).
+  // "ZB": An address that is held in a general-purpose register. The offset is
+  //       zero.
+  // "ZC": A memory operand whose address is formed by a base register and
+  //       offset that is suitable for use in instructions with the same
+  //       addressing mode as ll.w and sc.w.
+  if (Constraint.size() == 1) {
+    switch (Constraint[0]) {
+    default:
+      break;
+    case 'f':
+      return C_RegisterClass;
+    case 'l':
+    case 'I':
+    case 'K':
+      return C_Immediate;
+    }
+  }
+
+  // TODO: handle 'k", "ZB" and "ZC".
+
+  return TargetLowering::getConstraintType(Constraint);
+}
+
+std::pair<unsigned, const TargetRegisterClass *>
+LoongArchTargetLowering::getRegForInlineAsmConstraint(
+    const TargetRegisterInfo *TRI, StringRef Constraint, MVT VT) const {
+  // First, see if this is a constraint that directly corresponds to a LoongArch
+  // register class.
+  if (Constraint.size() == 1) {
+    switch (Constraint[0]) {
+    case 'r':
+      // TODO: Support fixed vectors up to GRLen?
+      if (VT.isVector())
+        break;
+      return std::make_pair(0U, &LoongArch::GPRRegClass);
+    case 'f':
+      if (Subtarget.hasBasicF() && VT == MVT::f32)
+        return std::make_pair(0U, &LoongArch::FPR32RegClass);
+      if (Subtarget.hasBasicD() && VT == MVT::f64)
+        return std::make_pair(0U, &LoongArch::FPR64RegClass);
+      break;
+    default:
+      break;
+    }
+  }
+
+  // TargetLowering::getRegForInlineAsmConstraint uses the name of the TableGen
+  // record (e.g. the "R0" in `def R0`) to choose registers for InlineAsm
+  // constraints while the official register name is prefixed with a '$'. So we
+  // clip the '$' from the original constraint string (e.g. {$r0} to {r0}.)
+  // before it being parsed. And TargetLowering::getRegForInlineAsmConstraint is
+  // case insensitive, so no need to convert the constraint to upper case here.
+  //
+  // For now, no need to support ABI names (e.g. `$a0`) as clang will correctly
+  // decode the usage of register name aliases into their official names. And
+  // AFAIK, the not yet upstreamed `rustc` for LoongArch will always use
+  // official register names.
+  if (Constraint.startswith("{$r") || Constraint.startswith("{$f")) {
+    bool IsFP = Constraint[2] == 'f';
+    std::pair<StringRef, StringRef> Temp = Constraint.split('$');
+    std::pair<unsigned, const TargetRegisterClass *> R;
+    R = TargetLowering::getRegForInlineAsmConstraint(
+        TRI, join_items("", Temp.first, Temp.second), VT);
+    // Match those names to the widest floating point register type available.
+    if (IsFP) {
+      unsigned RegNo = R.first;
+      if (LoongArch::F0 <= RegNo && RegNo <= LoongArch::F31) {
+        if (Subtarget.hasBasicD() && (VT == MVT::f64 || VT == MVT::Other)) {
+          unsigned DReg = RegNo - LoongArch::F0 + LoongArch::F0_64;
+          return std::make_pair(DReg, &LoongArch::FPR64RegClass);
+        }
+      }
+    }
+    return R;
+  }
+
+  return TargetLowering::getRegForInlineAsmConstraint(TRI, Constraint, VT);
+}
+
+void LoongArchTargetLowering::LowerAsmOperandForConstraint(
+    SDValue Op, std::string &Constraint, std::vector<SDValue> &Ops,
+    SelectionDAG &DAG) const {
+  // Currently only support length 1 constraints.
+  if (Constraint.length() == 1) {
+    switch (Constraint[0]) {
+    case 'l':
+      // Validate & create a 16-bit signed immediate operand.
+      if (auto *C = dyn_cast<ConstantSDNode>(Op)) {
+        uint64_t CVal = C->getSExtValue();
+        if (isInt<16>(CVal))
+          Ops.push_back(
+              DAG.getTargetConstant(CVal, SDLoc(Op), Subtarget.getGRLenVT()));
+      }
+      return;
+    case 'I':
+      // Validate & create a 12-bit signed immediate operand.
+      if (auto *C = dyn_cast<ConstantSDNode>(Op)) {
+        uint64_t CVal = C->getSExtValue();
+        if (isInt<12>(CVal))
+          Ops.push_back(
+              DAG.getTargetConstant(CVal, SDLoc(Op), Subtarget.getGRLenVT()));
+      }
+      return;
+    case 'K':
+      // Validate & create a 12-bit unsigned immediate operand.
+      if (auto *C = dyn_cast<ConstantSDNode>(Op)) {
+        uint64_t CVal = C->getZExtValue();
+        if (isUInt<12>(CVal))
+          Ops.push_back(
+              DAG.getTargetConstant(CVal, SDLoc(Op), Subtarget.getGRLenVT()));
+      }
+      return;
+    default:
+      break;
+    }
+  }
+  TargetLowering::LowerAsmOperandForConstraint(Op, Constraint, Ops, DAG);
+}

diff  --git a/llvm/lib/Target/LoongArch/LoongArchISelLowering.h b/llvm/lib/Target/LoongArch/LoongArchISelLowering.h
index eb97622182e5..5e7dfee87217 100644
--- a/llvm/lib/Target/LoongArch/LoongArchISelLowering.h
+++ b/llvm/lib/Target/LoongArch/LoongArchISelLowering.h
@@ -148,6 +148,16 @@ class LoongArchTargetLowering : public TargetLowering {
                     bool ForCodeSize) const override;
 
   bool shouldInsertFencesForAtomic(const Instruction *I) const override;
+
+  ConstraintType getConstraintType(StringRef Constraint) const override;
+
+  std::pair<unsigned, const TargetRegisterClass *>
+  getRegForInlineAsmConstraint(const TargetRegisterInfo *TRI,
+                               StringRef Constraint, MVT VT) const override;
+
+  void LowerAsmOperandForConstraint(SDValue Op, std::string &Constraint,
+                                    std::vector<SDValue> &Ops,
+                                    SelectionDAG &DAG) const override;
 };
 
 } // end namespace llvm

diff  --git a/llvm/test/CodeGen/LoongArch/inline-asm-clobbers.ll b/llvm/test/CodeGen/LoongArch/inline-asm-clobbers.ll
new file mode 100644
index 000000000000..f7e460e47545
--- /dev/null
+++ b/llvm/test/CodeGen/LoongArch/inline-asm-clobbers.ll
@@ -0,0 +1,50 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc --mtriple=loongarch32 --mattr=+d --target-abi=ilp32d --verify-machineinstrs < %s \
+; RUN:   | FileCheck --check-prefix=LA32 %s
+; RUN: llc --mtriple=loongarch64 --mattr=+d --target-abi=lp64d --verify-machineinstrs < %s \
+; RUN:   | FileCheck --check-prefix=LA64 %s
+
+;; Check that callee-saved registers clobbered by inlineasm are correctly saved.
+;;
+;; $r23: $s0 (callee-saved register under all ABIs)
+;; $r24: $s1 (callee-saved register under all ABIs)
+;; $f24: $fs0 (callee-saved register under *d/*f ABIs)
+;; $f25: $fs1 (callee-saved register under *d/*f ABIs)
+
+;; TODO: test other ABIs.
+
+define void @test() nounwind {
+; LA32-LABEL: test:
+; LA32:       # %bb.0:
+; LA32-NEXT:    addi.w $sp, $sp, -32
+; LA32-NEXT:    st.w $s0, $sp, 28 # 4-byte Folded Spill
+; LA32-NEXT:    st.w $s1, $sp, 24 # 4-byte Folded Spill
+; LA32-NEXT:    fst.d $fs0, $sp, 16 # 8-byte Folded Spill
+; LA32-NEXT:    fst.d $fs1, $sp, 8 # 8-byte Folded Spill
+; LA32-NEXT:    #APP
+; LA32-NEXT:    #NO_APP
+; LA32-NEXT:    fld.d $fs1, $sp, 8 # 8-byte Folded Reload
+; LA32-NEXT:    fld.d $fs0, $sp, 16 # 8-byte Folded Reload
+; LA32-NEXT:    ld.w $s1, $sp, 24 # 4-byte Folded Reload
+; LA32-NEXT:    ld.w $s0, $sp, 28 # 4-byte Folded Reload
+; LA32-NEXT:    addi.w $sp, $sp, 32
+; LA32-NEXT:    ret
+;
+; LA64-LABEL: test:
+; LA64:       # %bb.0:
+; LA64-NEXT:    addi.d $sp, $sp, -32
+; LA64-NEXT:    st.d $s0, $sp, 24 # 8-byte Folded Spill
+; LA64-NEXT:    st.d $s1, $sp, 16 # 8-byte Folded Spill
+; LA64-NEXT:    fst.d $fs0, $sp, 8 # 8-byte Folded Spill
+; LA64-NEXT:    fst.d $fs1, $sp, 0 # 8-byte Folded Spill
+; LA64-NEXT:    #APP
+; LA64-NEXT:    #NO_APP
+; LA64-NEXT:    fld.d $fs1, $sp, 0 # 8-byte Folded Reload
+; LA64-NEXT:    fld.d $fs0, $sp, 8 # 8-byte Folded Reload
+; LA64-NEXT:    ld.d $s1, $sp, 16 # 8-byte Folded Reload
+; LA64-NEXT:    ld.d $s0, $sp, 24 # 8-byte Folded Reload
+; LA64-NEXT:    addi.d $sp, $sp, 32
+; LA64-NEXT:    ret
+  tail call void asm sideeffect "", "~{$f24},~{$f25},~{$r23},~{$r24}"()
+  ret void
+}

diff  --git a/llvm/test/CodeGen/LoongArch/inline-asm-constraint-error.ll b/llvm/test/CodeGen/LoongArch/inline-asm-constraint-error.ll
new file mode 100644
index 000000000000..470581d1faf9
--- /dev/null
+++ b/llvm/test/CodeGen/LoongArch/inline-asm-constraint-error.ll
@@ -0,0 +1,40 @@
+; RUN: not llc --mtriple=loongarch32 < %s 2>&1 | FileCheck %s
+; RUN: not llc --mtriple=loongarch64 < %s 2>&1 | FileCheck %s
+
+define void @constraint_l() {
+; CHECK: error: value out of range for constraint 'l'
+  tail call void asm sideeffect "lu12i.w $$a0, $0", "l"(i32 32768)
+; CHECK: error: value out of range for constraint 'l'
+  tail call void asm sideeffect "lu12i.w $$a0, $0", "l"(i32 -32769)
+  ret void
+}
+
+define void @constraint_I() {
+; CHECK: error: value out of range for constraint 'I'
+  tail call void asm sideeffect "addi.w $$a0, $$a0, $0", "I"(i32 2048)
+; CHECK: error: value out of range for constraint 'I'
+  tail call void asm sideeffect "addi.w $$a0, $$a0, $0", "I"(i32 -2049)
+  ret void
+}
+
+define void @constraint_K() {
+; CHECK: error: value out of range for constraint 'K'
+  tail call void asm sideeffect "andi.w $$a0, $$a0, $0", "K"(i32 4096)
+; CHECK: error: value out of range for constraint 'K'
+  tail call void asm sideeffect "andi.w $$a0, $$a0, $0", "K"(i32 -1)
+  ret void
+}
+
+define void @constraint_f() nounwind {
+; CHECK: error: couldn't allocate input reg for constraint 'f'
+  tail call void asm "fadd.s $$fa0, $$fa0, $0", "f"(float 0.0)
+; CHECK: error: couldn't allocate input reg for constraint 'f'
+  tail call void asm "fadd.s $$fa0, $$fa0, $0", "f"(double 0.0)
+  ret void
+}
+
+define void @constraint_r_vec() nounwind {
+; CHECK: error: couldn't allocate input reg for constraint 'r'
+  tail call void asm "add.w $$a0, $$a0, $0", "r"(<4 x i32> zeroinitializer)
+  ret void
+}

diff  --git a/llvm/test/CodeGen/LoongArch/inline-asm-constraint-f.ll b/llvm/test/CodeGen/LoongArch/inline-asm-constraint-f.ll
new file mode 100644
index 000000000000..a5569cdf9a5b
--- /dev/null
+++ b/llvm/test/CodeGen/LoongArch/inline-asm-constraint-f.ll
@@ -0,0 +1,62 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc --mtriple=loongarch32 --mattr=+d --target-abi=ilp32d --verify-machineinstrs < %s \
+; RUN:   | FileCheck --check-prefix=LA32 %s
+; RUN: llc --mtriple=loongarch64 --mattr=+d --target-abi=lp64d --verify-machineinstrs < %s \
+; RUN:   | FileCheck --check-prefix=LA64 %s
+
+ at gd = external dso_local global double
+
+define double @constraint_f_double(double %a) nounwind {
+; LA32-LABEL: constraint_f_double:
+; LA32:       # %bb.0:
+; LA32-NEXT:    pcalau12i $a0, %pc_hi20(gd)
+; LA32-NEXT:    addi.w $a0, $a0, %pc_lo12(gd)
+; LA32-NEXT:    fld.d $fa1, $a0, 0
+; LA32-NEXT:    #APP
+; LA32-NEXT:    fadd.d $fa0, $fa0, $fa1
+; LA32-NEXT:    #NO_APP
+; LA32-NEXT:    ret
+;
+; LA64-LABEL: constraint_f_double:
+; LA64:       # %bb.0:
+; LA64-NEXT:    pcalau12i $a0, %pc_hi20(gd)
+; LA64-NEXT:    addi.d $a0, $a0, %pc_lo12(gd)
+; LA64-NEXT:    fld.d $fa1, $a0, 0
+; LA64-NEXT:    #APP
+; LA64-NEXT:    fadd.d $fa0, $fa0, $fa1
+; LA64-NEXT:    #NO_APP
+; LA64-NEXT:    ret
+  %1 = load double, double* @gd
+  %2 = tail call double asm "fadd.d $0, $1, $2", "=f,f,f"(double %a, double %1)
+  ret double %2
+}
+
+define double @constraint_gpr(double %a) {
+; LA32-LABEL: constraint_gpr:
+; LA32:       # %bb.0:
+; LA32-NEXT:    addi.w $sp, $sp, -16
+; LA32-NEXT:    .cfi_def_cfa_offset 16
+; LA32-NEXT:    fst.d $fa0, $sp, 8
+; LA32-NEXT:    ld.w $a7, $sp, 8
+; LA32-NEXT:    ld.w $t0, $sp, 12
+; LA32-NEXT:    #APP
+; LA32-NEXT:    move $a6, $a7
+; LA32-NEXT:    #NO_APP
+; LA32-NEXT:    st.w $a7, $sp, 4
+; LA32-NEXT:    st.w $a6, $sp, 0
+; LA32-NEXT:    fld.d $fa0, $sp, 0
+; LA32-NEXT:    addi.w $sp, $sp, 16
+; LA32-NEXT:    ret
+;
+; LA64-LABEL: constraint_gpr:
+; LA64:       # %bb.0:
+; LA64-NEXT:    .cfi_def_cfa_offset 0
+; LA64-NEXT:    movfr2gr.d $a7, $fa0
+; LA64-NEXT:    #APP
+; LA64-NEXT:    move $a6, $a7
+; LA64-NEXT:    #NO_APP
+; LA64-NEXT:    movgr2fr.d $fa0, $a6
+; LA64-NEXT:    ret
+  %1 = tail call double asm sideeffect alignstack "move $0, $1", "={$r10},{$r11}"(double %a)
+  ret double %1
+}

diff  --git a/llvm/test/CodeGen/LoongArch/inline-asm-constraint.ll b/llvm/test/CodeGen/LoongArch/inline-asm-constraint.ll
new file mode 100644
index 000000000000..ed2c621bf86a
--- /dev/null
+++ b/llvm/test/CodeGen/LoongArch/inline-asm-constraint.ll
@@ -0,0 +1,100 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc --mtriple=loongarch32 --verify-machineinstrs --no-integrated-as < %s \
+; RUN:   | FileCheck %s
+; RUN: llc --mtriple=loongarch64 --verify-machineinstrs --no-integrated-as < %s \
+; RUN:   | FileCheck %s
+
+ at gi = external dso_local global i32, align 4
+
+define i32 @constraint_r(i32 %a, i32 %b) nounwind {
+; CHECK-LABEL: constraint_r:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    add.w $a0, $a0, $a1
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    ret
+  %1 = tail call i32 asm "add.w $0, $1, $2", "=r,r,r"(i32 %a, i32 %b)
+  ret i32 %1
+}
+
+define i32 @constraint_i(i32 %a) nounwind {
+; CHECK-LABEL: constraint_i:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    addi.w $a0, $a0, 113
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    ret
+  %1 = tail call i32 asm "addi.w $0, $1, $2", "=r,r,i"(i32 %a, i32 113)
+  ret i32 %1
+}
+
+define void @constraint_l() nounwind {
+; CHECK-LABEL: constraint_l:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    lu12i.w $a0, 32767
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    lu12i.w $a0, -32768
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    ret
+  tail call void asm sideeffect "lu12i.w $$a0, $0", "l"(i32 32767)
+  tail call void asm sideeffect "lu12i.w $$a0, $0", "l"(i32 -32768)
+  ret void
+}
+
+define void @constraint_I() nounwind {
+; CHECK-LABEL: constraint_I:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    addi.w $a0, $a0, 2047
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    addi.w $a0, $a0, -2048
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    ret
+  tail call void asm sideeffect "addi.w $$a0, $$a0, $0", "I"(i32 2047)
+  tail call void asm sideeffect "addi.w $$a0, $$a0, $0", "I"(i32 -2048)
+  ret void
+}
+
+define void @constraint_K() nounwind {
+; CHECK-LABEL: constraint_K:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    andi $a0, $a0, 4095
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    andi $a0, $a0, 0
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    ret
+  tail call void asm sideeffect "andi $$a0, $$a0, $0", "K"(i32 4095)
+  tail call void asm sideeffect "andi $$a0, $$a0, $0", "K"(i32 0)
+  ret void
+}
+
+define void @operand_global() nounwind {
+; CHECK-LABEL: operand_global:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    .8byte gi
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    ret
+  tail call void asm sideeffect ".8byte $0", "i"(ptr @gi)
+  ret void
+}
+
+define void @operand_block_address() nounwind {
+; CHECK-LABEL: operand_block_address:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    b .Ltmp0
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:  .Ltmp0: # Block address taken
+; CHECK-NEXT:  # %bb.1: # %bb
+; CHECK-NEXT:    ret
+  call void asm sideeffect "b $0", "i"(i8* blockaddress(@operand_block_address, %bb))
+  br label %bb
+bb:
+  ret void
+}

diff  --git a/llvm/test/CodeGen/LoongArch/inline-asm-reg-names-error.ll b/llvm/test/CodeGen/LoongArch/inline-asm-reg-names-error.ll
new file mode 100644
index 000000000000..56c335ffb3a6
--- /dev/null
+++ b/llvm/test/CodeGen/LoongArch/inline-asm-reg-names-error.ll
@@ -0,0 +1,14 @@
+; RUN: not llc --mtriple=loongarch32 2>&1 < %s | FileCheck %s
+; RUN: not llc --mtriple=loongarch64 2>&1 < %s | FileCheck %s
+
+define i32 @non_exit_r32(i32 %a) nounwind {
+; CHECK: error: couldn't allocate input reg for constraint '{$r32}'
+  %1 = tail call i32 asm "addi.w $0, $1, 1", "=r,{$r32}"(i32 %a)
+  ret i32 %1
+}
+
+define i32 @non_exit_foo(i32 %a) nounwind {
+; CHECK: error: couldn't allocate input reg for constraint '{$foo}'
+  %1 = tail call i32 asm "addi.w $0, $1, 1", "=r,{$foo}"(i32 %a)
+  ret i32 %1
+}

diff  --git a/llvm/test/CodeGen/LoongArch/inline-asm-reg-names-f-error.ll b/llvm/test/CodeGen/LoongArch/inline-asm-reg-names-f-error.ll
new file mode 100644
index 000000000000..82d0d21e1cd5
--- /dev/null
+++ b/llvm/test/CodeGen/LoongArch/inline-asm-reg-names-f-error.ll
@@ -0,0 +1,14 @@
+; RUN: not llc --mtriple=loongarch32 --mattr=+f,+d 2>&1 < %s | FileCheck %s
+; RUN: not llc --mtriple=loongarch64 --mattr=+f,+d 2>&1 < %s | FileCheck %s
+
+define double @non_exit_f32(double %a) nounwind {
+; CHECK: error: couldn't allocate input reg for constraint '{$f32}'
+  %1 = tail call double asm "fabs.d $0, $1", "=f,{$f32}"(double %a)
+  ret double %1
+}
+
+define double @non_exit_foo(double %a) nounwind {
+; CHECK: error: couldn't allocate input reg for constraint '{$foo}'
+  %1 = tail call double asm "fabs.d $0, $1", "=f,{$foo}"(double %a)
+  ret double %1
+}

diff  --git a/llvm/test/CodeGen/LoongArch/inline-asm-reg-names-f.ll b/llvm/test/CodeGen/LoongArch/inline-asm-reg-names-f.ll
new file mode 100644
index 000000000000..8cf112223e25
--- /dev/null
+++ b/llvm/test/CodeGen/LoongArch/inline-asm-reg-names-f.ll
@@ -0,0 +1,89 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc --mtriple=loongarch32 --mattr=+f,+d --target-abi=ilp32d --verify-machineinstrs < %s \
+; RUN:   | FileCheck --check-prefix=LA32 %s
+; RUN: llc --mtriple=loongarch64 --mattr=+f,+d --target-abi=lp64d --verify-machineinstrs < %s \
+; RUN:   | FileCheck --check-prefix=LA64 %s
+
+;; These test that we can use architectural names ($f[0-9]*) refer to registers in
+;; inline asm constraint lists. In each case, the named register should be used
+;; for the source register of the `fabs.d`. It is very likely that `$fa0` will
+;; be chosen as the designation register, but this is left to the compiler to
+;; choose.
+;;
+;; Parenthesised registers in comments are the other aliases for this register.
+
+define double @register_f0(double %a) nounwind {
+; LA32-LABEL: register_f0:
+; LA32:       # %bb.0:
+; LA32-NEXT:    #APP
+; LA32-NEXT:    fabs.d $fa0, $fa0
+; LA32-NEXT:    #NO_APP
+; LA32-NEXT:    ret
+;
+; LA64-LABEL: register_f0:
+; LA64:       # %bb.0:
+; LA64-NEXT:    #APP
+; LA64-NEXT:    fabs.d $fa0, $fa0
+; LA64-NEXT:    #NO_APP
+; LA64-NEXT:    ret
+  %1 = tail call double asm "fabs.d $0, $1", "=f,{$f0}"(double %a)
+  ret double %1
+}
+
+;; NOTE: This test uses `$f24` (`$fs0`) as an input, so it should be saved.
+define double @register_f24(double %a) nounwind {
+; LA32-LABEL: register_f24:
+; LA32:       # %bb.0:
+; LA32-NEXT:    addi.w $sp, $sp, -16
+; LA32-NEXT:    fst.d $fs0, $sp, 8 # 8-byte Folded Spill
+; LA32-NEXT:    fmov.d $fs0, $fa0
+; LA32-NEXT:    #APP
+; LA32-NEXT:    fabs.d $fa0, $fs0
+; LA32-NEXT:    #NO_APP
+; LA32-NEXT:    fld.d $fs0, $sp, 8 # 8-byte Folded Reload
+; LA32-NEXT:    addi.w $sp, $sp, 16
+; LA32-NEXT:    ret
+;
+; LA64-LABEL: register_f24:
+; LA64:       # %bb.0:
+; LA64-NEXT:    addi.d $sp, $sp, -16
+; LA64-NEXT:    fst.d $fs0, $sp, 8 # 8-byte Folded Spill
+; LA64-NEXT:    fmov.d $fs0, $fa0
+; LA64-NEXT:    #APP
+; LA64-NEXT:    fabs.d $fa0, $fs0
+; LA64-NEXT:    #NO_APP
+; LA64-NEXT:    fld.d $fs0, $sp, 8 # 8-byte Folded Reload
+; LA64-NEXT:    addi.d $sp, $sp, 16
+; LA64-NEXT:    ret
+  %1 = tail call double asm "fabs.d $0, $1", "=f,{$f24}"(double %a)
+  ret double %1
+}
+
+;; NOTE: This test uses `$f31` (`$fs7`) as an input, so it should be saved.
+define double @register_f31(double %a) nounwind {
+; LA32-LABEL: register_f31:
+; LA32:       # %bb.0:
+; LA32-NEXT:    addi.w $sp, $sp, -16
+; LA32-NEXT:    fst.d $fs7, $sp, 8 # 8-byte Folded Spill
+; LA32-NEXT:    fmov.d $fs7, $fa0
+; LA32-NEXT:    #APP
+; LA32-NEXT:    fabs.d $fa0, $fs7
+; LA32-NEXT:    #NO_APP
+; LA32-NEXT:    fld.d $fs7, $sp, 8 # 8-byte Folded Reload
+; LA32-NEXT:    addi.w $sp, $sp, 16
+; LA32-NEXT:    ret
+;
+; LA64-LABEL: register_f31:
+; LA64:       # %bb.0:
+; LA64-NEXT:    addi.d $sp, $sp, -16
+; LA64-NEXT:    fst.d $fs7, $sp, 8 # 8-byte Folded Spill
+; LA64-NEXT:    fmov.d $fs7, $fa0
+; LA64-NEXT:    #APP
+; LA64-NEXT:    fabs.d $fa0, $fs7
+; LA64-NEXT:    #NO_APP
+; LA64-NEXT:    fld.d $fs7, $sp, 8 # 8-byte Folded Reload
+; LA64-NEXT:    addi.d $sp, $sp, 16
+; LA64-NEXT:    ret
+  %1 = tail call double asm "fabs.d $0, $1", "=f,{$f31}"(double %a)
+  ret double %1
+}

diff  --git a/llvm/test/CodeGen/LoongArch/inline-asm-reg-names.ll b/llvm/test/CodeGen/LoongArch/inline-asm-reg-names.ll
new file mode 100644
index 000000000000..4bc16e6cc5fb
--- /dev/null
+++ b/llvm/test/CodeGen/LoongArch/inline-asm-reg-names.ll
@@ -0,0 +1,109 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc --mtriple=loongarch32 --verify-machineinstrs < %s \
+; RUN:   | FileCheck --check-prefix=LA32 %s
+; RUN: llc --mtriple=loongarch64 --verify-machineinstrs < %s \
+; RUN:   | FileCheck --check-prefix=LA64 %s
+
+;; These test that we can use architectural names ($r*) refer to registers in
+;; inline asm constraint lists. In each case, the named register should be used
+;; for the source register of the `addi.w`. It is very likely that `$a0` will
+;; be chosen as the designation register, but this is left to the compiler to
+;; choose.
+;;
+;; Parenthesised registers in comments are the other aliases for this register.
+
+;; NOTE: This test has to pass in 0 to the inline asm, because that's the only
+;; value `$r0` (`$zero`) can take.
+define i32 @register_r0() nounwind {
+; LA32-LABEL: register_r0:
+; LA32:       # %bb.0:
+; LA32-NEXT:    #APP
+; LA32-NEXT:    addi.w $a0, $zero, 0
+; LA32-NEXT:    #NO_APP
+; LA32-NEXT:    ret
+;
+; LA64-LABEL: register_r0:
+; LA64:       # %bb.0:
+; LA64-NEXT:    #APP
+; LA64-NEXT:    addi.w $a0, $zero, 0
+; LA64-NEXT:    #NO_APP
+; LA64-NEXT:    ret
+  %1 = tail call i32 asm "addi.w $0, $1, 0", "=r,{$r0}"(i32 0)
+  ret i32 %1
+}
+
+define i32 @register_r4(i32 %a) nounwind {
+; LA32-LABEL: register_r4:
+; LA32:       # %bb.0:
+; LA32-NEXT:    #APP
+; LA32-NEXT:    addi.w $a0, $a0, 1
+; LA32-NEXT:    #NO_APP
+; LA32-NEXT:    ret
+;
+; LA64-LABEL: register_r4:
+; LA64:       # %bb.0:
+; LA64-NEXT:    #APP
+; LA64-NEXT:    addi.w $a0, $a0, 1
+; LA64-NEXT:    #NO_APP
+; LA64-NEXT:    ret
+  %1 = tail call i32 asm "addi.w $0, $1, 1", "=r,{$r4}"(i32 %a)
+  ret i32 %1
+}
+
+;; NOTE: This test uses `$r22` (`$s9`, `$fp`) as an input, so it should be saved.
+define i32 @register_r22(i32 %a) nounwind {
+; LA32-LABEL: register_r22:
+; LA32:       # %bb.0:
+; LA32-NEXT:    addi.w $sp, $sp, -16
+; LA32-NEXT:    st.w $fp, $sp, 12 # 4-byte Folded Spill
+; LA32-NEXT:    move $fp, $a0
+; LA32-NEXT:    #APP
+; LA32-NEXT:    addi.w $a0, $fp, 1
+; LA32-NEXT:    #NO_APP
+; LA32-NEXT:    ld.w $fp, $sp, 12 # 4-byte Folded Reload
+; LA32-NEXT:    addi.w $sp, $sp, 16
+; LA32-NEXT:    ret
+;
+; LA64-LABEL: register_r22:
+; LA64:       # %bb.0:
+; LA64-NEXT:    addi.d $sp, $sp, -16
+; LA64-NEXT:    st.d $fp, $sp, 8 # 8-byte Folded Spill
+; LA64-NEXT:    move $fp, $a0
+; LA64-NEXT:    #APP
+; LA64-NEXT:    addi.w $a0, $fp, 1
+; LA64-NEXT:    #NO_APP
+; LA64-NEXT:    ld.d $fp, $sp, 8 # 8-byte Folded Reload
+; LA64-NEXT:    addi.d $sp, $sp, 16
+; LA64-NEXT:    ret
+  %1 = tail call i32 asm "addi.w $0, $1, 1", "=r,{$r22}"(i32 %a)
+  ret i32 %1
+}
+
+;; NOTE: This test uses `$r31` (`$s8`) as an input, so it should be saved.
+define i32 @register_r31(i32 %a) nounwind {
+; LA32-LABEL: register_r31:
+; LA32:       # %bb.0:
+; LA32-NEXT:    addi.w $sp, $sp, -16
+; LA32-NEXT:    st.w $s8, $sp, 12 # 4-byte Folded Spill
+; LA32-NEXT:    move $s8, $a0
+; LA32-NEXT:    #APP
+; LA32-NEXT:    addi.w $a0, $s8, 1
+; LA32-NEXT:    #NO_APP
+; LA32-NEXT:    ld.w $s8, $sp, 12 # 4-byte Folded Reload
+; LA32-NEXT:    addi.w $sp, $sp, 16
+; LA32-NEXT:    ret
+;
+; LA64-LABEL: register_r31:
+; LA64:       # %bb.0:
+; LA64-NEXT:    addi.d $sp, $sp, -16
+; LA64-NEXT:    st.d $s8, $sp, 8 # 8-byte Folded Spill
+; LA64-NEXT:    move $s8, $a0
+; LA64-NEXT:    #APP
+; LA64-NEXT:    addi.w $a0, $s8, 1
+; LA64-NEXT:    #NO_APP
+; LA64-NEXT:    ld.d $s8, $sp, 8 # 8-byte Folded Reload
+; LA64-NEXT:    addi.d $sp, $sp, 16
+; LA64-NEXT:    ret
+  %1 = tail call i32 asm "addi.w $0, $1, 1", "=r,{$r31}"(i32 %a)
+  ret i32 %1
+}


        


More information about the llvm-commits mailing list