diff --git a/llvm/lib/Target/RISCV/CMakeLists.txt b/llvm/lib/Target/RISCV/CMakeLists.txt index 8715403f3839a..cbb276a0dc04d 100644 --- a/llvm/lib/Target/RISCV/CMakeLists.txt +++ b/llvm/lib/Target/RISCV/CMakeLists.txt @@ -15,6 +15,7 @@ tablegen(LLVM RISCVGenRegisterBank.inc -gen-register-bank) tablegen(LLVM RISCVGenRegisterInfo.inc -gen-register-info) tablegen(LLVM RISCVGenSearchableTables.inc -gen-searchable-tables) tablegen(LLVM RISCVGenSubtargetInfo.inc -gen-subtarget) +tablegen(LLVM RISCVGenExegesis.inc -gen-exegesis) set(LLVM_TARGET_DEFINITIONS RISCVGISel.td) tablegen(LLVM RISCVGenGlobalISel.inc -gen-global-isel) diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h index 08f056f78979a..552f5962b22a5 100644 --- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h +++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h @@ -299,6 +299,9 @@ enum OperandType : unsigned { OPERAND_RVKRNUM_2_14, OPERAND_SPIMM, OPERAND_LAST_RISCV_IMM = OPERAND_SPIMM, + // Operand is a 3-bit rounding mode, '111' indicates FRM register. + // Represents 'frm' argument passing to floating-point operations. + OPERAND_FRMARG, // Operand is either a register or uimm5, this is used by V extension pseudo // instructions to represent a value that be passed as AVL to either vsetvli // or vsetivli. diff --git a/llvm/lib/Target/RISCV/RISCV.td b/llvm/lib/Target/RISCV/RISCV.td index 09f496574d64a..fe8d0a757d58b 100644 --- a/llvm/lib/Target/RISCV/RISCV.td +++ b/llvm/lib/Target/RISCV/RISCV.td @@ -82,3 +82,9 @@ def RISCV : Target { let AssemblyWriters = [RISCVAsmWriter]; let AllowRegisterRenaming = 1; } + +//===----------------------------------------------------------------------===// +// Pfm Counters +//===----------------------------------------------------------------------===// + +include "RISCVPfmCounters.td" diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.td b/llvm/lib/Target/RISCV/RISCVInstrInfo.td index 9d574edb4e6d1..c0a77db8601c7 100644 --- a/llvm/lib/Target/RISCV/RISCVInstrInfo.td +++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.td @@ -511,7 +511,7 @@ class BranchCC_rri funct3, string opcodestr> let isTerminator = 1; } -let hasSideEffects = 0, mayLoad = 1, mayStore = 0 in { +let hasSideEffects = 0, mayLoad = 1, mayStore = 0, UseNamedOperandTable = 1 in { class Load_ri funct3, string opcodestr> : RVInstI; @@ -526,7 +526,7 @@ class HLoad_r funct7, bits<5> funct5, string opcodestr> // Operands for stores are in the order srcreg, base, offset rather than // reflecting the order these fields are specified in the instruction // encoding. -let hasSideEffects = 0, mayLoad = 0, mayStore = 1 in { +let hasSideEffects = 0, mayLoad = 0, mayStore = 1, UseNamedOperandTable = 1 in { class Store_rri funct3, string opcodestr> : RVInstS { let ParserMatchClass = FRMArg; let PrintMethod = "printFRMArg"; let DecoderMethod = "decodeFRMArg"; + let OperandType = "OPERAND_FRMARG"; + let OperandNamespace = "RISCVOp"; } // Variants of the rounding mode operand that default to 'rne'. This is used @@ -150,6 +152,8 @@ def frmarglegacy : Operand { let ParserMatchClass = FRMArgLegacy; let PrintMethod = "printFRMArgLegacy"; let DecoderMethod = "decodeFRMArg"; + let OperandType = "OPERAND_FRMARG"; + let OperandNamespace = "RISCVOp"; } //===----------------------------------------------------------------------===// diff --git a/llvm/lib/Target/RISCV/RISCVPfmCounters.td b/llvm/lib/Target/RISCV/RISCVPfmCounters.td new file mode 100644 index 0000000000000..33650dc5b0017 --- /dev/null +++ b/llvm/lib/Target/RISCV/RISCVPfmCounters.td @@ -0,0 +1,18 @@ +//===-- RISCVPfmCounters.td - RISC-V Hardware Counters -----*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This describes the available hardware counters for RISC-V. +// +//===----------------------------------------------------------------------===// + +def CpuCyclesPfmCounter : PfmCounter<"CPU_CYCLES">; + +def DefaultPfmCounters : ProcPfmCounters { + let CycleCounter = CpuCyclesPfmCounter; +} +def : PfmCountersDefaultBinding; diff --git a/llvm/test/tools/llvm-exegesis/RISCV/latency-by-extension-A.s b/llvm/test/tools/llvm-exegesis/RISCV/latency-by-extension-A.s new file mode 100644 index 0000000000000..bdc02d4af2155 --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/RISCV/latency-by-extension-A.s @@ -0,0 +1,59 @@ +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=AMOAND_D -mattr="+a" | FileCheck --check-prefix=AMOAND_D %s + +AMOAND_D: --- +AMOAND_D-NEXT: mode: latency +AMOAND_D-NEXT: key: +AMOAND_D-NEXT: instructions: +AMOAND_D-NEXT: - 'AMOAND_D [[RE01:X[0-9]+]] X10 [[RE01:X[0-9]+]]' +AMOAND_D-NEXT: config: '' +AMOAND_D-NEXT: register_initial_values: +AMOAND_D-NEXT: - '[[RE01:X[0-9]+]]=0x0' +AMOAND_D-DAG: ... + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=AMOADD_W -mattr="+a" | FileCheck --check-prefix=AMOADD_W %s + +AMOADD_W: --- +AMOADD_W-NEXT: mode: latency +AMOADD_W-NEXT: key: +AMOADD_W-NEXT: instructions: +AMOADD_W-NEXT: - 'AMOADD_W [[RE02:X[0-9]+]] X10 [[RE02:X[0-9]+]]' +AMOADD_W-NEXT: config: '' +AMOADD_W-NEXT: register_initial_values: +AMOADD_W-NEXT: - '[[RE02:X[0-9]+]]=0x0' +AMOADD_W-DAG: ... + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=AMOMAXU_D -mattr="+a" | FileCheck --check-prefix=AMOMAXU_D %s + +AMOMAXU_D: --- +AMOMAXU_D-NEXT: mode: latency +AMOMAXU_D-NEXT: key: +AMOMAXU_D-NEXT: instructions: +AMOMAXU_D-NEXT: - 'AMOMAXU_D [[RE03:X[0-9]+]] X10 [[RE03:X[0-9]+]]' +AMOMAXU_D-NEXT: config: '' +AMOMAXU_D-NEXT: register_initial_values: +AMOMAXU_D-NEXT: - '[[RE03:X[0-9]+]]=0x0' +AMOMAXU_D-DAG: ... + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=AMOMIN_W -mattr="+a" | FileCheck --check-prefix=AMOMIN_W %s + +AMOMIN_W: --- +AMOMIN_W-NEXT: mode: latency +AMOMIN_W-NEXT: key: +AMOMIN_W-NEXT: instructions: +AMOMIN_W-NEXT: - 'AMOMIN_W [[RE04:X[0-9]+]] X10 [[RE04:X[0-9]+]]' +AMOMIN_W-NEXT: config: '' +AMOMIN_W-NEXT: register_initial_values: +AMOMIN_W-NEXT: - '[[RE04:X[0-9]+]]=0x0' +AMOMIN_W-DAG: ... + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=AMOXOR_D -mattr="+a" | FileCheck --check-prefix=AMOXOR_D %s + +AMOXOR_D: --- +AMOXOR_D-NEXT: mode: latency +AMOXOR_D-NEXT: key: +AMOXOR_D-NEXT: instructions: +AMOXOR_D-NEXT: - 'AMOXOR_D [[RE05:X[0-9]+]] X10 [[RE05:X[0-9]+]]' +AMOXOR_D-NEXT: config: '' +AMOXOR_D-NEXT: register_initial_values: +AMOXOR_D-NEXT: - '[[RE05:X[0-9]+]]=0x0' +AMOXOR_D-DAG: ... diff --git a/llvm/test/tools/llvm-exegesis/RISCV/latency-by-extension-C.s b/llvm/test/tools/llvm-exegesis/RISCV/latency-by-extension-C.s new file mode 100644 index 0000000000000..00bd79729539f --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/RISCV/latency-by-extension-C.s @@ -0,0 +1,65 @@ +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=C_ADDI -mattr=+c | FileCheck --check-prefix=C_ADDI %s + +C_ADDI: --- +C_ADDI-NEXT: mode: latency +C_ADDI-NEXT: key: +C_ADDI-NEXT: instructions: +C_ADDI-NEXT: - 'C_ADDI [[REG01:X[0-9]+]] [[RE02:X[0-9]+]] [[IMM0:i_0x[0-9]+]]' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=C_ADDIW -mattr=+c | FileCheck --check-prefix=C_ADDIW %s + +C_ADDIW: --- +C_ADDIW-NEXT: mode: latency +C_ADDIW-NEXT: key: +C_ADDIW-NEXT: instructions: +C_ADDIW-NEXT: - 'C_ADDIW [[REG11:X[0-9]+]] [[RE12:X[0-9]+]] [[IMM1:i_0x[0-9]+]]' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=C_ANDI -mattr=+c | FileCheck --check-prefix=C_ANDI %s + +C_ANDI: --- +C_ANDI-NEXT: mode: latency +C_ANDI-NEXT: key: +C_ANDI-NEXT: instructions: +C_ANDI-NEXT: - 'C_ANDI [[REG31:X[0-9]+]] [[REG32:X[0-9]+]] [[IMM3:i_0x[0-9]+]]' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=C_SLLI -mattr=+c | FileCheck --check-prefix=C_SLLI %s + +C_SLLI: --- +C_SLLI-NEXT: mode: latency +C_SLLI-NEXT: key: +C_SLLI-NEXT: instructions: +C_SLLI-NEXT: - 'C_SLLI [[REG81:X[0-9]+]] [[REG82:X[0-9]+]] [[IMM8:i_0x[0-9]+]]' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=C_SRAI -mattr=+c | FileCheck --check-prefix=C_SRAI %s + +C_SRAI: --- +C_SRAI-NEXT: mode: latency +C_SRAI-NEXT: key: +C_SRAI-NEXT: instructions: +C_SRAI-NEXT: - 'C_SRAI [[REG91:X[0-9]+]] [[REG92:X[0-9]+]] [[IMM9:i_0x[0-9]+]]' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=C_SRLI -mattr=+c | FileCheck --check-prefix=C_SRLI %s + +C_SRLI: --- +C_SRLI-NEXT: mode: latency +C_SRLI-NEXT: key: +C_SRLI-NEXT: instructions: +C_SRLI-NEXT: - 'C_SRLI [[REG101:X[0-9]+]] [[REG102:X[0-9]+]] [[IMM10:i_0x[0-9]+]]' +C_SRLI-DAG: ... + + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=C_LD -mattr=+c | FileCheck --check-prefix=C_LD %s + +C_LD: --- +C_LD-NEXT: mode: latency +C_LD-NEXT: key: +C_LD-NEXT: instructions: +C_LD-NEXT: - 'C_LD [[REG61:X[0-9]+]] [[REG62:X[0-9]+]] [[IMM6:i_0x[0-9]+]]' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=C_LW -mattr=+c | FileCheck --check-prefix=C_LW %s + +C_LW: --- +C_LW-NEXT: mode: latency +C_LW-NEXT: key: +C_LW-NEXT: instructions: +C_LW-NEXT: - 'C_LW [[REG71:X[0-9]+]] [[REG72:X[0-9]+]] [[IMM7:i_0x[0-9]+]]' diff --git a/llvm/test/tools/llvm-exegesis/RISCV/latency-by-load.s b/llvm/test/tools/llvm-exegesis/RISCV/latency-by-load.s new file mode 100644 index 0000000000000..ba985e231e5ce --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/RISCV/latency-by-load.s @@ -0,0 +1,60 @@ +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=LD | FileCheck --check-prefix=LD %s + +LD: --- +LD-NEXT: mode: latency +LD-NEXT: key: +LD-NEXT: instructions: +LD-NEXT: - 'LD X10 X10 i_0x0' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=LW | FileCheck --check-prefix=LW %s + +LW: --- +LW-NEXT: mode: latency +LW-NEXT: key: +LW-NEXT: instructions: +LW-NEXT: - 'LW X10 X10 i_0x0' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=LH | FileCheck --check-prefix=LH %s + +LH: --- +LH-NEXT: mode: latency +LH-NEXT: key: +LH-NEXT: instructions: +LH-NEXT: - 'LH X10 X10 i_0x0' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=LWU | FileCheck --check-prefix=LWU %s + +LWU: --- +LWU-NEXT: mode: latency +LWU-NEXT: key: +LWU-NEXT: instructions: +LWU-NEXT: - 'LWU X10 X10 i_0x0' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=LBU | FileCheck --check-prefix=LBU %s + +LBU: --- +LBU-NEXT: mode: latency +LBU-NEXT: key: +LBU-NEXT: instructions: +LBU-NEXT: - 'LBU X10 X10 i_0x0' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=LUI 2>&1 | FileCheck --check-prefix=LUI %s + +LUI: LUI: No strategy found to make the execution serial + + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=LB | FileCheck --check-prefix=LB %s + +LB: --- +LB-NEXT: mode: latency +LB-NEXT: key: +LB-NEXT: instructions: +LB-NEXT: - 'LB X10 X10 i_0x0' + +# RUN: llvm-exegesis -mode=latency -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --benchmark-phase=assemble-measured-code -opcode-name=LR_W_RL -mattr="+a" | FileCheck --check-prefix=LR_W_RL %s + +LR_W_RL: --- +LR_W_RL-NEXT: mode: latency +LR_W_RL-NEXT: key: +LR_W_RL-NEXT: instructions: +LR_W_RL-NEXT: - 'LR_W_RL X10 X10' diff --git a/llvm/test/tools/llvm-exegesis/RISCV/latency-by-opcode-name-FADD_D.s b/llvm/test/tools/llvm-exegesis/RISCV/latency-by-opcode-name-FADD_D.s new file mode 100644 index 0000000000000..2dea89cca4d7e --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/RISCV/latency-by-opcode-name-FADD_D.s @@ -0,0 +1,11 @@ +# RUN: llvm-exegesis -mtriple=riscv64-unknown-linux-gnu --mcpu=generic -mode=latency --benchmark-phase=assemble-measured-code -mattr=+d -opcode-name=FADD_D | FileCheck %s + +CHECK: --- +CHECK-NEXT: mode: latency +CHECK-NEXT: key: +CHECK-NEXT: instructions: +CHECK-NEXT: - 'FADD_D [[REG1:F[0-9]+_D]] [[REG2:F[0-9]+_D]] [[REG3:F[0-9]+_D]] i_0x7' +CHECK-NEXT: config: '' +CHECK-NEXT: register_initial_values: +CHECK-DAG: - '[[REG1]]=0x0' +CHECK-DAG: ... diff --git a/llvm/tools/llvm-exegesis/lib/CMakeLists.txt b/llvm/tools/llvm-exegesis/lib/CMakeLists.txt index 414b49e5e021c..d95c37ff5426b 100644 --- a/llvm/tools/llvm-exegesis/lib/CMakeLists.txt +++ b/llvm/tools/llvm-exegesis/lib/CMakeLists.txt @@ -12,6 +12,9 @@ endif() if (LLVM_TARGETS_TO_BUILD MATCHES "Mips") list(APPEND LLVM_EXEGESIS_TARGETS "Mips") endif() +if(LLVM_TARGETS_TO_BUILD MATCHES "RISCV") + list(APPEND LLVM_EXEGESIS_TARGETS "RISCV") +endif() set(LLVM_EXEGESIS_TARGETS ${LLVM_EXEGESIS_TARGETS} PARENT_SCOPE) diff --git a/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp b/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp index fd156ee01e7ce..74a78b0f1c56f 100644 --- a/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp +++ b/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp @@ -57,6 +57,15 @@ const MCOperand &InstructionTemplate::getValueFor(const Operand &Op) const { return getValueFor(Instr->Variables[Op.getVariableIndex()]); } +MCOperand &InstructionTemplate::getValueForOperandIdx(unsigned OpIdx) { + return getValueFor(Instr->Variables[OpIdx]); +} + +const MCOperand & +InstructionTemplate::getValueForOperandIdx(unsigned OpIdx) const { + return getValueFor(Instr->Variables[OpIdx]); +} + bool InstructionTemplate::hasImmediateVariables() const { return any_of(Instr->Variables, [this](const Variable &Var) { return Instr->getPrimaryOperand(Var).isImmediate(); diff --git a/llvm/tools/llvm-exegesis/lib/CodeTemplate.h b/llvm/tools/llvm-exegesis/lib/CodeTemplate.h index 7aca224302a1f..662fd90306a45 100644 --- a/llvm/tools/llvm-exegesis/lib/CodeTemplate.h +++ b/llvm/tools/llvm-exegesis/lib/CodeTemplate.h @@ -35,6 +35,8 @@ struct InstructionTemplate { const MCOperand &getValueFor(const Variable &Var) const; MCOperand &getValueFor(const Operand &Op); const MCOperand &getValueFor(const Operand &Op) const; + MCOperand &getValueForOperandIdx(unsigned OpIdx); + const MCOperand &getValueForOperandIdx(unsigned OpIdx) const; bool hasImmediateVariables() const; const Instruction &getInstr() const { return *Instr; } ArrayRef getVariableValues() const { return VariableValues; } @@ -133,6 +135,10 @@ struct CodeTemplate { // the pointer to this memory is passed in to the function. unsigned ScratchSpacePointerInReg = 0; + // Require to pre-store value of a given register (fisrt) + // to scratch memory with given offset (second) + SmallVector, 2> PreinitScratchMemory; + #if defined(__GNUC__) && (defined(__clang__) || LLVM_GNUC_PREREQ(8, 0, 0)) // FIXME: GCC7 bug workaround. Drop #if after GCC7 no longer supported. private: diff --git a/llvm/tools/llvm-exegesis/lib/MCInstrDescView.cpp b/llvm/tools/llvm-exegesis/lib/MCInstrDescView.cpp index 9c926d1fc6112..f9b3666d36f78 100644 --- a/llvm/tools/llvm-exegesis/lib/MCInstrDescView.cpp +++ b/llvm/tools/llvm-exegesis/lib/MCInstrDescView.cpp @@ -95,11 +95,12 @@ Instruction::Instruction(const MCInstrDesc *Description, StringRef Name, const BitVector *ImplDefRegs, const BitVector *ImplUseRegs, const BitVector *AllDefRegs, - const BitVector *AllUseRegs) + const BitVector *AllUseRegs, + const BitVector *NotMemoryRegs) : Description(*Description), Name(Name), Operands(std::move(Operands)), Variables(std::move(Variables)), ImplDefRegs(*ImplDefRegs), ImplUseRegs(*ImplUseRegs), AllDefRegs(*AllDefRegs), - AllUseRegs(*AllUseRegs) {} + AllUseRegs(*AllUseRegs), NotMemoryRegs(*NotMemoryRegs) {} std::unique_ptr Instruction::create(const MCInstrInfo &InstrInfo, @@ -166,6 +167,8 @@ Instruction::create(const MCInstrInfo &InstrInfo, BitVector ImplUseRegs = RATC.emptyRegisters(); BitVector AllDefRegs = RATC.emptyRegisters(); BitVector AllUseRegs = RATC.emptyRegisters(); + BitVector NotMemoryRegs = RATC.emptyRegisters(); + for (const auto &Op : Operands) { if (Op.isReg()) { const auto &AliasingBits = Op.getRegisterAliasing().aliasedBits(); @@ -177,6 +180,8 @@ Instruction::create(const MCInstrInfo &InstrInfo, ImplDefRegs |= AliasingBits; if (Op.isUse() && Op.isImplicit()) ImplUseRegs |= AliasingBits; + if (Op.isUse() && !Op.isMemory()) + NotMemoryRegs |= AliasingBits; } } // Can't use make_unique because constructor is private. @@ -185,7 +190,8 @@ Instruction::create(const MCInstrInfo &InstrInfo, std::move(Variables), BVC.getUnique(std::move(ImplDefRegs)), BVC.getUnique(std::move(ImplUseRegs)), BVC.getUnique(std::move(AllDefRegs)), - BVC.getUnique(std::move(AllUseRegs)))); + BVC.getUnique(std::move(AllUseRegs)), + BVC.getUnique(std::move(NotMemoryRegs)))); } const Operand &Instruction::getPrimaryOperand(const Variable &Var) const { @@ -240,6 +246,12 @@ bool Instruction::hasAliasingRegisters( ForbiddenRegisters); } +bool Instruction::hasAliasingNotMemoryRegisters( + const BitVector &ForbiddenRegisters) const { + return anyCommonExcludingForbidden(AllDefRegs, NotMemoryRegs, + ForbiddenRegisters); +} + bool Instruction::hasOneUseOrOneDef() const { return AllDefRegs.count() || AllUseRegs.count(); } diff --git a/llvm/tools/llvm-exegesis/lib/MCInstrDescView.h b/llvm/tools/llvm-exegesis/lib/MCInstrDescView.h index f8ebc07d01f35..c6f47a438ad49 100644 --- a/llvm/tools/llvm-exegesis/lib/MCInstrDescView.h +++ b/llvm/tools/llvm-exegesis/lib/MCInstrDescView.h @@ -133,6 +133,12 @@ struct Instruction { // aliasing Use and Def registers. bool hasAliasingRegisters(const BitVector &ForbiddenRegisters) const; + // Whether this instruction is self aliasing through some registers. + // Repeating this instruction may execute sequentially by picking aliasing + // Def and Not Memory Use registers. It may also execute in parallel by + // picking non aliasing Def and Not Memory Use registers. + bool hasAliasingNotMemoryRegisters(const BitVector &ForbiddenRegisters) const; + // Whether this instruction's registers alias with OtherInstr's registers. bool hasAliasingRegistersThrough(const Instruction &OtherInstr, const BitVector &ForbiddenRegisters) const; @@ -160,12 +166,15 @@ struct Instruction { const BitVector &ImplUseRegs; // The set of aliased implicit use registers. const BitVector &AllDefRegs; // The set of all aliased def registers. const BitVector &AllUseRegs; // The set of all aliased use registers. + // The set of all aliased not memory use registers. + const BitVector &NotMemoryRegs; + private: Instruction(const MCInstrDesc *Description, StringRef Name, SmallVector Operands, SmallVector Variables, const BitVector *ImplDefRegs, const BitVector *ImplUseRegs, const BitVector *AllDefRegs, - const BitVector *AllUseRegs); + const BitVector *AllUseRegs, const BitVector *NotMemoryRegs); }; // Instructions are expensive to instantiate. This class provides a cache of diff --git a/llvm/tools/llvm-exegesis/lib/Mips/Target.cpp b/llvm/tools/llvm-exegesis/lib/Mips/Target.cpp index 731e037c240df..6231c971fadb9 100644 --- a/llvm/tools/llvm-exegesis/lib/Mips/Target.cpp +++ b/llvm/tools/llvm-exegesis/lib/Mips/Target.cpp @@ -62,7 +62,6 @@ class ExegesisMipsTarget : public ExegesisTarget { unsigned getMaxMemoryAccessSize() const override { return 64; } void fillMemoryOperands(InstructionTemplate &IT, unsigned Reg, unsigned Offset) const override; - std::vector setRegTo(const MCSubtargetInfo &STI, unsigned Reg, const APInt &Value) const override; bool matchesArch(Triple::ArchType Arch) const override { diff --git a/llvm/tools/llvm-exegesis/lib/PerfHelper.h b/llvm/tools/llvm-exegesis/lib/PerfHelper.h index 4a825b293b716..1e0fca7380863 100644 --- a/llvm/tools/llvm-exegesis/lib/PerfHelper.h +++ b/llvm/tools/llvm-exegesis/lib/PerfHelper.h @@ -119,7 +119,7 @@ class CounterGroup { virtual void start(); /// Stops the measurement of the event. - void stop(); + virtual void stop(); /// Returns the current value of the counter or error if it cannot be read. /// FunctionBytes: The benchmark function being executed. diff --git a/llvm/tools/llvm-exegesis/lib/RISCV/CMakeLists.txt b/llvm/tools/llvm-exegesis/lib/RISCV/CMakeLists.txt new file mode 100644 index 0000000000000..ea0ced1786a0c --- /dev/null +++ b/llvm/tools/llvm-exegesis/lib/RISCV/CMakeLists.txt @@ -0,0 +1,22 @@ +include_directories( + ${LLVM_MAIN_SRC_DIR}/lib/Target/RISCV + ${LLVM_BINARY_DIR}/lib/Target/RISCV +) + +set(LLVM_LINK_COMPONENTS + RISCV + Exegesis + Core + Support + ) + +add_llvm_library(LLVMExegesisRISCV + DISABLE_LLVM_LINK_LLVM_DYLIB + STATIC + Target.cpp + RISCVCounters.cpp + + DEPENDS + intrinsics_gen + RISCVCommonTableGen + ) diff --git a/llvm/tools/llvm-exegesis/lib/RISCV/RISCVCounters.cpp b/llvm/tools/llvm-exegesis/lib/RISCV/RISCVCounters.cpp new file mode 100644 index 0000000000000..a9d207fa3d157 --- /dev/null +++ b/llvm/tools/llvm-exegesis/lib/RISCV/RISCVCounters.cpp @@ -0,0 +1,73 @@ +//===-- RISCVCounters.cpp ---------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines RISC-V perf counters. +// +//===----------------------------------------------------------------------===// + +#include "RISCVCounters.h" + +namespace llvm { +namespace exegesis { + +// This implementation of RISCV target for Exegesis doesn't use libpfm +// and provides manual implementation of performance counters. + +inline uint64_t getRISCVCpuCyclesCount() { +#ifdef __riscv + uint64_t Counter; + asm("csrr %0, cycle" : "=r"(Counter)::"memory"); + return Counter; +#else + return 0; +#endif +} + +class RISCVCpuCyclesCounter : public pfm::CounterGroup { + uint64_t StartValue; + uint64_t EndValue; + uint64_t MeasurementCycles; + +public: + explicit RISCVCpuCyclesCounter(pfm::PerfEvent &&Event); + + void start() override { StartValue = getRISCVCpuCyclesCount(); } + + void stop() override { EndValue = getRISCVCpuCyclesCount(); } + + Expected> + readOrError(StringRef FunctionBytes) const override; +}; + +RISCVCpuCyclesCounter::RISCVCpuCyclesCounter(pfm::PerfEvent &&Event) + : CounterGroup(std::move(Event), {}) { + StartValue = getRISCVCpuCyclesCount(); + EndValue = getRISCVCpuCyclesCount(); + MeasurementCycles = EndValue - StartValue; + // If values of two calls CpuCyclesCounters don`t differ + // it means that counters don`t configured properly, report error. + // MeasurementCycles the smallest interval between two counter calls. + if (MeasurementCycles == 0) { + report_fatal_error("MeasurementCycles == 0, " + "performance counters are not configured."); + } + StartValue = EndValue = 0; +} + +Expected> +RISCVCpuCyclesCounter::readOrError(StringRef FunctionBytes) const { + uint64_t Counter = EndValue - StartValue - MeasurementCycles; + return SmallVector({static_cast(Counter)}); +} +std::unique_ptr +createRISCVCpuCyclesCounter(pfm::PerfEvent &&Event) { + return std::make_unique(std::move(Event)); +} + +} // namespace exegesis +} // namespace llvm diff --git a/llvm/tools/llvm-exegesis/lib/RISCV/RISCVCounters.h b/llvm/tools/llvm-exegesis/lib/RISCV/RISCVCounters.h new file mode 100644 index 0000000000000..203f6af76aaaf --- /dev/null +++ b/llvm/tools/llvm-exegesis/lib/RISCV/RISCVCounters.h @@ -0,0 +1,31 @@ +//===-- RISCVCounters.h -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// RISC-V perf counters. +/// +/// More info at: https://lwn.net/Articles/680985 +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVM_EXEGESIS_LIB_RISCV_RISCVCOUNTERS_H +#define LLVM_TOOLS_LLVM_EXEGESIS_LIB_RISCV_RISCVCOUNTERS_H + +#include "../PerfHelper.h" +#include "../Target.h" +#include + +namespace llvm { +namespace exegesis { + +std::unique_ptr +createRISCVCpuCyclesCounter(pfm::PerfEvent &&Event); + +} // namespace exegesis +} // namespace llvm + +#endif // LLVM_TOOLS_LLVM_EXEGESIS_LIB_RISCV_RISCVCOUNTERS_H diff --git a/llvm/tools/llvm-exegesis/lib/RISCV/Target.cpp b/llvm/tools/llvm-exegesis/lib/RISCV/Target.cpp new file mode 100644 index 0000000000000..84634db285ba6 --- /dev/null +++ b/llvm/tools/llvm-exegesis/lib/RISCV/Target.cpp @@ -0,0 +1,404 @@ +//===-- Target.cpp ----------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "../Target.h" + +#include "RISCVCounters.h" + +#include "MCTargetDesc/RISCVBaseInfo.h" +#include "MCTargetDesc/RISCVMCTargetDesc.h" +#include "MCTargetDesc/RISCVMatInt.h" +#include "RISCVInstrInfo.h" + +// include computeAvailableFeatures and computeRequiredFeatures. +#define GET_COMPUTE_FEATURES +#define GET_AVAILABLE_OPCODE_CHECKER +#include "RISCVGenInstrInfo.inc" +#undef GET_COMPUTE_FEATURES +#undef GET_AVAILABLE_OPCODE_CHECKER + +#include "llvm/CodeGen/MachineInstrBuilder.h" + +#include + +namespace llvm { +namespace exegesis { + +namespace { + +#include "RISCVGenExegesis.inc" + +class ExegesisRISCVTarget : public ExegesisTarget { +public: + ExegesisRISCVTarget(); + + Expected> + createCounter(StringRef CounterName, const LLVMState &State, + ArrayRef ValidationCounters, + const pid_t ProcessID) const override; + + bool checkOpcodeSupported(int Opcode, + const MCSubtargetInfo &SI) const override; + + MCRegister findRegisterByName(const StringRef RegName) const override; + + bool matchesArch(Triple::ArchType Arch) const override; + + std::vector setRegTo(const MCSubtargetInfo &STI, unsigned Reg, + const APInt &Value) const override; + + unsigned getDefaultLoopCounterRegister(const Triple &) const override; + + void decrementLoopCounterAndJump(MachineBasicBlock &MBB, + MachineBasicBlock &TargetMBB, + const MCInstrInfo &MII, + unsigned LoopRegister) const override; + + unsigned getScratchMemoryRegister(const Triple &TT) const override; + + void fillMemoryOperands(InstructionTemplate &IT, unsigned Reg, + unsigned Offset) const override; + + virtual std::vector + storeRegValueToScratch(const MCSubtargetInfo &STI, unsigned Reg, + unsigned Offset) const override; + ArrayRef getUnavailableRegisters() const override; + + Error randomizeTargetMCOperand(const Instruction &Instr, const Variable &Var, + MCOperand &AssignedValue, + const BitVector &ForbiddenRegs) const override; + + void processInstructionReservedRegs(InstructionTemplate &IT) const override; + + std::vector + generateInstructionVariants(const Instruction &Instr, + unsigned MaxConfigsPerOpcode) const override; +}; + +ExegesisRISCVTarget::ExegesisRISCVTarget() + : ExegesisTarget(RISCVCpuPfmCounters, RISCV_MC::isOpcodeAvailable) {} + +Expected> ExegesisRISCVTarget::createCounter( + StringRef CounterName, const LLVMState &State, + ArrayRef ValidationCounters, const pid_t ProcessID) const { + if (CounterName == RISCVPfmCounterNames[0]) { + // TODO add support for Linux perf counters + return createRISCVCpuCyclesCounter(pfm::PerfEvent(CounterName)); + } + return make_error(Twine("Unsupported performance counter '") + .concat(CounterName) + .concat("'")); +} + +bool ExegesisRISCVTarget::checkOpcodeSupported( + int Opcode, const MCSubtargetInfo &SI) const { + auto Features = SI.getFeatureBits(); + FeatureBitset AvailableFeatures = + RISCV_MC::computeAvailableFeatures(Features); + FeatureBitset RequiredFeatures = RISCV_MC::computeRequiredFeatures(Opcode); + FeatureBitset MissingFeatures = + (AvailableFeatures & RequiredFeatures) ^ RequiredFeatures; + return MissingFeatures.none(); +} + +#define GET_REGISTER_MATCHER +#include "RISCVGenAsmMatcher.inc" + +MCRegister +ExegesisRISCVTarget::findRegisterByName(const StringRef RegName) const { + MCRegister Reg; + if ((Reg = MatchRegisterName(RegName))) + return Reg; + if ((Reg = MatchRegisterAltName(RegName))) + return Reg; + return RISCV::NoRegister; +} + +bool ExegesisRISCVTarget::matchesArch(Triple::ArchType Arch) const { + return Arch == Triple::riscv32 || Arch == Triple::riscv64; +} + +// Stores constant value to a general-purpose (integer) register. +static std::vector loadIntReg(const MCSubtargetInfo &STI, unsigned Reg, + const APInt &Value) { + RISCVMatInt::InstSeq InstSeq = + RISCVMatInt::generateInstSeq(Value.getSExtValue(), STI); + // First instruction has form 'Op DestReg, X0, Imm' + MCRegister SrcReg = RISCV::X0; + MCRegister DestReg = Reg; + std::vector MatIntInstrs; + MatIntInstrs.reserve(InstSeq.size()); + for (const RISCVMatInt::Inst &Inst : InstSeq) { + switch (Inst.getOpndKind()) { + case RISCVMatInt::Imm: + MatIntInstrs.push_back( + MCInstBuilder(RISCV::LUI).addReg(DestReg).addImm(Inst.getImm())); + break; + case RISCVMatInt::RegX0: + MatIntInstrs.push_back(MCInstBuilder(RISCV::ADD_UW) + .addReg(DestReg) + .addReg(SrcReg) + .addReg(RISCV::X0)); + break; + case RISCVMatInt::RegReg: + MatIntInstrs.push_back(MCInstBuilder(Inst.getOpcode()) + .addReg(DestReg) + .addReg(SrcReg) + .addReg(SrcReg)); + break; + case RISCVMatInt::RegImm: + MatIntInstrs.push_back(MCInstBuilder(Inst.getOpcode()) + .addReg(DestReg) + .addReg(SrcReg) + .addImm(Inst.getImm())); + break; + default: + llvm_unreachable("Unexpected kind!"); + } + // Further instructions have form 'Op DestReg, DestReg, Imm' + SrcReg = DestReg; + } + return MatIntInstrs; +} + +const unsigned ScratchIntReg = RISCV::X30; // t5 + +// Stores constant bits to a floating-point register. +static std::vector loadFPRegBits(const MCSubtargetInfo &STI, + unsigned Reg, const APInt &Bits, + unsigned FmvOpcode) { + std::vector Instrs = loadIntReg(STI, ScratchIntReg, Bits); + Instrs.push_back(MCInstBuilder(FmvOpcode).addReg(Reg).addReg(ScratchIntReg)); + return Instrs; +} + +// main idea is: +// we support APInt only if (represented as double) it have zero fractional +// part: 1.0, 2.0, 3.0, etc... then we can do the trick: write int to tmp reg t5 +// and then do FCVT this is only reliable thing in 32-bit mode, otherwise we +// need to use __floatsidf +static std::vector loadFP64RegBits32(const MCSubtargetInfo &STI, + unsigned Reg, const APInt &Bits) { + double D = Bits.bitsToDouble(); + double IPart; + double FPart = std::modf(D, &IPart); + + if (std::abs(FPart) > std::numeric_limits::epsilon()) { + errs() << "loadFP64RegBits32 is not implemented for doubles like " << D + << ", please remove fractional part\n"; + return {}; + } + + std::vector Instrs = loadIntReg(STI, ScratchIntReg, Bits); + Instrs.push_back( + MCInstBuilder(RISCV::FCVT_D_W).addReg(Reg).addReg(ScratchIntReg)); + return Instrs; +} + +static MCInst nop() { + // ADDI X0, X0, 0 + return MCInstBuilder(RISCV::ADDI) + .addReg(RISCV::X0) + .addReg(RISCV::X0) + .addImm(0); +} + +static bool isVectorRegList(unsigned Reg) { + return RISCV::VRM2RegClass.contains(Reg) || + RISCV::VRM4RegClass.contains(Reg) || + RISCV::VRM8RegClass.contains(Reg) || + RISCV::VRN2M1RegClass.contains(Reg) || + RISCV::VRN2M2RegClass.contains(Reg) || + RISCV::VRN2M4RegClass.contains(Reg) || + RISCV::VRN3M1RegClass.contains(Reg) || + RISCV::VRN3M2RegClass.contains(Reg) || + RISCV::VRN4M1RegClass.contains(Reg) || + RISCV::VRN4M2RegClass.contains(Reg) || + RISCV::VRN5M1RegClass.contains(Reg) || + RISCV::VRN6M1RegClass.contains(Reg) || + RISCV::VRN7M1RegClass.contains(Reg) || + RISCV::VRN8M1RegClass.contains(Reg); +} + +std::vector ExegesisRISCVTarget::setRegTo(const MCSubtargetInfo &STI, + unsigned Reg, + const APInt &Value) const { + if (RISCV::GPRRegClass.contains(Reg)) + return loadIntReg(STI, Reg, Value); + if (RISCV::FPR16RegClass.contains(Reg)) + return loadFPRegBits(STI, Reg, Value, RISCV::FMV_H_X); + if (RISCV::FPR32RegClass.contains(Reg)) + return loadFPRegBits(STI, Reg, Value, RISCV::FMV_W_X); + if (RISCV::FPR64RegClass.contains(Reg)) { + if (STI.hasFeature(RISCV::Feature64Bit)) + return loadFPRegBits(STI, Reg, Value, RISCV::FMV_D_X); + else + return loadFP64RegBits32(STI, Reg, Value); + } + if (Reg == RISCV::FRM || Reg == RISCV::VL || Reg == RISCV::VLENB || + Reg == RISCV::VTYPE || RISCV::GPRPairRegClass.contains(Reg) || + RISCV::VRRegClass.contains(Reg) || isVectorRegList(Reg)) { + // Don't initialize: + // - FRM + // - VL, VLENB, VTYPE + // - vector registers (and vector register lists) + // - Zfinx registers + // Generate 'NOP' so that exegesis treats such registers as initialized + // (it tries to initialize them with '0' anyway). + return {nop()}; + } + errs() << "setRegTo is not implemented for Reg " << Reg + << ", results will be unreliable\n"; + return {}; +} + +const unsigned DefaultLoopCounterReg = RISCV::X31; // t6 +const unsigned ScratchMemoryReg = RISCV::X10; // a0 + +unsigned +ExegesisRISCVTarget::getDefaultLoopCounterRegister(const Triple &) const { + return DefaultLoopCounterReg; +} + +void ExegesisRISCVTarget::decrementLoopCounterAndJump( + MachineBasicBlock &MBB, MachineBasicBlock &TargetMBB, + const MCInstrInfo &MII, unsigned LoopRegister) const { + BuildMI(&MBB, DebugLoc(), MII.get(RISCV::ADDI)) + .addDef(LoopRegister) + .addUse(LoopRegister) + .addImm(-1); + BuildMI(&MBB, DebugLoc(), MII.get(RISCV::BNE)) + .addUse(LoopRegister) + .addUse(RISCV::X0) + .addMBB(&TargetMBB); +} + +unsigned ExegesisRISCVTarget::getScratchMemoryRegister(const Triple &TT) const { + return ScratchMemoryReg; // a0 +} + +void ExegesisRISCVTarget::fillMemoryOperands(InstructionTemplate &IT, + unsigned Reg, + unsigned Offset) const { + // TODO: for now we ignore Offset because have no way + // to detect it in instruction. + auto &I = IT.getInstr(); + + auto MemOpIt = + find_if(I.Operands, [](Operand const &Op) { return Op.isMemory(); }); + assert(MemOpIt != I.Operands.end() && + "Instruction must have memory operands"); + + auto &MemOp = *MemOpIt; + + assert(MemOp.isReg() && "Memory operand expected to be register"); + + IT.getValueFor(MemOp) = MCOperand::createReg(Reg); +} + +std::vector ExegesisRISCVTarget::storeRegValueToScratch( + const MCSubtargetInfo &STI, unsigned Reg, unsigned Offset) const { + std::vector Ret; + + if (RISCV::GPRRegClass.contains(Reg)) { + Ret.push_back(MCInstBuilder(RISCV::SW) + .addReg(Reg) + .addReg(ScratchMemoryReg) + .addImm(Offset)); + return Ret; + } + + errs() << "Failed to store value of " << Reg << " register to scratch memory"; + return {nop()}; +} + +const unsigned UnavailableRegisters[4] = {RISCV::X0, DefaultLoopCounterReg, + ScratchIntReg, ScratchMemoryReg}; + +ArrayRef ExegesisRISCVTarget::getUnavailableRegisters() const { + return ArrayRef(UnavailableRegisters); +} + +Error ExegesisRISCVTarget::randomizeTargetMCOperand( + const Instruction &Instr, const Variable &Var, MCOperand &AssignedValue, + const BitVector &ForbiddenRegs) const { + uint8_t OperandType = + Instr.getPrimaryOperand(Var).getExplicitOperandInfo().OperandType; + + switch (OperandType) { + case RISCVOp::OPERAND_FRMARG: + AssignedValue = MCOperand::createImm(RISCVFPRndMode::DYN); + break; + case RISCVOp::OPERAND_SIMM10_LSB0000_NONZERO: + AssignedValue = MCOperand::createImm(0b1 << 4); + break; + case RISCVOp::OPERAND_SIMM6_NONZERO: + case RISCVOp::OPERAND_UIMMLOG2XLEN_NONZERO: + AssignedValue = MCOperand::createImm(1); + break; + default: + if (OperandType >= RISCVOp::OPERAND_FIRST_RISCV_IMM && + OperandType <= RISCVOp::OPERAND_LAST_RISCV_IMM) + AssignedValue = MCOperand::createImm(0); + } + return Error::success(); +} + +// Process instructions that used ReservedRegisters. +// We must not create instructions that used rd=x0. But for some of them in C +// extension we use registers in which we are really not going to write to. +// Registers were reserved in RISCVRegisterInfo.cpp using markSuperRegs function +// and should not be redeclared. Thus we must set appropriate register +// explicitly for each instruction according to RVC spec. +void ExegesisRISCVTarget::processInstructionReservedRegs( + InstructionTemplate &IT) const { + MCOperand &AssignedValue = IT.getValueForOperandIdx(0); + + switch (IT.getOpcode()) { + case RISCV::C_ADDI16SP: + case RISCV::C_ADDI4SPN: + AssignedValue = MCOperand::createReg(RISCV::X2); + break; + case RISCV::C_ADDI_NOP: + case RISCV::C_LI_HINT: + case RISCV::C_LUI_HINT: + case RISCV::C_MV_HINT: + case RISCV::C_ADD_HINT: + case RISCV::C_SLLI_HINT: + AssignedValue = MCOperand::createReg(RISCV::X0); + break; + default: + break; + } +} + +std::vector +ExegesisRISCVTarget::generateInstructionVariants( + const Instruction &Instr, unsigned int MaxConfigsPerOpcode) const { + InstructionTemplate IT{&Instr}; + for (const Operand &Op : Instr.Operands) { + if (Op.isMemory()) { + IT.getValueFor(Op) = MCOperand::createReg(ScratchMemoryReg); + } + } + return {IT}; +} + +} // anonymous namespace + +static ExegesisTarget *getTheRISCVExegesisTarget() { + static ExegesisRISCVTarget Target; + return &Target; +} + +void InitializeRISCVExegesisTarget() { + ExegesisTarget::registerTarget(getTheRISCVExegesisTarget()); +} + +} // namespace exegesis +} // namespace llvm diff --git a/llvm/tools/llvm-exegesis/lib/SerialSnippetGenerator.cpp b/llvm/tools/llvm-exegesis/lib/SerialSnippetGenerator.cpp index 7100b51bbb729..1f1e3983d3014 100644 --- a/llvm/tools/llvm-exegesis/lib/SerialSnippetGenerator.cpp +++ b/llvm/tools/llvm-exegesis/lib/SerialSnippetGenerator.cpp @@ -52,14 +52,11 @@ computeAliasingInstructions(const LLVMState &State, const Instruction *Instr, continue; if (OtherOpcode == Instr->Description.getOpcode()) continue; + if (!State.getExegesisTarget().checkOpcodeSupported( + OtherOpcode, State.getSubtargetInfo())) + continue; const Instruction &OtherInstr = State.getIC().getInstr(OtherOpcode); const MCInstrDesc &OtherInstrDesc = OtherInstr.Description; - // Ignore instructions that we cannot run. - if (OtherInstrDesc.isPseudo() || OtherInstrDesc.usesCustomInsertionHook() || - OtherInstrDesc.isBranch() || OtherInstrDesc.isIndirectBranch() || - OtherInstrDesc.isCall() || OtherInstrDesc.isReturn()) { - continue; - } if (OtherInstr.hasMemoryOperands()) continue; if (!ET.allowAsBackToBack(OtherInstr)) @@ -81,12 +78,10 @@ static ExecutionMode getExecutionModes(const Instruction &Instr, EM |= ExecutionMode::ALWAYS_SERIAL_TIED_REGS_ALIAS; if (Instr.hasMemoryOperands()) EM |= ExecutionMode::SERIAL_VIA_MEMORY_INSTR; - else { - if (Instr.hasAliasingRegisters(ForbiddenRegisters)) - EM |= ExecutionMode::SERIAL_VIA_EXPLICIT_REGS; - if (Instr.hasOneUseOrOneDef()) - EM |= ExecutionMode::SERIAL_VIA_NON_MEMORY_INSTR; - } + if (Instr.hasAliasingNotMemoryRegisters(ForbiddenRegisters)) + EM |= ExecutionMode::SERIAL_VIA_EXPLICIT_REGS; + if (Instr.hasOneUseOrOneDef()) + EM |= ExecutionMode::SERIAL_VIA_NON_MEMORY_INSTR; return EM; } @@ -104,6 +99,7 @@ static void appendCodeTemplates(const LLVMState &State, case ExecutionMode::ALWAYS_SERIAL_TIED_REGS_ALIAS: { // Picking whatever value for the tied variable will make the instruction // serial. + State.getExegesisTarget().processInstructionReservedRegs(Variant); CodeTemplate CT; CT.Execution = ExecutionModeBit; CT.Info = std::string(ExecutionClassDescription); @@ -113,7 +109,51 @@ static void appendCodeTemplates(const LLVMState &State, } case ExecutionMode::SERIAL_VIA_MEMORY_INSTR: { // Select back-to-back memory instruction. - // TODO: Implement me. + + auto &I = Variant.getInstr(); + if (I.Description.mayLoad()) { + // If instruction is load, we can self-alias it in case when instruction + // overrides whole address register. For that we use provided scratch + // memory. + + // TODO: now it is not checked if load writes the whole register. + + auto DefOpIt = find_if(I.Operands, [](Operand const &op) { + return op.isDef() && op.isReg(); + }); + + if (DefOpIt == I.Operands.end()) + return; + + const Operand &DefOp = *DefOpIt; + auto &ET = State.getExegesisTarget(); + auto ScratchMemoryRegister = ET.getScratchMemoryRegister( + State.getTargetMachine().getTargetTriple()); + auto &RegClass = + State.getTargetMachine().getMCRegisterInfo()->getRegClass( + DefOp.getExplicitOperandInfo().RegClass); + + // Register classes of def operand and memory operand must be the same + // to perform aliasing. + if (!RegClass.contains(ScratchMemoryRegister)) + return; + + ET.fillMemoryOperands(Variant, ScratchMemoryRegister, 0); + Variant.getValueFor(DefOp) = MCOperand::createReg(ScratchMemoryRegister); + + CodeTemplate CT; + CT.Execution = ExecutionModeBit; + if (CT.ScratchSpacePointerInReg == 0) + CT.ScratchSpacePointerInReg = ScratchMemoryRegister; + + CT.Info = std::string(ExecutionClassDescription); + CT.Instructions.push_back(std::move(Variant)); + CT.PreinitScratchMemory.emplace_back(ScratchMemoryRegister, + /* Offset */ 0); + CodeTemplates.push_back(std::move(CT)); + } + + // TODO: implement more cases return; } case ExecutionMode::SERIAL_VIA_EXPLICIT_REGS: { diff --git a/llvm/tools/llvm-exegesis/lib/SnippetFile.cpp b/llvm/tools/llvm-exegesis/lib/SnippetFile.cpp index 431d99c72b808..412c06cc5acbd 100644 --- a/llvm/tools/llvm-exegesis/lib/SnippetFile.cpp +++ b/llvm/tools/llvm-exegesis/lib/SnippetFile.cpp @@ -36,10 +36,11 @@ namespace { class BenchmarkCodeStreamer : public MCStreamer, public AsmCommentConsumer { public: explicit BenchmarkCodeStreamer( - MCContext *Context, const DenseMap &RegNameToRegNo, + const ExegesisTarget &Target, MCContext *Context, + const DenseMap &RegNameToRegNo, BenchmarkCode *Result) - : MCStreamer(*Context), RegNameToRegNo(RegNameToRegNo), Result(Result) {} - + : MCStreamer(*Context), Target(Target), RegNameToRegNo(RegNameToRegNo), + Result(Result) {} // Implementation of the MCStreamer interface. We only care about // instructions. void emitInstruction(const MCInst &Instruction, @@ -207,6 +208,9 @@ class BenchmarkCodeStreamer : public MCStreamer, public AsmCommentConsumer { Align ByteAlignment, SMLoc Loc) override {} unsigned findRegisterByName(const StringRef RegName) const { + // TODO make use of RegNameToRegNo map + if (unsigned Reg = Target.findRegisterByName(RegName)) + return Reg; auto Iter = RegNameToRegNo.find(RegName); if (Iter != RegNameToRegNo.end()) return Iter->second; @@ -214,7 +218,7 @@ class BenchmarkCodeStreamer : public MCStreamer, public AsmCommentConsumer { << "' is not a valid register name for the target\n"; return 0; } - + const ExegesisTarget &Target; const DenseMap &RegNameToRegNo; BenchmarkCode *const Result; unsigned InvalidComments = 0; @@ -248,8 +252,8 @@ Expected> readSnippets(const LLVMState &State, TM.getTarget().createMCObjectFileInfo(Context, /*PIC=*/false)); Context.setObjectFileInfo(ObjectFileInfo.get()); Context.initInlineSourceManager(); - BenchmarkCodeStreamer Streamer(&Context, State.getRegNameToRegNoMapping(), - &Result); + BenchmarkCodeStreamer Streamer(State.getExegesisTarget(), &Context, + State.getRegNameToRegNoMapping(), &Result); std::string Error; raw_string_ostream ErrorStream(Error); diff --git a/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp b/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp index 7dcff60a8fd11..48357d443f713 100644 --- a/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp +++ b/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp @@ -73,6 +73,9 @@ Error SnippetGenerator::generateConfigurations( for (CodeTemplate &CT : Templates) { // TODO: Generate as many BenchmarkCode as needed. { + CT.ScratchSpacePointerInReg = + State.getExegesisTarget().getScratchMemoryRegister( + State.getTargetMachine().getTargetTriple()); BenchmarkCode BC; BC.Info = CT.Info; BC.Key.Instructions.reserve(CT.Instructions.size()); @@ -108,6 +111,12 @@ std::vector SnippetGenerator::computeRegisterInitialValues( // Loop invariant: DefinedRegs[i] is true iif it has been set at least once // before the current instruction. BitVector DefinedRegs = State.getRATC().emptyRegisters(); + // If target always expects a scratch memory register as live input, + // mark it as defined. + const ExegesisTarget &Target = State.getExegesisTarget(); + unsigned ScratchMemoryReg = Target.getScratchMemoryRegister( + State.getTargetMachine().getTargetTriple()); + DefinedRegs.set(ScratchMemoryReg); std::vector RIV; for (const InstructionTemplate &IT : Instructions) { // Returns the register that this Operand sets or uses, or 0 if this is not @@ -200,7 +209,8 @@ static void setRegisterOperandValue(const RegisterOperandAssignment &ROV, if (ROV.Op->isExplicit()) { auto &AssignedValue = IB.getValueFor(*ROV.Op); if (AssignedValue.isValid()) { - assert(AssignedValue.isReg() && AssignedValue.getReg() == ROV.Reg); + // TODO don't re-assign register operands which are already "locked" + // by Target in corresponding InstructionTemplate return; } AssignedValue = MCOperand::createReg(ROV.Reg); diff --git a/llvm/tools/llvm-exegesis/lib/Target.h b/llvm/tools/llvm-exegesis/lib/Target.h index 522c75d15703d..b48d027fb83fb 100644 --- a/llvm/tools/llvm-exegesis/lib/Target.h +++ b/llvm/tools/llvm-exegesis/lib/Target.h @@ -86,6 +86,16 @@ class ExegesisTarget { ArrayRef ValidationCounters, const pid_t ProcessID = 0) const; + virtual bool checkOpcodeSupported(int Opcode, + const MCSubtargetInfo &SI) const { + return true; + } + + // Find register by name, NoRegister if not found. + virtual MCRegister findRegisterByName(const StringRef RegName) const { + return MCRegister::NoRegister; + } + // Targets can use this to add target-specific passes in assembleToStream(); virtual void addTargetSpecificPasses(PassManagerBase &PM) const {} @@ -201,6 +211,14 @@ class ExegesisTarget { "fillMemoryOperands() requires getScratchMemoryRegister() > 0"); } + // Generates code to store register into scratch memory with offset. + virtual std::vector storeRegValueToScratch(const MCSubtargetInfo &STI, + unsigned Reg, + unsigned Offset) const { + llvm_unreachable( + "storeRegValueToScratch() requires getScratchMemoryRegister() > 0"); + } + // Returns a counter usable as a loop counter. virtual unsigned getDefaultLoopCounterRegister(const Triple &) const { return 0; @@ -238,6 +256,9 @@ class ExegesisTarget { "targets with target-specific operands should implement this"); } + // Process instructions that used reserved registers. + virtual void processInstructionReservedRegs(InstructionTemplate &IT) const {} + // Returns true if this instruction is supported as a back-to-back // instructions. // FIXME: Eventually we should discover this dynamically. diff --git a/llvm/tools/llvm-exegesis/llvm-exegesis.cpp b/llvm/tools/llvm-exegesis/llvm-exegesis.cpp index 06e1c7f3c1bbe..b8ef7e2a5c60c 100644 --- a/llvm/tools/llvm-exegesis/llvm-exegesis.cpp +++ b/llvm/tools/llvm-exegesis/llvm-exegesis.cpp @@ -50,15 +50,27 @@ namespace llvm { namespace exegesis { +struct OpcodeNameParser + : public cl::parser>> { + OpcodeNameParser(cl::Option &O) + : cl::parser>>(O) {} + bool parse(cl::Option &O, StringRef ArgName, const StringRef ArgValue, + std::vector> &Val); +}; + static cl::opt OpcodeIndex( "opcode-index", cl::desc("opcode to measure, by index, or -1 to measure all opcodes"), cl::cat(BenchmarkOptions), cl::init(0)); -static cl::opt +static cl::opt>, false, + OpcodeNameParser> OpcodeNames("opcode-name", - cl::desc("comma-separated list of opcodes to measure, by name"), - cl::cat(BenchmarkOptions), cl::init("")); + cl::desc("comma-separated list of opcodes to measure, " + "each item is either opcode name ('OP') " + "or opcode range ('OP1..OP2', ends are inclusive)"), + cl::cat(BenchmarkOptions), + cl::init(std::vector>())); static cl::opt SnippetsFile("snippets-file", cl::desc("code snippets to measure"), @@ -269,6 +281,10 @@ static cl::list ValidationCounters( "counter to validate benchmarking assumptions"), cl::CommaSeparated, cl::cat(BenchmarkOptions), ValidationEventOptions()); +static cl::opt MAttr( + "mattr", cl::desc("comma-separated list of target architecture features"), + cl::value_desc("+feature1,-feature2,..."), cl::cat(Options), cl::init("")); + static ExitOnError ExitOnErr("llvm-exegesis error: "); // Helper function that logs the error(s) and exits. @@ -291,6 +307,43 @@ T ExitOnFileError(const Twine &FileName, Expected &&E) { return std::move(*E); } +static const char *getIgnoredOpcodeReasonOrNull(const LLVMState &State, + unsigned Opcode) { + const MCInstrDesc &InstrDesc = State.getIC().getInstr(Opcode).Description; + if (InstrDesc.isPseudo() || InstrDesc.usesCustomInsertionHook()) + return "Unsupported opcode: isPseudo/usesCustomInserter"; + if (InstrDesc.isBranch() || InstrDesc.isIndirectBranch()) + return "Unsupported opcode: isBranch/isIndirectBranch"; + if (InstrDesc.isCall() || InstrDesc.isReturn()) + return "Unsupported opcode: isCall/isReturn"; + return nullptr; +} + +static bool isIgnoredOpcode(const LLVMState &State, unsigned Opcode) { + return getIgnoredOpcodeReasonOrNull(State, Opcode) != nullptr; +} + +bool OpcodeNameParser::parse( + cl::Option &O, StringRef ArgName, const StringRef OpcodeNames, + std::vector> &Val) { + SmallVector Pieces; + StringRef(OpcodeNames) + .split(Pieces, ",", /* MaxSplit */ -1, /* KeepEmpty */ false); + for (const StringRef &OpcodeName : Pieces) { + size_t DotDotPos = OpcodeName.find(".."); + if (DotDotPos == StringRef::npos) { + Val.push_back(std::make_pair(OpcodeName, OpcodeName)); + continue; + } + StringRef BeginOpcodeName = OpcodeName.substr(0, DotDotPos); + StringRef EndOpcodeName = OpcodeName.substr(DotDotPos + 2); + Val.push_back(std::make_pair(BeginOpcodeName, EndOpcodeName)); + } + if (Val.empty()) + return O.error("No matching opcode names"); + return false; +} + // Checks that only one of OpcodeNames, OpcodeIndex or SnippetsFile is provided, // and returns the opcode indices or {} if snippets should be read from // `SnippetsFile`. @@ -329,16 +382,35 @@ static std::vector getOpcodesOrDie(const LLVMState &State) { return I->getSecond(); return 0u; }; - SmallVector Pieces; - StringRef(OpcodeNames.getValue()) - .split(Pieces, ",", /* MaxSplit */ -1, /* KeepEmpty */ false); + std::vector Result; - Result.reserve(Pieces.size()); - for (const StringRef &OpcodeName : Pieces) { - if (unsigned Opcode = ResolveName(OpcodeName)) - Result.push_back(Opcode); - else - ExitWithError(Twine("unknown opcode ").concat(OpcodeName)); + for (const std::pair &OpcodeName : OpcodeNames) { + if (OpcodeName.first == OpcodeName.second) { + if (unsigned Opcode = ResolveName(OpcodeName.first)) { + Result.push_back(Opcode); + continue; + } else { + ExitWithError(Twine("unknown opcode ").concat(OpcodeName.first)); + } + } else { + StringRef BeginOpcodeName = OpcodeName.first; + unsigned BeginOpcode = + BeginOpcodeName.empty() ? 1 : ResolveName(BeginOpcodeName); + if (BeginOpcode == 0) { + ExitWithError(Twine("unknown opcode ").concat(BeginOpcodeName)); + } + StringRef EndOpcodeName = OpcodeName.second; + unsigned EndOpcode = EndOpcodeName.empty() + ? State.getInstrInfo().getNumOpcodes() - 1 + : ResolveName(EndOpcodeName); + if (EndOpcode == 0) { + ExitWithError(Twine("unknown opcode ").concat(EndOpcodeName)); + } + for (unsigned I = BeginOpcode; I <= EndOpcode; ++I) { + if (!isIgnoredOpcode(State, I)) + Result.push_back(I); + } + } } return Result; } @@ -347,17 +419,11 @@ static std::vector getOpcodesOrDie(const LLVMState &State) { static Expected> generateSnippets(const LLVMState &State, unsigned Opcode, const BitVector &ForbiddenRegs) { - const Instruction &Instr = State.getIC().getInstr(Opcode); - const MCInstrDesc &InstrDesc = Instr.Description; // Ignore instructions that we cannot run. - if (InstrDesc.isPseudo() || InstrDesc.usesCustomInsertionHook()) - return make_error( - "Unsupported opcode: isPseudo/usesCustomInserter"); - if (InstrDesc.isBranch() || InstrDesc.isIndirectBranch()) - return make_error("Unsupported opcode: isBranch/isIndirectBranch"); - if (InstrDesc.isCall() || InstrDesc.isReturn()) - return make_error("Unsupported opcode: isCall/isReturn"); + if (const char *Reason = getIgnoredOpcodeReasonOrNull(State, Opcode)) + return make_error(Reason); + const Instruction &Instr = State.getIC().getInstr(Opcode); const std::vector InstructionVariants = State.getExegesisTarget().generateInstructionVariants( Instr, MaxConfigsPerOpcode); @@ -474,8 +540,8 @@ void benchmarkMain() { InitializeAllAsmParsers(); InitializeAllExegesisTargets(); - const LLVMState State = - ExitOnErr(LLVMState::Create(TripleName, MCPU, "", UseDummyPerfCounters)); + const LLVMState State = ExitOnErr( + LLVMState::Create(TripleName, MCPU, MAttr, UseDummyPerfCounters)); // Preliminary check to ensure features needed for requested // benchmark mode are present on target CPU and/or OS. diff --git a/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel b/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel index c469da74fc561..752f215d6a781 100644 --- a/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel +++ b/utils/bazel/llvm-project-overlay/llvm/BUILD.bazel @@ -2178,6 +2178,7 @@ llvm_target_lib_list = [lib for lib in [ ("-gen-register-info", "lib/Target/RISCV/RISCVGenRegisterInfo.inc"), ("-gen-subtarget", "lib/Target/RISCV/RISCVGenSubtargetInfo.inc"), ("-gen-searchable-tables", "lib/Target/RISCV/RISCVGenSearchableTables.inc"), + ("-gen-exegesis", "lib/Target/RISCV/RISCVGenExegesis.inc"), ], "tbl_deps": [ ":riscv_isel_target_gen",