From edfe28541a6ed94357f873aa69778c7eba707cbb Mon Sep 17 00:00:00 2001 From: Robbin Ehn Date: Thu, 28 Nov 2024 12:05:23 +0000 Subject: [PATCH] 8344306: RISC-V: Add zicond Reviewed-by: fyang, luhenry, mli --- src/hotspot/cpu/riscv/assembler_riscv.hpp | 32 ++++ .../cpu/riscv/c1_LIRAssembler_riscv.cpp | 1 + .../cpu/riscv/c2_MacroAssembler_riscv.cpp | 46 +++++- .../cpu/riscv/c2_MacroAssembler_riscv.hpp | 1 - src/hotspot/cpu/riscv/globals_riscv.hpp | 1 + .../cpu/riscv/macroAssembler_riscv.cpp | 141 ++++++++++++++++++ .../cpu/riscv/macroAssembler_riscv.hpp | 11 ++ src/hotspot/cpu/riscv/vm_version_riscv.hpp | 4 + .../os_cpu/linux_riscv/riscv_hwprobe.cpp | 3 + .../gtest/riscv/test_assembler_riscv.cpp | 109 ++++++++++++++ 10 files changed, 344 insertions(+), 5 deletions(-) create mode 100644 test/hotspot/gtest/riscv/test_assembler_riscv.cpp diff --git a/src/hotspot/cpu/riscv/assembler_riscv.hpp b/src/hotspot/cpu/riscv/assembler_riscv.hpp index 7334ec675e31f..31713d7362a18 100644 --- a/src/hotspot/cpu/riscv/assembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/assembler_riscv.hpp @@ -3107,6 +3107,38 @@ enum Nf { #undef INSN +// -------------- Zicond Instruction Definitions -------------- +// Zicond conditional operations extension + private: + enum CZERO_OP : unsigned int { + CZERO_NEZ = 0b111, + CZERO_EQZ = 0b101 + }; + + template + void czero(Register Rd, Register Rs1, Register Rs2) { + assert_cond(UseZicond); + uint32_t insn = 0; + patch ((address)&insn, 6, 0, 0b0110011); // bits: 7, name: 0x33, attr: ['OP'] + patch_reg((address)&insn, 7, Rd); // bits: 5, name: 'rd' + patch ((address)&insn, 14, 12, OP_VALUE); // bits: 3, name: 0x7, attr: ['CZERO.NEZ'] / 0x5, attr: ['CZERO.EQZ']} + patch_reg((address)&insn, 15, Rs1); // bits: 5, name: 'rs1', attr: ['value'] + patch_reg((address)&insn, 20, Rs2); // bits: 5, name: 'rs2', attr: ['condition'] + patch ((address)&insn, 31, 25, 0b0000111); // bits: 7, name: 0x7, attr: ['CZERO'] + emit_int32(insn); + } + + public: + // Moves zero to a register rd, if the condition rs2 is equal to zero, otherwise moves rs1 to rd. + void czero_eqz(Register rd, Register rs1_value, Register rs2_condition) { + czero(rd, rs1_value, rs2_condition); + } + + // Moves zero to a register rd, if the condition rs2 is nonzero, otherwise moves rs1 to rd. + void czero_nez(Register rd, Register rs1_value, Register rs2_condition) { + czero(rd, rs1_value, rs2_condition); + } + // -------------- ZCB Instruction Definitions -------------- // Zcb additional C instructions private: diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index eb715227f7e4e..b7edd3d231f81 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -870,6 +870,7 @@ void LIR_Assembler::emit_op3(LIR_Op3* op) { } } +// Consider using cmov (Zicond) void LIR_Assembler::cmove(LIR_Condition condition, LIR_Opr opr1, LIR_Opr opr2, LIR_Opr result, BasicType type, LIR_Opr cmp_opr1, LIR_Opr cmp_opr2) { Label label; diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp index 49efb6190932d..2125f67cf9da4 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp @@ -2003,10 +2003,48 @@ void C2_MacroAssembler::enc_cmpEqNe_imm0_branch(int cmpFlag, Register op1, Label } void C2_MacroAssembler::enc_cmove(int cmpFlag, Register op1, Register op2, Register dst, Register src) { - Label L; - cmp_branch(cmpFlag ^ (1 << neg_cond_bits), op1, op2, L); - mv(dst, src); - bind(L); + bool is_unsigned = (cmpFlag & unsigned_branch_mask) == unsigned_branch_mask; + int op_select = cmpFlag & (~unsigned_branch_mask); + + switch (op_select) { + case BoolTest::eq: + cmov_eq(op1, op2, dst, src); + break; + case BoolTest::ne: + cmov_ne(op1, op2, dst, src); + break; + case BoolTest::le: + if (is_unsigned) { + cmov_leu(op1, op2, dst, src); + } else { + cmov_le(op1, op2, dst, src); + } + break; + case BoolTest::ge: + if (is_unsigned) { + cmov_geu(op1, op2, dst, src); + } else { + cmov_ge(op1, op2, dst, src); + } + break; + case BoolTest::lt: + if (is_unsigned) { + cmov_ltu(op1, op2, dst, src); + } else { + cmov_lt(op1, op2, dst, src); + } + break; + case BoolTest::gt: + if (is_unsigned) { + cmov_gtu(op1, op2, dst, src); + } else { + cmov_gt(op1, op2, dst, src); + } + break; + default: + assert(false, "unsupported compare condition"); + ShouldNotReachHere(); + } } // Set dst to NaN if any NaN input. diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp index 2d14f98780dd6..a8eb0df419c64 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp @@ -98,7 +98,6 @@ // refer to conditional_branches and float_conditional_branches static const int bool_test_bits = 3; - static const int neg_cond_bits = 2; static const int unsigned_branch_mask = 1 << bool_test_bits; static const int double_branch_mask = 1 << bool_test_bits; diff --git a/src/hotspot/cpu/riscv/globals_riscv.hpp b/src/hotspot/cpu/riscv/globals_riscv.hpp index ffbb7c589112a..6772fae50ca14 100644 --- a/src/hotspot/cpu/riscv/globals_riscv.hpp +++ b/src/hotspot/cpu/riscv/globals_riscv.hpp @@ -110,6 +110,7 @@ define_pd_global(intx, InlineSmallCode, 1000); product(bool, UseZicbom, false, EXPERIMENTAL, "Use Zicbom instructions") \ product(bool, UseZicbop, false, EXPERIMENTAL, "Use Zicbop instructions") \ product(bool, UseZicboz, false, EXPERIMENTAL, "Use Zicboz instructions") \ + product(bool, UseZicond, false, DIAGNOSTIC, "Use Zicond instructions") \ product(bool, UseZihintpause, false, EXPERIMENTAL, \ "Use Zihintpause instructions") \ product(bool, UseZtso, false, EXPERIMENTAL, "Assume Ztso memory model") \ diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index cf3c851a7ecdd..c39a086838d0b 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -1128,6 +1128,147 @@ void MacroAssembler::wrap_label(Register r1, Register r2, Label &L, #undef INSN +// cmov +void MacroAssembler::cmov_eq(Register cmp1, Register cmp2, Register dst, Register src) { + if (UseZicond) { + xorr(t0, cmp1, cmp2); + czero_eqz(dst, dst, t0); + czero_nez(t0 , src, t0); + orr(dst, dst, t0); + return; + } + Label no_set; + bne(cmp1, cmp2, no_set); + mv(dst, src); + bind(no_set); +} + +void MacroAssembler::cmov_ne(Register cmp1, Register cmp2, Register dst, Register src) { + if (UseZicond) { + xorr(t0, cmp1, cmp2); + czero_nez(dst, dst, t0); + czero_eqz(t0 , src, t0); + orr(dst, dst, t0); + return; + } + Label no_set; + beq(cmp1, cmp2, no_set); + mv(dst, src); + bind(no_set); +} + +void MacroAssembler::cmov_le(Register cmp1, Register cmp2, Register dst, Register src) { + if (UseZicond) { + slt(t0, cmp2, cmp1); + czero_eqz(dst, dst, t0); + czero_nez(t0, src, t0); + orr(dst, dst, t0); + return; + } + Label no_set; + bgt(cmp1, cmp2, no_set); + mv(dst, src); + bind(no_set); +} + +void MacroAssembler::cmov_leu(Register cmp1, Register cmp2, Register dst, Register src) { + if (UseZicond) { + sltu(t0, cmp2, cmp1); + czero_eqz(dst, dst, t0); + czero_nez(t0, src, t0); + orr(dst, dst, t0); + return; + } + Label no_set; + bgtu(cmp1, cmp2, no_set); + mv(dst, src); + bind(no_set); +} + +void MacroAssembler::cmov_ge(Register cmp1, Register cmp2, Register dst, Register src) { + if (UseZicond) { + slt(t0, cmp1, cmp2); + czero_eqz(dst, dst, t0); + czero_nez(t0, src, t0); + orr(dst, dst, t0); + return; + } + Label no_set; + blt(cmp1, cmp2, no_set); + mv(dst, src); + bind(no_set); +} + +void MacroAssembler::cmov_geu(Register cmp1, Register cmp2, Register dst, Register src) { + if (UseZicond) { + sltu(t0, cmp1, cmp2); + czero_eqz(dst, dst, t0); + czero_nez(t0, src, t0); + orr(dst, dst, t0); + return; + } + Label no_set; + bltu(cmp1, cmp2, no_set); + mv(dst, src); + bind(no_set); +} + +void MacroAssembler::cmov_lt(Register cmp1, Register cmp2, Register dst, Register src) { + if (UseZicond) { + slt(t0, cmp1, cmp2); + czero_nez(dst, dst, t0); + czero_eqz(t0, src, t0); + orr(dst, dst, t0); + return; + } + Label no_set; + bge(cmp1, cmp2, no_set); + mv(dst, src); + bind(no_set); +} + +void MacroAssembler::cmov_ltu(Register cmp1, Register cmp2, Register dst, Register src) { + if (UseZicond) { + sltu(t0, cmp1, cmp2); + czero_nez(dst, dst, t0); + czero_eqz(t0, src, t0); + orr(dst, dst, t0); + return; + } + Label no_set; + bgeu(cmp1, cmp2, no_set); + mv(dst, src); + bind(no_set); +} + +void MacroAssembler::cmov_gt(Register cmp1, Register cmp2, Register dst, Register src) { + if (UseZicond) { + slt(t0, cmp2, cmp1); + czero_nez(dst, dst, t0); + czero_eqz(t0, src, t0); + orr(dst, dst, t0); + return; + } + Label no_set; + ble(cmp1, cmp2, no_set); + mv(dst, src); + bind(no_set); +} + +void MacroAssembler::cmov_gtu(Register cmp1, Register cmp2, Register dst, Register src) { + if (UseZicond) { + sltu(t0, cmp2, cmp1); + czero_nez(dst, dst, t0); + czero_eqz(t0, src, t0); + orr(dst, dst, t0); + return; + } + Label no_set; + bleu(cmp1, cmp2, no_set); + mv(dst, src); + bind(no_set); +} + // Float compare branch instructions #define INSN(NAME, FLOATCMP, BRANCH) \ diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index 54f7127106b92..cbf69e93c5c97 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -626,6 +626,17 @@ class MacroAssembler: public Assembler { void bltz(Register Rs, const address dest); void bgtz(Register Rs, const address dest); + void cmov_eq(Register cmp1, Register cmp2, Register dst, Register src); + void cmov_ne(Register cmp1, Register cmp2, Register dst, Register src); + void cmov_le(Register cmp1, Register cmp2, Register dst, Register src); + void cmov_leu(Register cmp1, Register cmp2, Register dst, Register src); + void cmov_ge(Register cmp1, Register cmp2, Register dst, Register src); + void cmov_geu(Register cmp1, Register cmp2, Register dst, Register src); + void cmov_lt(Register cmp1, Register cmp2, Register dst, Register src); + void cmov_ltu(Register cmp1, Register cmp2, Register dst, Register src); + void cmov_gt(Register cmp1, Register cmp2, Register dst, Register src); + void cmov_gtu(Register cmp1, Register cmp2, Register dst, Register src); + public: // We try to follow risc-v asm menomics. // But as we don't layout a reachable GOT, diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp index 8fdde0094f40d..e08838c3a6fe4 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp @@ -116,6 +116,8 @@ class VM_Version : public Abstract_VM_Version { // // Zfh Half-Precision Floating-Point instructions // + // Zicond Conditional operations + // // Zicsr Control and Status Register (CSR) Instructions // Zifencei Instruction-Fetch Fence // Zic64b Cache blocks must be 64 bytes in size, naturally aligned in the address space. @@ -164,6 +166,7 @@ class VM_Version : public Abstract_VM_Version { decl(ext_Zvbb , "Zvbb" , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZvbb)) \ decl(ext_Zvfh , "Zvfh" , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZvfh)) \ decl(ext_Zvkn , "Zvkn" , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZvkn)) \ + decl(ext_Zicond , "Zicond" , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicond)) \ decl(mvendorid , "VendorId" , RV_NO_FLAG_BIT, false, NO_UPDATE_DEFAULT) \ decl(marchid , "ArchId" , RV_NO_FLAG_BIT, false, NO_UPDATE_DEFAULT) \ decl(mimpid , "ImpId" , RV_NO_FLAG_BIT, false, NO_UPDATE_DEFAULT) \ @@ -223,6 +226,7 @@ class VM_Version : public Abstract_VM_Version { RV_ENABLE_EXTENSION(UseZicbom) \ RV_ENABLE_EXTENSION(UseZicbop) \ RV_ENABLE_EXTENSION(UseZicboz) \ + RV_ENABLE_EXTENSION(UseZicond) \ RV_ENABLE_EXTENSION(UseZihintpause) \ static void useRVA23U64Profile(); diff --git a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp index 2020e2fdb2429..f785d93539348 100644 --- a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp +++ b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp @@ -181,6 +181,9 @@ void RiscvHwprobe::add_features_from_query_result() { if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVFH)) { VM_Version::ext_Zvfh.enable_feature(); } + if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZICOND)) { + VM_Version::ext_Zicond.enable_feature(); + } if (is_valid(RISCV_HWPROBE_KEY_CPUPERF_0)) { VM_Version::unaligned_access.enable_feature( query[RISCV_HWPROBE_KEY_CPUPERF_0].value & RISCV_HWPROBE_MISALIGNED_MASK); diff --git a/test/hotspot/gtest/riscv/test_assembler_riscv.cpp b/test/hotspot/gtest/riscv/test_assembler_riscv.cpp new file mode 100644 index 0000000000000..152b997b3c8cd --- /dev/null +++ b/test/hotspot/gtest/riscv/test_assembler_riscv.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, Rivos Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "precompiled.hpp" + +#if (defined(RISCV) || defined(RISCV64)) && !defined(ZERO) + +#include "asm/assembler.inline.hpp" +#include "asm/macroAssembler.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/orderAccess.hpp" +#include "unittest.hpp" + +typedef int64_t (*zicond_func)(int64_t cmp1, int64_t cmp2, int64_t dst, int64_t src); +typedef void (MacroAssembler::*cmov_func)(Register cmp1, Register cmp2, Register dst, Register src); + +class CmovTester { + public: + static void test(cmov_func func, int64_t a0, int64_t a1, int64_t a2, int64_t a3, int64_t result) { + BufferBlob* bb = BufferBlob::create("riscvTest", 128); + CodeBuffer code(bb); + MacroAssembler _masm(&code); + address entry = _masm.pc(); + { + ((&_masm)->*func)(c_rarg0, c_rarg1, c_rarg2, c_rarg3); + _masm.mv(c_rarg0, c_rarg2); + _masm.ret(); + } + _masm.flush(); + OrderAccess::cross_modify_fence(); + int64_t ret = ((zicond_func)entry)(a0, a1, a2, a3); + ASSERT_EQ(ret, result); + BufferBlob::free(bb); + } +}; + +void run_cmov_tests() { + // If 42(a0) eq 42(a1): assign dest(a2/66) the src(a3/77), expect result: 77 + CmovTester::test(&MacroAssembler::cmov_eq, 42, 42, 66, 77, 77); + // If 41(a0) eq 42(a1): assign dest(a2/66) the src(a3/77), expect result: 66 + CmovTester::test(&MacroAssembler::cmov_eq, 41, 42, 66, 77, 66); + + CmovTester::test(&MacroAssembler::cmov_ne, 41, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_ne, 42, 42, 66, 77, 66); + + CmovTester::test(&MacroAssembler::cmov_le, 41, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_le, 42, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_le, 42, -1, 66, 77, 66); + + CmovTester::test(&MacroAssembler::cmov_leu, 41, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_leu, 42, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_leu, -1, 42, 66, 77, 66); + + CmovTester::test(&MacroAssembler::cmov_ge, 43, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_ge, 42, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_ge, -1, 42, 66, 77, 66); + + CmovTester::test(&MacroAssembler::cmov_geu, 43, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_geu, 42, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_geu, 42, -1, 66, 77, 66); + + CmovTester::test(&MacroAssembler::cmov_lt, 41, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_lt, 42, 42, 66, 77, 66); + CmovTester::test(&MacroAssembler::cmov_lt, 42, -1, 66, 77, 66); + + CmovTester::test(&MacroAssembler::cmov_ltu, 41, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_ltu, 42, 42, 66, 77, 66); + CmovTester::test(&MacroAssembler::cmov_ltu, -1, 42, 66, 77, 66); + + CmovTester::test(&MacroAssembler::cmov_gt, 43, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_gt, 42, 42, 66, 77, 66); + CmovTester::test(&MacroAssembler::cmov_gt, -1, 42, 66, 77, 66); + + CmovTester::test(&MacroAssembler::cmov_gtu, 43, 42, 66, 77, 77); + CmovTester::test(&MacroAssembler::cmov_gtu, 42, 42, 66, 77, 66); + CmovTester::test(&MacroAssembler::cmov_gtu, 42, -1, 66, 77, 66); +} + +TEST_VM(RiscV, cmov) { + run_cmov_tests(); + if (UseZicond) { + UseZicond = false; + run_cmov_tests(); + UseZicond = true; + } +} + +#endif // RISCV