[vm] Add the RISC-V Zicond extension.

TEST=ci, local qemu
Change-Id: I0d367b762d989b3f9bae0937c7670b175d111453
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/394620
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
diff --git a/runtime/vm/compiler/assembler/assembler_riscv.cc b/runtime/vm/compiler/assembler/assembler_riscv.cc
index ca4c559..db4a4f8 100644
--- a/runtime/vm/compiler/assembler/assembler_riscv.cc
+++ b/runtime/vm/compiler/assembler/assembler_riscv.cc
@@ -1634,6 +1634,16 @@
   EmitRType(BSET, shamt, rs1, F3_BSET, rd, OPIMM);
 }
 
+void MicroAssembler::czeroeqz(Register rd, Register rs1, Register rs2) {
+  ASSERT(Supports(RV_Zicond));
+  EmitRType(CZERO, rs2, rs1, CZEROEQZ, rd, OP);
+}
+
+void MicroAssembler::czeronez(Register rd, Register rs1, Register rs2) {
+  ASSERT(Supports(RV_Zicond));
+  EmitRType(CZERO, rs2, rs1, CZERONEZ, rd, OP);
+}
+
 void MicroAssembler::lb(Register rd, Address addr, std::memory_order order) {
   ASSERT(addr.offset() == 0);
   ASSERT((order == std::memory_order_acquire) ||
diff --git a/runtime/vm/compiler/assembler/assembler_riscv.h b/runtime/vm/compiler/assembler/assembler_riscv.h
index b6d5d07..1adbfe3 100644
--- a/runtime/vm/compiler/assembler/assembler_riscv.h
+++ b/runtime/vm/compiler/assembler/assembler_riscv.h
@@ -606,6 +606,12 @@
   void bset(Register rd, Register rs1, Register rs2);
   void bseti(Register rd, Register rs1, intx_t shamt);
 
+  // ==== Zicond: Integer conditional operations ====
+  // rd := rs2 == 0 ? 0 : rs1
+  void czeroeqz(Register rd, Register rs1, Register rs2);
+  // rd := rs2 != 0 ? 0 : rs1
+  void czeronez(Register rd, Register rs1, Register rs2);
+
   // ==== Zalasr: Load-acquire, store-release ====
   void lb(Register rd, Address addr, std::memory_order order);
   void lh(Register rd, Address addr, std::memory_order order);
diff --git a/runtime/vm/compiler/assembler/assembler_riscv_test.cc b/runtime/vm/compiler/assembler/assembler_riscv_test.cc
index 4ecb945..d115882 100644
--- a/runtime/vm/compiler/assembler/assembler_riscv_test.cc
+++ b/runtime/vm/compiler/assembler/assembler_riscv_test.cc
@@ -6924,6 +6924,36 @@
   EXPECT_EQ(-1, Call(test->entry(), -1));
 }
 
+ASSEMBLER_TEST_GENERATE(ConditionalZeroIfEqualsZero, assembler) {
+  __ SetExtensions(RV_GC | RV_Zicond);
+  __ czeroeqz(A0, A0, A1);
+  __ ret();
+}
+ASSEMBLER_TEST_RUN(ConditionalZeroIfEqualsZero, test) {
+  EXPECT_DISASSEMBLY(
+      "0eb55533 czero.eqz a0, a0, a1\n"
+      "    8082 ret\n");
+
+  EXPECT_EQ(0, Call(test->entry(), 42, 0));
+  EXPECT_EQ(42, Call(test->entry(), 42, 1));
+  EXPECT_EQ(42, Call(test->entry(), 42, -1));
+}
+
+ASSEMBLER_TEST_GENERATE(ConditionalZeroIfNotEqualsZero, assembler) {
+  __ SetExtensions(RV_GC | RV_Zicond);
+  __ czeronez(A0, A0, A1);
+  __ ret();
+}
+ASSEMBLER_TEST_RUN(ConditionalZeroIfNotEqualsZero, test) {
+  EXPECT_DISASSEMBLY(
+      "0eb57533 czero.nez a0, a0, a1\n"
+      "    8082 ret\n");
+
+  EXPECT_EQ(42, Call(test->entry(), 42, 0));
+  EXPECT_EQ(0, Call(test->entry(), 42, 1));
+  EXPECT_EQ(0, Call(test->entry(), 42, -1));
+}
+
 ASSEMBLER_TEST_GENERATE(LoadByteAcquire, assembler) {
   __ SetExtensions(RV_GC | RV_Zalasr);
   __ lb(A0, Address(A1), std::memory_order_acquire);
diff --git a/runtime/vm/compiler/assembler/disassembler_riscv.cc b/runtime/vm/compiler/assembler/disassembler_riscv.cc
index b2634a9..9c1a412 100644
--- a/runtime/vm/compiler/assembler/disassembler_riscv.cc
+++ b/runtime/vm/compiler/assembler/disassembler_riscv.cc
@@ -69,6 +69,7 @@
   void DisassembleOP_MINMAXCLMUL(Instr instr);
   void DisassembleOP_ROTATE(Instr instr);
   void DisassembleOP_BCLRBEXT(Instr instr);
+  void DisassembleOP_CZERO(Instr instr);
   void DisassembleOP32(Instr instr);
   void DisassembleOP32_0(Instr instr);
   void DisassembleOP32_SUB(Instr instr);
@@ -713,6 +714,9 @@
       Print("zext.h 'rd, 'rs1", instr, RV_Zbb);
       break;
 #endif
+    case CZERO:
+      DisassembleOP_CZERO(instr);
+      break;
     default:
       UnknownInstruction(instr);
   }
@@ -886,6 +890,19 @@
   }
 }
 
+void RISCVDisassembler::DisassembleOP_CZERO(Instr instr) {
+  switch (instr.funct3()) {
+    case CZEROEQZ:
+      Print("czero.eqz 'rd, 'rs1, 'rs2", instr, RV_Zicond);
+      break;
+    case CZERONEZ:
+      Print("czero.nez 'rd, 'rs1, 'rs2", instr, RV_Zicond);
+      break;
+    default:
+      UnknownInstruction(instr);
+  }
+}
+
 void RISCVDisassembler::DisassembleOP32(Instr instr) {
   switch (instr.funct7()) {
     case 0:
diff --git a/runtime/vm/constants_riscv.h b/runtime/vm/constants_riscv.h
index 8200b11..fa1b73d 100644
--- a/runtime/vm/constants_riscv.h
+++ b/runtime/vm/constants_riscv.h
@@ -838,6 +838,9 @@
   BEXT = 0b101,
   F3_BINV = 0b001,
   F3_BSET = 0b001,
+
+  CZEROEQZ = 0b101,
+  CZERONEZ = 0b111,
 };
 
 enum Funct7 {
@@ -885,6 +888,8 @@
   BCLRBEXT = 0b0100100,
   BINV = 0b0110100,
   BSET = 0b0010100,
+
+  CZERO = 0b0000111,
 };
 
 enum Funct5 {
@@ -1617,10 +1622,11 @@
 static constexpr Extension RV_Zba(6);  // Address generation
 static constexpr Extension RV_Zbb(7);  // Basic bit-manipulation
 static constexpr Extension RV_Zbs(8);  // Single-bit instructions
+static constexpr Extension RV_Zbc(9);  // Carry-less multiplication
 static constexpr ExtensionSet RV_B = RV_Zba | RV_Zbb | RV_Zbs;
 static constexpr ExtensionSet RV_GCB = RV_GC | RV_B;
-static constexpr Extension RV_Zbc(9);      // Carry-less multiplication
-static constexpr Extension RV_Zalasr(10);  // Load-acquire, store-release
+static constexpr Extension RV_Zicond(10);  // Integer conditional operations
+static constexpr Extension RV_Zalasr(11);  // Load-acquire, store-release
 
 #if defined(DART_TARGET_OS_FUCHSIA) || defined(DART_TARGET_OS_ANDROID)
 static constexpr ExtensionSet RV_baseline = RV_GCB;
diff --git a/runtime/vm/simulator_riscv.cc b/runtime/vm/simulator_riscv.cc
index eb44c25..353127a 100644
--- a/runtime/vm/simulator_riscv.cc
+++ b/runtime/vm/simulator_riscv.cc
@@ -1332,6 +1332,9 @@
       pc_ += instr.length();
       break;
 #endif
+    case CZERO:
+      InterpretOP_CZERO(instr);
+      break;
     default:
       IllegalInstruction(instr);
   }
@@ -1664,6 +1667,23 @@
 }
 
 DART_FORCE_INLINE
+void Simulator::InterpretOP_CZERO(Instr instr) {
+  switch (instr.funct3()) {
+    case CZEROEQZ:
+      set_xreg(instr.rd(),
+               get_xreg(instr.rs2()) == 0 ? 0 : get_xreg(instr.rs1()));
+      break;
+    case CZERONEZ:
+      set_xreg(instr.rd(),
+               get_xreg(instr.rs2()) != 0 ? 0 : get_xreg(instr.rs1()));
+      break;
+    default:
+      IllegalInstruction(instr);
+  }
+  pc_ += instr.length();
+}
+
+DART_FORCE_INLINE
 void Simulator::InterpretOP32(Instr instr) {
   switch (instr.funct7()) {
 #if XLEN >= 64
diff --git a/runtime/vm/simulator_riscv.h b/runtime/vm/simulator_riscv.h
index 1f9e6f3..ee655839 100644
--- a/runtime/vm/simulator_riscv.h
+++ b/runtime/vm/simulator_riscv.h
@@ -211,6 +211,7 @@
   void InterpretOP_MINMAXCLMUL(Instr instr);
   void InterpretOP_ROTATE(Instr instr);
   void InterpretOP_BCLRBEXT(Instr instr);
+  void InterpretOP_CZERO(Instr instr);
   void InterpretOP32(Instr instr);
   void InterpretOP32_0(Instr instr);
   void InterpretOP32_SUB(Instr instr);