diff --git a/clang/lib/Basic/Targets/RISCV.cpp b/clang/lib/Basic/Targets/RISCV.cpp --- a/clang/lib/Basic/Targets/RISCV.cpp +++ b/clang/lib/Basic/Targets/RISCV.cpp @@ -158,8 +158,10 @@ Builder.defineMacro(Twine("__riscv_", ExtName), Twine(Version)); } - if (ISAInfo->hasExtension("m")) { + if (ISAInfo->hasExtension("m") || ISAInfo->hasExtension("zmmul")) Builder.defineMacro("__riscv_mul"); + + if (ISAInfo->hasExtension("m")) { Builder.defineMacro("__riscv_div"); Builder.defineMacro("__riscv_muldiv"); } diff --git a/clang/test/Driver/riscv-arch.c b/clang/test/Driver/riscv-arch.c --- a/clang/test/Driver/riscv-arch.c +++ b/clang/test/Driver/riscv-arch.c @@ -579,3 +579,12 @@ // RUN: -fsyntax-only 2>&1 | FileCheck -check-prefix=RV32-ZHINX-BADVERS %s // RV32-ZHINX-BADVERS: error: invalid arch name 'rv32izhinx0p1' // RV32-ZHINX-BADVERS: unsupported version number 0.1 for extension 'zhinx' + +// RUN: %clang -target riscv32-unknown-elf -march=rv32i_zmmul2p0 -### %s \ +// RUN: -fsyntax-only 2>&1 | FileCheck -check-prefix=RV32-ZMMUL-BADVERS %s +// RV32-ZMMUL-BADVERS: error: invalid arch name 'rv32i_zmmul2p0' +// RV32-ZMMUL-BADVERS: unsupported version number 2.0 for extension + +// RUN: %clang -target riscv32-unknown-elf -march=rv32i_zmmul1p0 -### %s \ +// RUN: -fsyntax-only 2>&1 | FileCheck -check-prefix=RV32-ZMMUL-GOODVERS %s +// RV32-ZMMUL-GOODVERS: "-target-feature" "+zmmul" diff --git a/llvm/lib/Support/RISCVISAInfo.cpp b/llvm/lib/Support/RISCVISAInfo.cpp --- a/llvm/lib/Support/RISCVISAInfo.cpp +++ b/llvm/lib/Support/RISCVISAInfo.cpp @@ -77,6 +77,8 @@ {"zkt", RISCVExtensionVersion{1, 0}}, {"zk", RISCVExtensionVersion{1, 0}}, + {"zmmul", RISCVExtensionVersion{1, 0}}, + {"v", RISCVExtensionVersion{1, 0}}, {"zvl32b", RISCVExtensionVersion{1, 0}}, {"zvl64b", RISCVExtensionVersion{1, 0}}, diff --git a/llvm/lib/Target/RISCV/RISCV.td b/llvm/lib/Target/RISCV/RISCV.td --- a/llvm/lib/Target/RISCV/RISCV.td +++ b/llvm/lib/Target/RISCV/RISCV.td @@ -19,6 +19,19 @@ AssemblerPredicate<(all_of FeatureStdExtM), "'M' (Integer Multiplication and Division)">; +def FeatureStdExtZmmul + : SubtargetFeature<"zmmul", "HasStdExtZmmul", "true", + "'Zmmul' (Integer Multiplication)">; +def HasStdExtZmmul : Predicate<"Subtarget->hasStdExtZmmul()">, + AssemblerPredicate<(all_of FeatureStdExtZmmul), + "'Zmmul' (Integer Multiplication)">; + +def HasStdExtMOrZmmul + : Predicate<"Subtarget->hasStdExtM() || Subtarget->hasStdExtZmmul()">, + AssemblerPredicate<(any_of FeatureStdExtM, FeatureStdExtZmmul), + "'M' (Integer Multiplication and Division) or " + "'Zmmul' (Integer Multiplication)">; + def FeatureStdExtA : SubtargetFeature<"a", "HasStdExtA", "true", "'A' (Atomic Instructions)">; diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp --- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp +++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp @@ -215,21 +215,26 @@ setLibcallName(RTLIB::MULO_I64, nullptr); } - if (!Subtarget.hasStdExtM()) { - setOperationAction({ISD::MUL, ISD::MULHS, ISD::MULHU, ISD::SDIV, ISD::UDIV, - ISD::SREM, ISD::UREM}, - XLenVT, Expand); + if (!Subtarget.hasStdExtM() && !Subtarget.hasStdExtZmmul()) { + setOperationAction({ISD::MUL, ISD::MULHS, ISD::MULHU}, XLenVT, Expand); } else { if (Subtarget.is64Bit()) { setOperationAction(ISD::MUL, {MVT::i32, MVT::i128}, Custom); - - setOperationAction({ISD::SDIV, ISD::UDIV, ISD::UREM}, - {MVT::i8, MVT::i16, MVT::i32}, Custom); } else { setOperationAction(ISD::MUL, MVT::i64, Custom); } } + if (!Subtarget.hasStdExtM()) { + setOperationAction({ISD::SDIV, ISD::UDIV, ISD::SREM, ISD::UREM}, + XLenVT, Expand); + } else { + if (Subtarget.is64Bit()) { + setOperationAction({ISD::SDIV, ISD::UDIV, ISD::UREM}, + {MVT::i8, MVT::i16, MVT::i32}, Custom); + } + } + setOperationAction( {ISD::SDIVREM, ISD::UDIVREM, ISD::SMUL_LOHI, ISD::UMUL_LOHI}, XLenVT, Expand); @@ -12258,10 +12263,12 @@ bool RISCVTargetLowering::decomposeMulByConstant(LLVMContext &Context, EVT VT, SDValue C) const { // Check integral scalar types. + const bool HasExtMOrZmmul = + Subtarget.hasStdExtM() || Subtarget.hasStdExtZmmul(); if (VT.isScalarInteger()) { // Omit the optimization if the sub target has the M extension and the data // size exceeds XLen. - if (Subtarget.hasStdExtM() && VT.getSizeInBits() > Subtarget.getXLen()) + if (HasExtMOrZmmul && VT.getSizeInBits() > Subtarget.getXLen()) return false; if (auto *ConstNode = dyn_cast(C.getNode())) { // Break the MUL to a SLLI and an ADD/SUB. @@ -12276,7 +12283,7 @@ return true; // Omit the following optimization if the sub target has the M extension // and the data size >= XLen. - if (Subtarget.hasStdExtM() && VT.getSizeInBits() >= Subtarget.getXLen()) + if (HasExtMOrZmmul && VT.getSizeInBits() >= Subtarget.getXLen()) return false; // Break the MUL to two SLLI instructions and an ADD/SUB, if Imm needs // a pair of LUI/ADDI. diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp --- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp +++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp @@ -1883,10 +1883,11 @@ } else { Register N = MRI.createVirtualRegister(&RISCV::GPRRegClass); movImm(MBB, II, DL, N, NumOfVReg, Flag); - if (!STI.hasStdExtM()) + if (!STI.hasStdExtM() && !STI.hasStdExtZmmul()) MF.getFunction().getContext().diagnose(DiagnosticInfoUnsupported{ MF.getFunction(), - "M-extension must be enabled to calculate the vscaled size/offset."}); + "M- or Zmmul-extension must be enabled to calculate the vscaled size/" + "offset."}); BuildMI(MBB, II, DL, get(RISCV::MUL), VL) .addReg(VL, RegState::Kill) .addReg(N, RegState::Kill) diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoM.td b/llvm/lib/Target/RISCV/RISCVInstrInfoM.td --- a/llvm/lib/Target/RISCV/RISCVInstrInfoM.td +++ b/llvm/lib/Target/RISCV/RISCVInstrInfoM.td @@ -24,7 +24,7 @@ // Instructions //===----------------------------------------------------------------------===// -let Predicates = [HasStdExtM] in { +let Predicates = [HasStdExtMOrZmmul] in { def MUL : ALU_rr<0b0000001, 0b000, "mul", /*Commutable*/1>, Sched<[WriteIMul, ReadIMul, ReadIMul]>; def MULH : ALU_rr<0b0000001, 0b001, "mulh", /*Commutable*/1>, @@ -33,6 +33,9 @@ Sched<[WriteIMul, ReadIMul, ReadIMul]>; def MULHU : ALU_rr<0b0000001, 0b011, "mulhu", /*Commutable*/1>, Sched<[WriteIMul, ReadIMul, ReadIMul]>; +} // Predicates = [HasStdExtMOrZmmul] + +let Predicates = [HasStdExtM] in { def DIV : ALU_rr<0b0000001, 0b100, "div">, Sched<[WriteIDiv, ReadIDiv, ReadIDiv]>; def DIVU : ALU_rr<0b0000001, 0b101, "divu">, @@ -43,9 +46,12 @@ Sched<[WriteIDiv, ReadIDiv, ReadIDiv]>; } // Predicates = [HasStdExtM] -let Predicates = [HasStdExtM, IsRV64] in { +let Predicates = [HasStdExtMOrZmmul, IsRV64] in { def MULW : ALUW_rr<0b0000001, 0b000, "mulw", /*Commutable*/1>, Sched<[WriteIMul32, ReadIMul32, ReadIMul32]>; +} // Predicates = [HasStdExtMOrZmmul, IsRV64] + +let Predicates = [HasStdExtM, IsRV64] in { def DIVW : ALUW_rr<0b0000001, 0b100, "divw">, Sched<[WriteIDiv32, ReadIDiv32, ReadIDiv32]>; def DIVUW : ALUW_rr<0b0000001, 0b101, "divuw">, @@ -60,21 +66,25 @@ // Pseudo-instructions and codegen patterns //===----------------------------------------------------------------------===// -let Predicates = [HasStdExtM] in { +let Predicates = [HasStdExtMOrZmmul] in { def : PatGprGpr; def : PatGprGpr; def : PatGprGpr; def : PatGprGpr; +} // Predicates = [HasStdExtMOrZmmul] + +let Predicates = [HasStdExtM] in { def : PatGprGpr; def : PatGprGpr; def : PatGprGpr; def : PatGprGpr; } // Predicates = [HasStdExtM] -let Predicates = [HasStdExtM, IsRV64] in { // Select W instructions if only the lower 32-bits of the result are used. +let Predicates = [HasStdExtMOrZmmul, IsRV64] in def : PatGprGpr, MULW>; +let Predicates = [HasStdExtM, IsRV64] in { def : PatGprGpr; def : PatGprGpr; def : PatGprGpr; @@ -96,11 +106,11 @@ (REMW GPR:$rs1, GPR:$rs2)>; } // Predicates = [HasStdExtM, IsRV64] -let Predicates = [HasStdExtM, IsRV64, NotHasStdExtZba] in { +let Predicates = [HasStdExtMOrZmmul, IsRV64, NotHasStdExtZba] in { // Special case for calculating the full 64-bit product of a 32x32 unsigned // multiply where the inputs aren't known to be zero extended. We can shift the // inputs left by 32 and use a MULHU. This saves two SRLIs needed to finish // zeroing the upper 32 bits. def : Pat<(i64 (mul (and GPR:$rs1, 0xffffffff), (and GPR:$rs2, 0xffffffff))), (MULHU (SLLI GPR:$rs1, 32), (SLLI GPR:$rs2, 32))>; -} // Predicates = [HasStdExtM, IsRV64, NotHasStdExtZba] +} // Predicates = [HasStdExtMOrZmmul, IsRV64, NotHasStdExtZba] diff --git a/llvm/lib/Target/RISCV/RISCVSubtarget.h b/llvm/lib/Target/RISCV/RISCVSubtarget.h --- a/llvm/lib/Target/RISCV/RISCVSubtarget.h +++ b/llvm/lib/Target/RISCV/RISCVSubtarget.h @@ -89,6 +89,7 @@ bool HasStdExtZicbom = false; bool HasStdExtZicboz = false; bool HasStdExtZicbop = false; + bool HasStdExtZmmul = false; bool HasRV64 = false; bool IsRV32E = false; bool EnableLinkerRelax = false; @@ -184,6 +185,7 @@ bool hasStdExtZicbom() const { return HasStdExtZicbom; } bool hasStdExtZicboz() const { return HasStdExtZicboz; } bool hasStdExtZicbop() const { return HasStdExtZicbop; } + bool hasStdExtZmmul() const { return HasStdExtZmmul; } bool is64Bit() const { return HasRV64; } bool isRV32E() const { return IsRV32E; } bool enableLinkerRelax() const { return EnableLinkerRelax; } diff --git a/llvm/test/CodeGen/RISCV/attributes.ll b/llvm/test/CodeGen/RISCV/attributes.ll --- a/llvm/test/CodeGen/RISCV/attributes.ll +++ b/llvm/test/CodeGen/RISCV/attributes.ll @@ -1,6 +1,8 @@ ;; Generate ELF attributes from llc. ; RUN: llc -mtriple=riscv32 -mattr=+m %s -o - | FileCheck --check-prefix=RV32M %s +; RUN: llc -mtriple=riscv32 -mattr=+zmmul %s -o - | FileCheck --check-prefix=RV32ZMMUL %s +; RUN: llc -mtriple=riscv32 -mattr=+m,+zmmul %s -o - | FileCheck --check-prefix=RV32MZMMUL %s ; RUN: llc -mtriple=riscv32 -mattr=+a %s -o - | FileCheck --check-prefix=RV32A %s ; RUN: llc -mtriple=riscv32 -mattr=+f %s -o - | FileCheck --check-prefix=RV32F %s ; RUN: llc -mtriple=riscv32 -mattr=+d %s -o - | FileCheck --check-prefix=RV32D %s @@ -40,6 +42,8 @@ ; RUN: llc -mtriple=riscv32 -mattr=+zicboz %s -o - | FileCheck --check-prefix=RV32ZICBOZ %s ; RUN: llc -mtriple=riscv32 -mattr=+zicbop %s -o - | FileCheck --check-prefix=RV32ZICBOP %s ; RUN: llc -mtriple=riscv64 -mattr=+m %s -o - | FileCheck --check-prefix=RV64M %s +; RUN: llc -mtriple=riscv64 -mattr=+zmmul %s -o - | FileCheck --check-prefix=RV64ZMMUL %s +; RUN: llc -mtriple=riscv64 -mattr=+m,+zmmul %s -o - | FileCheck --check-prefix=RV64MZMMUL %s ; RUN: llc -mtriple=riscv64 -mattr=+a %s -o - | FileCheck --check-prefix=RV64A %s ; RUN: llc -mtriple=riscv64 -mattr=+f %s -o - | FileCheck --check-prefix=RV64F %s ; RUN: llc -mtriple=riscv64 -mattr=+d %s -o - | FileCheck --check-prefix=RV64D %s @@ -80,6 +84,8 @@ ; RUN: llc -mtriple=riscv64 -mattr=+zicbop %s -o - | FileCheck --check-prefix=RV64ZICBOP %s ; RV32M: .attribute 5, "rv32i2p0_m2p0" +; RV32ZMMUL: .attribute 5, "rv32i2p0_zmmul1p0" +; RV32MZMMUL: .attribute 5, "rv32i2p0_m2p0_zmmul1p0" ; RV32A: .attribute 5, "rv32i2p0_a2p0" ; RV32F: .attribute 5, "rv32i2p0_f2p0" ; RV32D: .attribute 5, "rv32i2p0_f2p0_d2p0" @@ -120,6 +126,8 @@ ; RV32ZICBOP: .attribute 5, "rv32i2p0_zicbop1p0" ; RV64M: .attribute 5, "rv64i2p0_m2p0" +; RV64ZMMUL: .attribute 5, "rv64i2p0_zmmul1p0" +; RV64MZMMUL: .attribute 5, "rv64i2p0_m2p0_zmmul1p0" ; RV64A: .attribute 5, "rv64i2p0_a2p0" ; RV64F: .attribute 5, "rv64i2p0_f2p0" ; RV64D: .attribute 5, "rv64i2p0_f2p0_d2p0" diff --git a/llvm/test/CodeGen/RISCV/zmmul.ll b/llvm/test/CodeGen/RISCV/zmmul.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/RISCV/zmmul.ll @@ -0,0 +1,41 @@ +; RUN: llc -mtriple=riscv32 -mattr=+zmmul -verify-machineinstrs < %s \ +; RUN: | not FileCheck -check-prefix=CHECK-DIV %s +; RUN: llc -mtriple=riscv64 -mattr=+zmmul -verify-machineinstrs < %s \ +; RUN: | not FileCheck -check-prefix=CHECK-DIV %s +; RUN: llc -mtriple=riscv32 -mattr=+zmmul -verify-machineinstrs < %s \ +; RUN: | not FileCheck -check-prefix=CHECK-REM %s +; RUN: llc -mtriple=riscv64 -mattr=+zmmul -verify-machineinstrs < %s \ +; RUN: | not FileCheck -check-prefix=CHECK-REM %s + +; RUN: llc -mtriple=riscv32 -mattr=+zmmul -verify-machineinstrs < %s \ +; RUN: | not FileCheck -check-prefix=CHECK-UDIV %s +; RUN: llc -mtriple=riscv64 -mattr=+zmmul -verify-machineinstrs < %s \ +; RUN: | not FileCheck -check-prefix=CHECK-UDIV %s +; RUN: llc -mtriple=riscv32 -mattr=+zmmul -verify-machineinstrs < %s \ +; RUN: | not FileCheck -check-prefix=CHECK-UREM %s +; RUN: llc -mtriple=riscv64 -mattr=+zmmul -verify-machineinstrs < %s \ +; RUN: | not FileCheck -check-prefix=CHECK-UREM %s + +; RUN: llc -mtriple=riscv32 -mattr=+zmmul -verify-machineinstrs < %s \ +; RUN: | FileCheck -check-prefix=CHECK-MUL %s +; RUN: llc -mtriple=riscv64 -mattr=+zmmul -verify-machineinstrs < %s \ +; RUN: | FileCheck -check-prefix=CHECK-MUL %s + +; RUN: llc -mtriple=riscv32 -mattr=+m -verify-machineinstrs < %s \ +; RUN: | FileCheck -check-prefixes=CHECK-MUL,CHECK-UDIV,CHECK-DIV,CHECK-UREM,CHECK-REM %s +; RUN: llc -mtriple=riscv64 -mattr=+m -verify-machineinstrs < %s \ +; RUN: | FileCheck -check-prefixes=CHECK-MUL,CHECK-UDIV,CHECK-DIV,CHECK-UREM,CHECK-REM %s + +define i32 @foo(i32 %a, i32 %b) { +; CHECK-UDIV: divu{{w?}} {{[as]}}{{[0-9]}}, {{[as]}}{{[0-9]}}, {{[as]}}{{[0-9]}} + %1 = udiv i32 %a, %b +; CHECK-DIV: div{{w?}} {{[as]}}{{[0-9]}}, {{[as]}}{{[0-9]}}, {{[as]}}{{[0-9]}} + %2 = sdiv i32 %a, %1 +; CHECK-MUL: mul{{w?}} {{[as]}}{{[0-9]}}, {{[as]}}{{[0-9]}}, {{[as]}}{{[0-9]}} + %3 = mul i32 %b, %2 +; CHECK-UREM: remu{{w?}} {{[as]}}{{[0-9]}}, {{[as]}}{{[0-9]}}, {{[as]}}{{[0-9]}} + %4 = urem i32 %3, %b +; CHECK-REM: rem{{w?}} {{[as]}}{{[0-9]}}, {{[as]}}{{[0-9]}}, {{[as]}}{{[0-9]}} + %5 = srem i32 %4, %a + ret i32 %5 +} diff --git a/llvm/test/MC/RISCV/rv32i-invalid.s b/llvm/test/MC/RISCV/rv32i-invalid.s --- a/llvm/test/MC/RISCV/rv32i-invalid.s +++ b/llvm/test/MC/RISCV/rv32i-invalid.s @@ -169,7 +169,7 @@ xor s2, s2 # CHECK: :[[@LINE]]:1: error: too few operands for instruction # Instruction not in the base ISA -mul a4, ra, s0 # CHECK: :[[@LINE]]:1: error: instruction requires the following: 'M' (Integer Multiplication and Division) +div a4, ra, s0 # CHECK: :[[@LINE]]:1: error: instruction requires the following: 'M' (Integer Multiplication and Division) amomaxu.w s5, s4, (s3) # CHECK: :[[@LINE]]:1: error: instruction requires the following: 'A' (Atomic Instructions) fadd.s ft0, ft1, ft2 # CHECK: :[[@LINE]]:1: error: instruction requires the following: 'F' (Single-Precision Floating-Point){{$}} fadd.h ft0, ft1, ft2 # CHECK: :[[@LINE]]:1: error: instruction requires the following: 'Zfh' (Half-Precision Floating-Point) diff --git a/llvm/test/MC/RISCV/rv32zmmul-invaild.s b/llvm/test/MC/RISCV/rv32zmmul-invaild.s new file mode 100644 --- /dev/null +++ b/llvm/test/MC/RISCV/rv32zmmul-invaild.s @@ -0,0 +1,14 @@ +# RUN: not llvm-mc %s -triple=riscv32 -mattr=+zmmul -riscv-no-aliases 2>&1 \ +# RUN: | FileCheck -check-prefixes=CHECK-ERROR %s + +# CHECK-ERROR: 5:1: error: instruction requires the following: 'M' (Integer Multiplication and Division) +div s0, s0, s0 + +# CHECK-ERROR: 8:1: error: instruction requires the following: 'M' (Integer Multiplication and Division) +divu gp, a0, a1 + +# CHECK-ERROR: 11:1: error: instruction requires the following: 'M' (Integer Multiplication and Division) +rem s2, s2, s8 + +# CHECK-ERROR: 14:1: error: instruction requires the following: 'M' (Integer Multiplication and Division) +remu x18, x18, x24 diff --git a/llvm/test/MC/RISCV/rv32zmmul-valid.s b/llvm/test/MC/RISCV/rv32zmmul-valid.s new file mode 100644 --- /dev/null +++ b/llvm/test/MC/RISCV/rv32zmmul-valid.s @@ -0,0 +1,14 @@ +# RUN: llvm-mc %s -triple=riscv32 -mattr=+zmmul -riscv-no-aliases 2>&1 \ +# RUN: | FileCheck -check-prefixes=CHECK-INST %s + +# CHECK-INST: mul a4, ra, s0 +mul a4, ra, s0 + +# CHECK-INST: mulh ra, zero, zero +mulh x1, x0, x0 + +# CHECK-INST: mulhsu t0, t2, t1 +mulhsu t0, t2, t1 + +# CHECK-INST: mulhu a5, a4, a3 +mulhu a5, a4, a3 diff --git a/llvm/test/MC/RISCV/rv64zmmul-invalid.s b/llvm/test/MC/RISCV/rv64zmmul-invalid.s new file mode 100644 --- /dev/null +++ b/llvm/test/MC/RISCV/rv64zmmul-invalid.s @@ -0,0 +1,14 @@ +# RUN: not llvm-mc %s -triple=riscv64 -mattr=+zmmul -riscv-no-aliases 2>&1 \ +# RUN: | FileCheck -check-prefixes=CHECK-ERROR %s + +# CHECK-ERROR: 5:1: error: instruction requires the following: 'M' (Integer Multiplication and Division) +divw tp, t0, t1 + +# CHECK-ERROR: 8:1: error: instruction requires the following: 'M' (Integer Multiplication and Division) +divuw t2, s0, s2 + +# CHECK-ERROR: 11:1: error: instruction requires the following: 'M' (Integer Multiplication and Division) +remw a0, a1, a2 + +# CHECK-ERROR: 14:1: error: instruction requires the following: 'M' (Integer Multiplication and Division) +remuw a3, a4, a5 diff --git a/llvm/test/MC/RISCV/rv64zmmul-valid.s b/llvm/test/MC/RISCV/rv64zmmul-valid.s new file mode 100644 --- /dev/null +++ b/llvm/test/MC/RISCV/rv64zmmul-valid.s @@ -0,0 +1,5 @@ +# RUN: llvm-mc %s -triple=riscv64 -mattr=+zmmul -riscv-no-aliases 2>&1 \ +# RUN: | FileCheck -check-prefixes=CHECK-INST %s + +# CHECK-INST: mulw ra, sp, gp +mulw ra, sp, gp