aboutsummaryrefslogtreecommitdiff
path: root/pk/emulation.c
diff options
context:
space:
mode:
Diffstat (limited to 'pk/emulation.c')
-rw-r--r--pk/emulation.c812
1 files changed, 812 insertions, 0 deletions
diff --git a/pk/emulation.c b/pk/emulation.c
new file mode 100644
index 0000000..25c4de0
--- /dev/null
+++ b/pk/emulation.c
@@ -0,0 +1,812 @@
+#include "mtrap.h"
+#include "softfloat.h"
+#include <limits.h>
+
+DECLARE_EMULATION_FUNC(truly_illegal_insn)
+{
+ return -1;
+}
+
+uintptr_t misaligned_load_trap(uintptr_t mcause, uintptr_t* regs)
+{
+ uintptr_t mstatus = read_csr(mstatus);
+ uintptr_t mepc = read_csr(mepc);
+ insn_fetch_t fetch = get_insn(mcause, mstatus, mepc);
+ if (fetch.error)
+ return -1;
+
+ uintptr_t val, res, tmp;
+ uintptr_t addr = GET_RS1(fetch.insn, regs) + IMM_I(fetch.insn);
+
+ #define DO_LOAD(type_lo, type_hi, insn_lo, insn_hi) ({ \
+ type_lo val_lo; \
+ type_hi val_hi; \
+ uintptr_t addr_lo = addr & -sizeof(type_hi); \
+ uintptr_t addr_hi = addr_lo + sizeof(type_hi); \
+ uintptr_t masked_addr = sizeof(type_hi) < 4 ? addr % sizeof(type_hi) : addr; \
+ res = unpriv_mem_access(mstatus, mepc, \
+ insn_lo " %[val_lo], (%[addr_lo]);" \
+ insn_hi " %[val_hi], (%[addr_hi])", \
+ val_lo, val_hi, addr_lo, addr_hi); \
+ val_lo >>= masked_addr * 8; \
+ val_hi <<= (sizeof(type_hi) - masked_addr) * 8; \
+ val = (type_hi)(val_lo | val_hi); \
+ })
+
+ if ((fetch.insn & MASK_LW) == MATCH_LW)
+ DO_LOAD(uint32_t, int32_t, "lw", "lw");
+#ifdef __riscv64
+ else if ((fetch.insn & MASK_LD) == MATCH_LD)
+ DO_LOAD(uint64_t, uint64_t, "ld", "ld");
+ else if ((fetch.insn & MASK_LWU) == MATCH_LWU)
+ DO_LOAD(uint32_t, uint32_t, "lwu", "lwu");
+#endif
+ else if ((fetch.insn & MASK_FLD) == MATCH_FLD) {
+#ifdef __riscv64
+ DO_LOAD(uint64_t, uint64_t, "ld", "ld");
+ if (res == 0) {
+ SET_F64_RD(fetch.insn, regs, val);
+ goto success;
+ }
+#else
+ DO_LOAD(uint32_t, int32_t, "lw", "lw");
+ if (res == 0) {
+ uint64_t double_val = val;
+ addr += 4;
+ DO_LOAD(uint32_t, int32_t, "lw", "lw");
+ double_val |= (uint64_t)val << 32;
+ if (res == 0) {
+ SET_F64_RD(fetch.insn, regs, val);
+ goto success;
+ }
+ }
+#endif
+ } else if ((fetch.insn & MASK_FLW) == MATCH_FLW) {
+ DO_LOAD(uint32_t, uint32_t, "lw", "lw");
+ if (res == 0) {
+ SET_F32_RD(fetch.insn, regs, val);
+ goto success;
+ }
+ } else if ((fetch.insn & MASK_LH) == MATCH_LH) {
+ // equivalent to DO_LOAD(uint32_t, int16_t, "lhu", "lh")
+ res = unpriv_mem_access(mstatus, mepc,
+ "lbu %[val], 0(%[addr]);"
+ "lb %[tmp], 1(%[addr])",
+ val, tmp, addr, mstatus/*X*/);
+ val |= tmp << 8;
+ } else if ((fetch.insn & MASK_LHU) == MATCH_LHU) {
+ // equivalent to DO_LOAD(uint32_t, uint16_t, "lhu", "lhu")
+ res = unpriv_mem_access(mstatus, mepc,
+ "lbu %[val], 0(%[addr]);"
+ "lbu %[tmp], 1(%[addr])",
+ val, tmp, addr, mstatus/*X*/);
+ val |= tmp << 8;
+ } else {
+ return -1;
+ }
+
+ if (res) {
+ restore_mstatus(mstatus, mepc);
+ return -1;
+ }
+
+ SET_RD(fetch.insn, regs, val);
+
+success:
+ write_csr(mepc, mepc + 4);
+ return 0;
+}
+
+uintptr_t misaligned_store_trap(uintptr_t mcause, uintptr_t* regs)
+{
+ uintptr_t mstatus = read_csr(mstatus);
+ uintptr_t mepc = read_csr(mepc);
+ insn_fetch_t fetch = get_insn(mcause, mstatus, mepc);
+ if (fetch.error)
+ return -1;
+
+ uintptr_t addr = GET_RS1(fetch.insn, regs) + IMM_S(fetch.insn);
+ uintptr_t val = GET_RS2(fetch.insn, regs), error;
+
+ if ((fetch.insn & MASK_SW) == MATCH_SW) {
+SW:
+ error = unpriv_mem_access(mstatus, mepc,
+ "sb %[val], 0(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 1(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 2(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 3(%[addr]);",
+ unused1, unused2, val, addr);
+#ifdef __riscv64
+ } else if ((fetch.insn & MASK_SD) == MATCH_SD) {
+SD:
+ error = unpriv_mem_access(mstatus, mepc,
+ "sb %[val], 0(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 1(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 2(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 3(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 4(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 5(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 6(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 7(%[addr]);",
+ unused1, unused2, val, addr);
+#endif
+ } else if ((fetch.insn & MASK_SH) == MATCH_SH) {
+ error = unpriv_mem_access(mstatus, mepc,
+ "sb %[val], 0(%[addr]);"
+ "srl %[val], %[val], 8; sb %[val], 1(%[addr]);",
+ unused1, unused2, val, addr);
+ } else if ((fetch.insn & MASK_FSD) == MATCH_FSD) {
+#ifdef __riscv64
+ val = GET_F64_RS2(fetch.insn, regs);
+ goto SD;
+#else
+ uint64_t double_val = GET_F64_RS2(fetch.insn, regs);
+ uint32_t val_lo = double_val, val_hi = double_val >> 32;
+ error = unpriv_mem_access(mstatus, mepc,
+ "sb %[val_lo], 0(%[addr]);"
+ "srl %[val_lo], %[val_lo], 8; sb %[val_lo], 1(%[addr]);"
+ "srl %[val_lo], %[val_lo], 8; sb %[val_lo], 2(%[addr]);"
+ "srl %[val_lo], %[val_lo], 8; sb %[val_lo], 3(%[addr]);"
+ "sb %[val_hi], 4(%[addr]);"
+ "srl %[val_hi], %[val_hi], 8; sb %[val_hi], 5(%[addr]);"
+ "srl %[val_hi], %[val_hi], 8; sb %[val_hi], 6(%[addr]);"
+ "srl %[val_hi], %[val_hi], 8; sb %[val_hi], 7(%[addr]);",
+ unused1, unused2, val_lo, val_hi, addr);
+#endif
+ } else if ((fetch.insn & MASK_FSW) == MATCH_FSW) {
+ val = GET_F32_RS2(fetch.insn, regs);
+ goto SW;
+ } else
+ return -1;
+
+ if (error) {
+ restore_mstatus(mstatus, mepc);
+ return -1;
+ }
+
+ write_csr(mepc, mepc + 4);
+ return 0;
+}
+
+DECLARE_EMULATION_FUNC(emulate_float_load)
+{
+ uintptr_t val_lo, val_hi, error;
+ uint64_t val;
+ uintptr_t addr = GET_RS1(insn, regs) + IMM_I(insn);
+
+ switch (insn & MASK_FUNCT3)
+ {
+ case MATCH_FLW & MASK_FUNCT3:
+ if (addr % 4 != 0)
+ return misaligned_load_trap(mcause, regs);
+
+ error = unpriv_mem_access(mstatus, mepc,
+ "lw %[val_lo], (%[addr])",
+ val_lo, val_hi/*X*/, addr, mstatus/*X*/);
+
+ if (error == 0) {
+ SET_F32_RD(insn, regs, val_lo);
+ goto success;
+ }
+ break;
+
+ case MATCH_FLD & MASK_FUNCT3:
+ if (addr % sizeof(uintptr_t) != 0)
+ return misaligned_load_trap(mcause, regs);
+#ifdef __riscv64
+ error = unpriv_mem_access(mstatus, mepc,
+ "ld %[val], (%[addr])",
+ val, val_hi/*X*/, addr, mstatus/*X*/);
+#else
+ error = unpriv_mem_access(mstatus, mepc,
+ "lw %[val_lo], (%[addr]);"
+ "lw %[val_hi], 4(%[addr])",
+ val_lo, val_hi, addr, mstatus/*X*/);
+ val = val_lo | ((uint64_t)val_hi << 32);
+#endif
+
+ if (error == 0) {
+ SET_F64_RD(insn, regs, val);
+ goto success;
+ }
+ break;
+ }
+
+ restore_mstatus(mstatus, mepc);
+ return -1;
+
+success:
+ write_csr(mepc, mepc + 4);
+ return 0;
+}
+
+DECLARE_EMULATION_FUNC(emulate_float_store)
+{
+ uintptr_t val_lo, val_hi, error;
+ uint64_t val;
+ uintptr_t addr = GET_RS1(insn, regs) + IMM_I(insn);
+
+ switch (insn & MASK_FUNCT3)
+ {
+ case MATCH_FSW & MASK_FUNCT3:
+ if (addr % 4 != 0)
+ return misaligned_store_trap(mcause, regs);
+
+ val_lo = GET_F32_RS2(insn, regs);
+ error = unpriv_mem_access(mstatus, mepc,
+ "sw %[val_lo], (%[addr])",
+ unused1, unused2, val_lo, addr);
+ break;
+
+ case MATCH_FSD & MASK_FUNCT3:
+ if (addr % sizeof(uintptr_t) != 0)
+ return misaligned_store_trap(mcause, regs);
+
+ val = GET_F64_RS2(insn, regs);
+#ifdef __riscv64
+ error = unpriv_mem_access(mstatus, mepc,
+ "sd %[val], (%[addr])",
+ unused1, unused2, val, addr);
+#else
+ val_lo = val;
+ val_hi = val >> 32;
+ error = unpriv_mem_access(mstatus, mepc,
+ "sw %[val_lo], (%[addr]);"
+ "sw %[val_hi], 4(%[addr])",
+ unused1, unused2, val_lo, val_hi, addr);
+#endif
+ break;
+
+ default:
+ error = 1;
+ }
+
+ if (error) {
+ restore_mstatus(mstatus, mepc);
+ return -1;
+ }
+
+ write_csr(mepc, mepc + 4);
+ return 0;
+}
+
+#ifdef __riscv64
+typedef int double_int __attribute__((mode(TI)));
+typedef unsigned int double_uint __attribute__((mode(TI)));
+#else
+typedef int64_t double_int;
+typedef uint64_t double_int;
+#endif
+
+DECLARE_EMULATION_FUNC(emulate_mul_div)
+{
+ uintptr_t rs1 = GET_RS1(insn, regs), rs2 = GET_RS2(insn, regs), val;
+
+ // If compiled with -mno-multiply, GCC will expand these out
+ if ((insn & MASK_MUL) == MATCH_MUL)
+ val = rs1 * rs2;
+ else if ((insn & MASK_DIV) == MATCH_DIV)
+ val = (intptr_t)rs1 / (intptr_t)rs2;
+ else if ((insn & MASK_DIVU) == MATCH_DIVU)
+ val = rs1 / rs2;
+ else if ((insn & MASK_REM) == MATCH_REM)
+ val = (intptr_t)rs1 % (intptr_t)rs2;
+ else if ((insn & MASK_REMU) == MATCH_REMU)
+ val = rs1 % rs2;
+ else if ((insn & MASK_MULH) == MATCH_MULH)
+ val = ((double_int)(intptr_t)rs1 * (double_int)(intptr_t)rs2) >> (8 * sizeof(rs1));
+ else if ((insn & MASK_MULHU) == MATCH_MULHU)
+ val = ((double_int)rs1 * (double_int)rs2) >> (8 * sizeof(rs1));
+ else if ((insn & MASK_MULHSU) == MATCH_MULHSU)
+ val = ((double_int)(intptr_t)rs1 * (double_int)rs2) >> (8 * sizeof(rs1));
+ else
+ return -1;
+
+ SET_RD(insn, regs, val);
+ return 0;
+}
+
+DECLARE_EMULATION_FUNC(emulate_mul_div32)
+{
+#ifndef __riscv64
+ return truly_illegal_insn(mcause, regs, insn, mstatus, mepc);
+#endif
+
+ uint32_t rs1 = GET_RS1(insn, regs), rs2 = GET_RS2(insn, regs);
+ int32_t val;
+
+ // If compiled with -mno-multiply, GCC will expand these out
+ if ((insn & MASK_MUL) == MATCH_MULW)
+ val = rs1 * rs2;
+ else if ((insn & MASK_DIV) == MATCH_DIV)
+ val = (int32_t)rs1 / (int32_t)rs2;
+ else if ((insn & MASK_DIVU) == MATCH_DIVU)
+ val = rs1 / rs2;
+ else if ((insn & MASK_REM) == MATCH_REM)
+ val = (int32_t)rs1 % (int32_t)rs2;
+ else if ((insn & MASK_REMU) == MATCH_REMU)
+ val = rs1 % rs2;
+ else
+ return -1;
+
+ SET_RD(insn, regs, val);
+ return 0;
+}
+
+static inline int emulate_read_csr(int num, uintptr_t* result, uintptr_t mstatus)
+{
+ switch (num)
+ {
+ case CSR_FRM:
+ if ((mstatus & MSTATUS_FS) == 0) break;
+ *result = GET_FRM();
+ return 0;
+ case CSR_FFLAGS:
+ if ((mstatus & MSTATUS_FS) == 0) break;
+ *result = GET_FFLAGS();
+ return 0;
+ case CSR_FCSR:
+ if ((mstatus & MSTATUS_FS) == 0) break;
+ *result = GET_FCSR();
+ return 0;
+ }
+ return -1;
+}
+
+static inline int emulate_write_csr(int num, uintptr_t value, uintptr_t mstatus)
+{
+ switch (num)
+ {
+ case CSR_FRM: SET_FRM(value); return 0;
+ case CSR_FFLAGS: SET_FFLAGS(value); return 0;
+ case CSR_FCSR: SET_FCSR(value); return 0;
+ }
+ return -1;
+}
+
+DECLARE_EMULATION_FUNC(emulate_system)
+{
+ int rs1_num = (insn >> 15) & 0x1f;
+ uintptr_t rs1_val = GET_RS1(insn, regs);
+ int csr_num = (uint32_t)insn >> 20;
+ uintptr_t csr_val, new_csr_val;
+
+ if (emulate_read_csr(csr_num, &csr_val, mstatus) != 0)
+ return -1;
+
+ int do_write = rs1_num;
+ switch (GET_RM(insn))
+ {
+ case 0: return -1;
+ case 1: new_csr_val = rs1_val; do_write = 1; break;
+ case 2: new_csr_val = csr_val | rs1_val; break;
+ case 3: new_csr_val = csr_val & ~rs1_val; break;
+ case 4: return -1;
+ case 5: new_csr_val = rs1_num; do_write = 1; break;
+ case 6: new_csr_val = csr_val | rs1_num; break;
+ case 7: new_csr_val = csr_val & ~rs1_num; break;
+ }
+
+ if (do_write && emulate_write_csr(csr_num, new_csr_val, mstatus) != 0)
+ return -1;
+
+ SET_RD(insn, regs, csr_val);
+ return 0;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fp)
+{
+ asm (".pushsection .rodata\n"
+ "fp_emulation_table:\n"
+ " .word emulate_fadd\n"
+ " .word emulate_fsub\n"
+ " .word emulate_fmul\n"
+ " .word emulate_fdiv\n"
+ " .word emulate_fsgnj\n"
+ " .word emulate_fmin\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word emulate_fcvt_ff\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word emulate_fsqrt\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word emulate_fcmp\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word truly_illegal_insn\n"
+ " .word emulate_fcvt_if\n"
+ " .word truly_illegal_insn\n"
+ " .word emulate_fcvt_fi\n"
+ " .word truly_illegal_insn\n"
+ " .word emulate_fmv_if\n"
+ " .word truly_illegal_insn\n"
+ " .word emulate_fmv_fi\n"
+ " .word truly_illegal_insn\n"
+ " .popsection");
+
+ // if FPU is disabled, punt back to the OS
+ if (unlikely((mstatus & MSTATUS_FS) == 0))
+ return -1;
+
+ extern int32_t fp_emulation_table[];
+ int32_t* pf = (void*)fp_emulation_table + ((insn >> 25) & 0x7c);
+ emulation_func f = (emulation_func)(uintptr_t)*pf;
+
+ SETUP_STATIC_ROUNDING(insn);
+ return f(mcause, regs, insn, mstatus, mepc);
+}
+
+uintptr_t emulate_any_fadd(uintptr_t mcause, uintptr_t* regs, insn_t insn, uintptr_t neg_b)
+{
+ if (GET_PRECISION(insn) == PRECISION_S) {
+ uint32_t rs1 = GET_F32_RS1(insn, regs);
+ uint32_t rs2 = GET_F32_RS2(insn, regs) ^ neg_b;
+ SET_F32_RD(insn, regs, f32_add(rs1, rs2));
+ return 0;
+ } else if (GET_PRECISION(insn) == PRECISION_D) {
+ uint64_t rs1 = GET_F64_RS1(insn, regs);
+ uint64_t rs2 = GET_F64_RS2(insn, regs) ^ ((uint64_t)neg_b << 32);
+ SET_F64_RD(insn, regs, f64_add(rs1, rs2));
+ return 0;
+ }
+ return -1;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fadd)
+{
+ return emulate_any_fadd(mcause, regs, insn, 0);
+}
+
+DECLARE_EMULATION_FUNC(emulate_fsub)
+{
+ return emulate_any_fadd(mcause, regs, insn, INT32_MIN);
+}
+
+DECLARE_EMULATION_FUNC(emulate_fmul)
+{
+ if (GET_PRECISION(insn) == PRECISION_S) {
+ uint32_t rs1 = GET_F32_RS1(insn, regs);
+ uint32_t rs2 = GET_F32_RS2(insn, regs);
+ SET_F32_RD(insn, regs, f32_mul(rs1, rs2));
+ return 0;
+ } else if (GET_PRECISION(insn) == PRECISION_D) {
+ uint64_t rs1 = GET_F64_RS1(insn, regs);
+ uint64_t rs2 = GET_F64_RS2(insn, regs);
+ SET_F64_RD(insn, regs, f64_mul(rs1, rs2));
+ return 0;
+ }
+ return -1;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fdiv)
+{
+ if (GET_PRECISION(insn) == PRECISION_S) {
+ uint32_t rs1 = GET_F32_RS1(insn, regs);
+ uint32_t rs2 = GET_F32_RS2(insn, regs);
+ SET_F32_RD(insn, regs, f32_div(rs1, rs2));
+ return 0;
+ } else if (GET_PRECISION(insn) == PRECISION_D) {
+ uint64_t rs1 = GET_F64_RS1(insn, regs);
+ uint64_t rs2 = GET_F64_RS2(insn, regs);
+ SET_F64_RD(insn, regs, f64_div(rs1, rs2));
+ return 0;
+ }
+ return -1;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fsqrt)
+{
+ if ((insn >> 20) & 0x1f)
+ return -1;
+
+ if (GET_PRECISION(insn) == PRECISION_S) {
+ SET_F32_RD(insn, regs, f32_sqrt(GET_F32_RS1(insn, regs)));
+ return 0;
+ } else if (GET_PRECISION(insn) == PRECISION_D) {
+ SET_F64_RD(insn, regs, f64_sqrt(GET_F64_RS1(insn, regs)));
+ return 0;
+ }
+ return -1;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fsgnj)
+{
+ int rm = GET_RM(insn);
+ if (rm >= 3)
+ return -1;
+
+ #define DO_FSGNJ(rs1, rs2, rm) ({ \
+ typeof(rs1) rs1_sign = (rs1) >> (8*sizeof(rs1)-1); \
+ typeof(rs1) rs2_sign = (rs2) >> (8*sizeof(rs1)-1); \
+ rs1_sign &= (rm) >> 1; \
+ rs1_sign ^= (rm) ^ rs2_sign; \
+ ((rs1) << 1 >> 1) | (rs1_sign << (8*sizeof(rs1)-1)); })
+
+ if (GET_PRECISION(insn) == PRECISION_S) {
+ uint32_t rs1 = GET_F32_RS1(insn, regs);
+ uint32_t rs2 = GET_F32_RS2(insn, regs);
+ SET_F32_RD(insn, regs, DO_FSGNJ(rs1, rs2, rm));
+ return 0;
+ } else if (GET_PRECISION(insn) == PRECISION_D) {
+ uint64_t rs1 = GET_F64_RS1(insn, regs);
+ uint64_t rs2 = GET_F64_RS2(insn, regs);
+ SET_F64_RD(insn, regs, DO_FSGNJ(rs1, rs2, rm));
+ return 0;
+ }
+ return -1;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fmin)
+{
+ int rm = GET_RM(insn);
+ if (rm >= 2)
+ return -1;
+
+ if (GET_PRECISION(insn) == PRECISION_S) {
+ uint32_t rs1 = GET_F32_RS1(insn, regs);
+ uint32_t rs2 = GET_F32_RS2(insn, regs);
+ uint32_t arg1 = rm ? rs2 : rs1;
+ uint32_t arg2 = rm ? rs1 : rs2;
+ int use_rs1 = f32_lt_quiet(arg1, arg2) || isNaNF32UI(rs2);
+ SET_F32_RD(insn, regs, use_rs1 ? rs1 : rs2);
+ return 0;
+ } else if (GET_PRECISION(insn) == PRECISION_D) {
+ uint64_t rs1 = GET_F64_RS1(insn, regs);
+ uint64_t rs2 = GET_F64_RS2(insn, regs);
+ uint64_t arg1 = rm ? rs2 : rs1;
+ uint64_t arg2 = rm ? rs1 : rs2;
+ int use_rs1 = f64_lt_quiet(arg1, arg2) || isNaNF64UI(rs2);
+ SET_F64_RD(insn, regs, use_rs1 ? rs1 : rs2);
+ return 0;
+ }
+ return -1;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fcvt_ff)
+{
+ int rs2_num = (insn >> 20) & 0x1f;
+ if (GET_PRECISION(insn) == PRECISION_S) {
+ if (rs2_num != 1)
+ return -1;
+ SET_F32_RD(insn, regs, f64_to_f32(GET_F64_RS1(insn, regs)));
+ return 0;
+ } else if (GET_PRECISION(insn) == PRECISION_D) {
+ if (rs2_num != 0)
+ return -1;
+ SET_F64_RD(insn, regs, f32_to_f64(GET_F32_RS1(insn, regs)));
+ return 0;
+ }
+ return -1;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fcvt_fi)
+{
+ if (GET_PRECISION(insn) != PRECISION_S && GET_PRECISION(insn) != PRECISION_D)
+ return -1;
+
+ int negative = 0;
+ uint64_t uint_val = GET_RS1(insn, regs);
+
+ switch ((insn >> 20) & 0x1f)
+ {
+ case 0: // int32
+ negative = (int32_t)uint_val < 0;
+ uint_val = negative ? -(int32_t)uint_val : (int32_t)uint_val;
+ break;
+ case 1: // uint32
+ uint_val = (uint32_t)uint_val;
+ break;
+#ifdef __riscv64
+ case 2: // int64
+ negative = (int64_t)uint_val < 0;
+ uint_val = negative ? -uint_val : uint_val;
+ case 3: // uint64
+ break;
+#endif
+ default:
+ return -1;
+ }
+
+ uint64_t float64 = ui64_to_f64(uint_val);
+ if (negative)
+ float64 ^= INT64_MIN;
+
+ if (GET_PRECISION(insn) == PRECISION_S)
+ SET_F32_RD(insn, regs, f64_to_f32(float64));
+ else
+ SET_F64_RD(insn, regs, float64);
+
+ return 0;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fcvt_if)
+{
+ int rs2_num = (insn >> 20) & 0x1f;
+#ifdef __riscv64
+ if (rs2_num >= 4)
+ return -1;
+#else
+ if (rs2_num >= 2)
+ return -1;
+#endif
+
+ int64_t float64;
+ if (GET_PRECISION(insn) == PRECISION_S)
+ float64 = f32_to_f64(GET_F32_RS1(insn, regs));
+ else if (GET_PRECISION(insn) == PRECISION_D)
+ float64 = GET_F64_RS1(insn, regs);
+ else
+ return -1;
+
+ int negative = 0;
+ if (float64 < 0) {
+ negative = 1;
+ float64 ^= INT64_MIN;
+ }
+ uint64_t uint_val = f64_to_ui64(float64, softfloat_roundingMode, true);
+ uint64_t result, limit, limit_result;
+
+ switch (rs2_num)
+ {
+ case 0: // int32
+ if (negative) {
+ result = (int32_t)-uint_val;
+ limit_result = limit = (uint32_t)INT32_MIN;
+ } else {
+ result = (int32_t)uint_val;
+ limit_result = limit = INT32_MAX;
+ }
+ break;
+
+ case 1: // uint32
+ limit = limit_result = UINT32_MAX;
+ if (negative)
+ result = limit = 0;
+ else
+ result = (uint32_t)uint_val;
+ break;
+
+ case 2: // int32
+ if (negative) {
+ result = (int64_t)-uint_val;
+ limit_result = limit = (uint64_t)INT64_MIN;
+ } else {
+ result = (int64_t)uint_val;
+ limit_result = limit = INT64_MAX;
+ }
+ break;
+
+ case 3: // uint64
+ limit = limit_result = UINT64_MAX;
+ if (negative)
+ result = limit = 0;
+ else
+ result = (uint64_t)uint_val;
+ break;
+ }
+
+ if (uint_val > limit) {
+ result = limit_result;
+ softfloat_raiseFlags(softfloat_flag_invalid);
+ }
+
+ SET_FS_DIRTY();
+ SET_RD(insn, regs, result);
+
+ return 0;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fcmp)
+{
+ int rm = GET_RM(insn);
+ if (rm >= 3)
+ return -1;
+
+ uintptr_t result;
+ if (GET_PRECISION(insn) == PRECISION_S) {
+ uint32_t rs1 = GET_F32_RS1(insn, regs);
+ uint32_t rs2 = GET_F32_RS2(insn, regs);
+ if (rm != 1)
+ result = f32_eq(rs1, rs2);
+ if (rm == 1 || (rm == 0 && !result))
+ result = f32_lt(rs1, rs2);
+ goto success;
+ } else if (GET_PRECISION(insn) == PRECISION_D) {
+ uint64_t rs1 = GET_F64_RS1(insn, regs);
+ uint64_t rs2 = GET_F64_RS2(insn, regs);
+ if (rm != 1)
+ result = f64_eq(rs1, rs2);
+ if (rm == 1 || (rm == 0 && !result))
+ result = f64_lt(rs1, rs2);
+ goto success;
+ }
+ return -1;
+success:
+ SET_RD(insn, regs, result);
+ return 0;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fmv_if)
+{
+ uintptr_t result;
+ if ((insn & MASK_FMV_X_S) == MATCH_FMV_X_S)
+ result = GET_F32_RS1(insn, regs);
+#ifdef __riscv64
+ else if ((insn & MASK_FMV_X_D) == MATCH_FMV_X_D)
+ result = GET_F64_RS1(insn, regs);
+#endif
+ else
+ return -1;
+
+ SET_RD(insn, regs, result);
+ return 0;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fmv_fi)
+{
+ uintptr_t rs1 = GET_RS1(insn, regs);
+
+ if ((insn & MASK_FMV_S_X) == MATCH_FMV_S_X)
+ SET_F32_RD(insn, regs, rs1);
+ else if ((insn & MASK_FMV_D_X) == MATCH_FMV_D_X)
+ SET_F64_RD(insn, regs, rs1);
+ else
+ return -1;
+
+ return 0;
+}
+
+uintptr_t emulate_any_fmadd(int op, uintptr_t* regs, insn_t insn, uintptr_t mstatus)
+{
+ // if FPU is disabled, punt back to the OS
+ if (unlikely((mstatus & MSTATUS_FS) == 0))
+ return -1;
+
+ SETUP_STATIC_ROUNDING(insn);
+ if (GET_PRECISION(insn) == PRECISION_S) {
+ uint32_t rs1 = GET_F32_RS1(insn, regs);
+ uint32_t rs2 = GET_F32_RS2(insn, regs);
+ uint32_t rs3 = GET_F32_RS3(insn, regs);
+ SET_F32_RD(insn, regs, softfloat_mulAddF32(op, rs1, rs2, rs3));
+ return 0;
+ } else if (GET_PRECISION(insn) == PRECISION_D) {
+ uint64_t rs1 = GET_F64_RS1(insn, regs);
+ uint64_t rs2 = GET_F64_RS2(insn, regs);
+ uint64_t rs3 = GET_F64_RS3(insn, regs);
+ SET_F64_RD(insn, regs, softfloat_mulAddF64(op, rs1, rs2, rs3));
+ return 0;
+ }
+ return -1;
+}
+
+DECLARE_EMULATION_FUNC(emulate_fmadd)
+{
+ int op = 0;
+ return emulate_any_fmadd(op, regs, insn, mstatus);
+}
+
+DECLARE_EMULATION_FUNC(emulate_fmsub)
+{
+ int op = softfloat_mulAdd_subC;
+ return emulate_any_fmadd(op, regs, insn, mstatus);
+}
+
+DECLARE_EMULATION_FUNC(emulate_fnmadd)
+{
+ int op = softfloat_mulAdd_subC | softfloat_mulAdd_subProd;
+ return emulate_any_fmadd(op, regs, insn, mstatus);
+}
+
+DECLARE_EMULATION_FUNC(emulate_fnmsub)
+{
+ int op = softfloat_mulAdd_subProd;
+ return emulate_any_fmadd(op, regs, insn, mstatus);
+}