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 @@ -33,7 +33,17 @@ "$f19", "$f20", "$f21", "$f22", "$f23", "$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30", "$f31", // Condition flag registers. - "$fcc0", "$fcc1", "$fcc2", "$fcc3", "$fcc4", "$fcc5", "$fcc6", "$fcc7"}; + "$fcc0", "$fcc1", "$fcc2", "$fcc3", "$fcc4", "$fcc5", "$fcc6", "$fcc7", + // 128-bit vector registers. + "$vr0", "$vr1", "$vr2", "$vr3", "$vr4", "$vr5", "$vr6", "$vr7", "$vr8", + "$vr9", "$vr10", "$vr11", "$vr12", "$vr13", "$vr14", "$vr15", "$vr16", + "$vr17", "$vr18", "$vr19", "$vr20", "$vr21", "$vr22", "$vr23", "$vr24", + "$vr25", "$vr26", "$vr27", "$vr28", "$vr29", "$vr30", "$vr31", + // 256-bit vector registers. + "$xr0", "$xr1", "$xr2", "$xr3", "$xr4", "$xr5", "$xr6", "$xr7", "$xr8", + "$xr9", "$xr10", "$xr11", "$xr12", "$xr13", "$xr14", "$xr15", "$xr16", + "$xr17", "$xr18", "$xr19", "$xr20", "$xr21", "$xr22", "$xr23", "$xr24", + "$xr25", "$xr26", "$xr27", "$xr28", "$xr29", "$xr30", "$xr31"}; return llvm::ArrayRef(GCCRegNames); } diff --git a/clang/test/CodeGen/LoongArch/lasx/inline-asm-gcc-regs-error.c b/clang/test/CodeGen/LoongArch/lasx/inline-asm-gcc-regs-error.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/LoongArch/lasx/inline-asm-gcc-regs-error.c @@ -0,0 +1,10 @@ +// RUN: not %clang_cc1 -triple loongarch64 -emit-llvm -O2 %s 2>&1 -o - | FileCheck %s + +typedef signed char v32i8 __attribute__((vector_size(32), aligned(32))); + +void test() { +// CHECK: :[[#@LINE+1]]:28: error: unknown register name 'xr0' in asm + register v32i8 p0 asm ("xr0"); +// CHECK: :[[#@LINE+1]]:29: error: unknown register name '$xr32' in asm + register v32i8 p32 asm ("$xr32"); +} diff --git a/clang/test/CodeGen/LoongArch/lasx/inline-asm-gcc-regs.c b/clang/test/CodeGen/LoongArch/lasx/inline-asm-gcc-regs.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/LoongArch/lasx/inline-asm-gcc-regs.c @@ -0,0 +1,36 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --filter "^define |tail call" +// RUN: %clang_cc1 -triple loongarch64 -emit-llvm -O2 %s -o - | FileCheck %s + +typedef signed char v32i8 __attribute__((vector_size(32), aligned(32))); + +// CHECK-LABEL: @test_xr0( +// CHECK: tail call void asm sideeffect "", "{$xr0}"(<32 x i8> undef) #[[ATTR1:[0-9]+]], !srcloc !2 +// +void test_xr0() { + register v32i8 a asm ("$xr0"); + asm ("" :: "f"(a)); +} + +// CHECK-LABEL: @test_xr7( +// CHECK: tail call void asm sideeffect "", "{$xr7}"(<32 x i8> undef) #[[ATTR1]], !srcloc !3 +// +void test_xr7() { + register v32i8 a asm ("$xr7"); + asm ("" :: "f"(a)); +} + +// CHECK-LABEL: @test_xr15( +// CHECK: tail call void asm sideeffect "", "{$xr15}"(<32 x i8> undef) #[[ATTR1]], !srcloc !4 +// +void test_xr15() { + register v32i8 a asm ("$xr15"); + asm ("" :: "f"(a)); +} + +// CHECK-LABEL: @test_xr31( +// CHECK: tail call void asm sideeffect "", "{$xr31}"(<32 x i8> undef) #[[ATTR1]], !srcloc !5 +// +void test_xr31() { + register v32i8 a asm ("$xr31"); + asm ("" :: "f"(a)); +} diff --git a/clang/test/CodeGen/LoongArch/lasx/inline-asm-operand-modifier.c b/clang/test/CodeGen/LoongArch/lasx/inline-asm-operand-modifier.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/LoongArch/lasx/inline-asm-operand-modifier.c @@ -0,0 +1,15 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 2 +// RUN: %clang_cc1 -triple loongarch64 -emit-llvm -O2 %s -o - | FileCheck %s + +typedef long long v4i64 __attribute__ ((vector_size(32), aligned(32))); + +// CHECK-LABEL: define dso_local void @test_u +// CHECK-SAME: () local_unnamed_addr #[[ATTR0:[0-9]+]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[TMP0:%.*]] = tail call <4 x i64> asm sideeffect "xvldi ${0:u}, 1", "=f"() #[[ATTR1:[0-9]+]], !srcloc !2 +// CHECK-NEXT: ret void +// +void test_u() { + v4i64 v4i64_r; + asm volatile ("xvldi %u0, 1" : "=f" (v4i64_r)); +} diff --git a/clang/test/CodeGen/LoongArch/lsx/inline-asm-gcc-regs-error.c b/clang/test/CodeGen/LoongArch/lsx/inline-asm-gcc-regs-error.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/LoongArch/lsx/inline-asm-gcc-regs-error.c @@ -0,0 +1,10 @@ +// RUN: not %clang_cc1 -triple loongarch64 -emit-llvm -O2 %s 2>&1 -o - | FileCheck %s + +typedef signed char v16i8 __attribute__((vector_size(16), aligned(16))); + +void test() { +// CHECK: :[[#@LINE+1]]:28: error: unknown register name 'vr0' in asm + register v16i8 p0 asm ("vr0"); +// CHECK: :[[#@LINE+1]]:29: error: unknown register name '$vr32' in asm + register v16i8 p32 asm ("$vr32"); +} diff --git a/clang/test/CodeGen/LoongArch/lsx/inline-asm-gcc-regs.c b/clang/test/CodeGen/LoongArch/lsx/inline-asm-gcc-regs.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/LoongArch/lsx/inline-asm-gcc-regs.c @@ -0,0 +1,36 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --filter "^define |tail call" +// RUN: %clang_cc1 -triple loongarch64 -emit-llvm -O2 %s -o - | FileCheck %s + +typedef signed char v16i8 __attribute__((vector_size(16), aligned(16))); + +// CHECK-LABEL: @test_vr0( +// CHECK: tail call void asm sideeffect "", "{$vr0}"(<16 x i8> undef) #[[ATTR1:[0-9]+]], !srcloc !2 +// +void test_vr0() { + register v16i8 a asm ("$vr0"); + asm ("" :: "f"(a)); +} + +// CHECK-LABEL: @test_vr7( +// CHECK: tail call void asm sideeffect "", "{$vr7}"(<16 x i8> undef) #[[ATTR1]], !srcloc !3 +// +void test_vr7() { + register v16i8 a asm ("$vr7"); + asm ("" :: "f"(a)); +} + +// CHECK-LABEL: @test_vr15( +// CHECK: tail call void asm sideeffect "", "{$vr15}"(<16 x i8> undef) #[[ATTR1]], !srcloc !4 +// +void test_vr15() { + register v16i8 a asm ("$vr15"); + asm ("" :: "f"(a)); +} + +// CHECK-LABEL: @test_vr31( +// CHECK: tail call void asm sideeffect "", "{$vr31}"(<16 x i8> undef) #[[ATTR1]], !srcloc !5 +// +void test_vr31() { + register v16i8 a asm ("$vr31"); + asm ("" :: "f"(a)); +} diff --git a/clang/test/CodeGen/LoongArch/lsx/inline-asm-operand-modifier.c b/clang/test/CodeGen/LoongArch/lsx/inline-asm-operand-modifier.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/LoongArch/lsx/inline-asm-operand-modifier.c @@ -0,0 +1,15 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 2 +// RUN: %clang_cc1 -triple loongarch64 -emit-llvm -O2 %s -o - | FileCheck %s + +typedef long long v2i64 __attribute__ ((vector_size(16), aligned(16))); + +// CHECK-LABEL: define dso_local void @test_w +// CHECK-SAME: () local_unnamed_addr #[[ATTR0:[0-9]+]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[TMP0:%.*]] = tail call <2 x i64> asm sideeffect "vldi ${0:w}, 1", "=f"() #[[ATTR1:[0-9]+]], !srcloc !2 +// CHECK-NEXT: ret void +// +void test_w() { + v2i64 v2i64_r; + asm volatile ("vldi %w0, 1" : "=f" (v2i64_r)); +} 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 @@ -67,6 +67,20 @@ return false; } break; + case 'w': // Print LSX registers. + if (MO.getReg().id() >= LoongArch::VR0 && + MO.getReg().id() <= LoongArch::VR31) + break; + // The modifier is 'w' but the operand is not an LSX register; Report an + // unknown operand error. + return true; + case 'u': // Print LASX registers. + if (MO.getReg().id() >= LoongArch::XR0 && + MO.getReg().id() <= LoongArch::XR31) + break; + // The modifier is 'u' but the operand is not an LASX register; Report an + // unknown operand error. + return true; // TODO: handle other extra codes if any. } } 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 @@ -53,6 +53,16 @@ addRegisterClass(MVT::f32, &LoongArch::FPR32RegClass); if (Subtarget.hasBasicD()) addRegisterClass(MVT::f64, &LoongArch::FPR64RegClass); + if (Subtarget.hasExtLSX()) { + for (auto VT : {MVT::v4f32, MVT::v2f64, MVT::v16i8, MVT::v8i16, MVT::v4i32, + MVT::v2i64}) + addRegisterClass(VT, &LoongArch::LSX128RegClass); + } + if (Subtarget.hasExtLASX()) { + for (auto VT : {MVT::v8f32, MVT::v4f64, MVT::v32i8, MVT::v16i16, MVT::v8i32, + MVT::v4i64}) + addRegisterClass(VT, &LoongArch::LASX256RegClass); + } setLoadExtAction({ISD::EXTLOAD, ISD::SEXTLOAD, ISD::ZEXTLOAD}, GRLenVT, MVT::i1, Promote); @@ -3044,6 +3054,12 @@ return std::make_pair(0U, &LoongArch::FPR32RegClass); if (Subtarget.hasBasicD() && VT == MVT::f64) return std::make_pair(0U, &LoongArch::FPR64RegClass); + if (Subtarget.hasExtLSX() && + TRI->isTypeLegalForClass(LoongArch::LSX128RegClass, VT)) + return std::make_pair(0U, &LoongArch::LSX128RegClass); + if (Subtarget.hasExtLASX() && + TRI->isTypeLegalForClass(LoongArch::LASX256RegClass, VT)) + return std::make_pair(0U, &LoongArch::LASX256RegClass); break; default: break; @@ -3061,7 +3077,8 @@ // 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")) { + if (Constraint.startswith("{$r") || Constraint.startswith("{$f") || + Constraint.startswith("{$vr") || Constraint.startswith("{$xr")) { bool IsFP = Constraint[2] == 'f'; std::pair Temp = Constraint.split('$'); std::pair R; diff --git a/llvm/test/CodeGen/LoongArch/lasx/inline-asm-operand-modifier.ll b/llvm/test/CodeGen/LoongArch/lasx/inline-asm-operand-modifier.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/LoongArch/lasx/inline-asm-operand-modifier.ll @@ -0,0 +1,14 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc --mtriple=loongarch64 --mattr=+lasx < %s | FileCheck %s + +define void @test_u() nounwind { +; CHECK-LABEL: test_u: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: #APP +; CHECK-NEXT: xvldi $xr0, 1 +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: ret +entry: + %0 = tail call <4 x i64> asm sideeffect "xvldi ${0:u}, 1", "=f"() + ret void +} diff --git a/llvm/test/CodeGen/LoongArch/lasx/inline-asm-reg-names.ll b/llvm/test/CodeGen/LoongArch/lasx/inline-asm-reg-names.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/LoongArch/lasx/inline-asm-reg-names.ll @@ -0,0 +1,58 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc --mtriple=loongarch64 --mattr=+lasx < %s | FileCheck %s + +define void @register_xr1() nounwind { +; CHECK-LABEL: register_xr1: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: #APP +; CHECK-NEXT: xvldi $xr1, 1 +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: ret +entry: + %0 = tail call <4 x i64> asm sideeffect "xvldi ${0:u}, 1", "={$xr1}"() + ret void +} + +define void @register_xr7() nounwind { +; CHECK-LABEL: register_xr7: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: #APP +; CHECK-NEXT: xvldi $xr7, 1 +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: ret +entry: + %0 = tail call <4 x i64> asm sideeffect "xvldi ${0:u}, 1", "={$xr7}"() + ret void +} + +define void @register_xr23() nounwind { +; CHECK-LABEL: register_xr23: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: #APP +; CHECK-NEXT: xvldi $xr23, 1 +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: ret +entry: + %0 = tail call <4 x i64> asm sideeffect "xvldi ${0:u}, 1", "={$xr23}"() + ret void +} + +;; The lower 64-bit of the vector register '$xr31' is same as the +;; floating-point register '$f31' ('$fs7'). And '$f31' ('$fs7') +;; is a callee-saved register which is preserved across calls. +;; That's why the fst.d and fld.d instructions are emitted. +define void @register_xr31() nounwind { +; CHECK-LABEL: register_xr31: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: addi.d $sp, $sp, -16 +; CHECK-NEXT: fst.d $fs7, $sp, 8 # 8-byte Folded Spill +; CHECK-NEXT: #APP +; CHECK-NEXT: xvldi $xr31, 1 +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: fld.d $fs7, $sp, 8 # 8-byte Folded Reload +; CHECK-NEXT: addi.d $sp, $sp, 16 +; CHECK-NEXT: ret +entry: + %0 = tail call <4 x i64> asm sideeffect "xvldi ${0:u}, 1", "={$xr31}"() + ret void +} diff --git a/llvm/test/CodeGen/LoongArch/lsx/inline-asm-operand-modifier.ll b/llvm/test/CodeGen/LoongArch/lsx/inline-asm-operand-modifier.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/LoongArch/lsx/inline-asm-operand-modifier.ll @@ -0,0 +1,14 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc --mtriple=loongarch64 --mattr=+lsx < %s | FileCheck %s + +define void @test_w() nounwind { +; CHECK-LABEL: test_w: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: #APP +; CHECK-NEXT: vldi $vr0, 1 +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: ret +entry: + %0 = tail call <2 x i64> asm sideeffect "vldi ${0:w}, 1", "=f"() + ret void +} diff --git a/llvm/test/CodeGen/LoongArch/lsx/inline-asm-reg-names.ll b/llvm/test/CodeGen/LoongArch/lsx/inline-asm-reg-names.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/LoongArch/lsx/inline-asm-reg-names.ll @@ -0,0 +1,58 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc --mtriple=loongarch64 --mattr=+lsx < %s | FileCheck %s + +define void @register_vr1() nounwind { +; CHECK-LABEL: register_vr1: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: #APP +; CHECK-NEXT: vldi $vr1, 1 +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: ret +entry: + %0 = tail call <2 x i64> asm sideeffect "vldi ${0:w}, 1", "={$vr1}"() + ret void +} + +define void @register_vr7() nounwind { +; CHECK-LABEL: register_vr7: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: #APP +; CHECK-NEXT: vldi $vr7, 1 +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: ret +entry: + %0 = tail call <2 x i64> asm sideeffect "vldi ${0:w}, 1", "={$vr7}"() + ret void +} + +define void @register_vr23() nounwind { +; CHECK-LABEL: register_vr23: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: #APP +; CHECK-NEXT: vldi $vr23, 1 +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: ret +entry: + %0 = tail call <2 x i64> asm sideeffect "vldi ${0:w}, 1", "={$vr23}"() + ret void +} + +;; The lower half of the vector register '$vr31' is same as the +;; floating-point register '$f31'. And '$f31' is a callee-saved +;; register which is preserved across calls. That's why the +;; fst.d and fld.d instructions are emitted. +define void @register_vr31() nounwind { +; CHECK-LABEL: register_vr31: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: addi.d $sp, $sp, -16 +; CHECK-NEXT: fst.d $fs7, $sp, 8 # 8-byte Folded Spill +; CHECK-NEXT: #APP +; CHECK-NEXT: vldi $vr31, 1 +; CHECK-NEXT: #NO_APP +; CHECK-NEXT: fld.d $fs7, $sp, 8 # 8-byte Folded Reload +; CHECK-NEXT: addi.d $sp, $sp, 16 +; CHECK-NEXT: ret +entry: + %0 = tail call <2 x i64> asm sideeffect "vldi ${0:w}, 1", "={$vr31}"() + ret void +}