diff --git a/clang/lib/Basic/Targets/LoongArch.cpp b/clang/lib/Basic/Targets/LoongArch.cpp --- a/clang/lib/Basic/Targets/LoongArch.cpp +++ b/clang/lib/Basic/Targets/LoongArch.cpp @@ -21,20 +21,73 @@ using namespace clang::targets; ArrayRef 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 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 --- /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 --- /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 --- /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 --- /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.h b/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.h --- a/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.h +++ b/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.h @@ -36,6 +36,9 @@ 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/LoongArchAsmPrinter.cpp b/llvm/lib/Target/LoongArch/LoongArchAsmPrinter.cpp --- 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 @@ 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/LoongArchISelLowering.h b/llvm/lib/Target/LoongArch/LoongArchISelLowering.h --- a/llvm/lib/Target/LoongArch/LoongArchISelLowering.h +++ b/llvm/lib/Target/LoongArch/LoongArchISelLowering.h @@ -148,6 +148,16 @@ bool ForCodeSize) const override; bool shouldInsertFencesForAtomic(const Instruction *I) const override; + + ConstraintType getConstraintType(StringRef Constraint) const override; + + std::pair + getRegForInlineAsmConstraint(const TargetRegisterInfo *TRI, + StringRef Constraint, MVT VT) const override; + + void LowerAsmOperandForConstraint(SDValue Op, std::string &Constraint, + std::vector &Ops, + SelectionDAG &DAG) const override; }; } // end namespace llvm diff --git a/llvm/lib/Target/LoongArch/LoongArchISelLowering.cpp b/llvm/lib/Target/LoongArch/LoongArchISelLowering.cpp --- a/llvm/lib/Target/LoongArch/LoongArchISelLowering.cpp +++ b/llvm/lib/Target/LoongArch/LoongArchISelLowering.cpp @@ -1907,3 +1907,139 @@ 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 +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 Temp = Constraint.split('$'); + std::pair 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 &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(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(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(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/test/CodeGen/LoongArch/inline-asm-clobbers.ll b/llvm/test/CodeGen/LoongArch/inline-asm-clobbers.ll new file mode 100644 --- /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 --- /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 --- /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 + +@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 --- /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 + +@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 --- /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 --- /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 --- /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 --- /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 +}