1// See LICENSE for license details.
2
3#include "fp_emulation.h"
4#include "unprivileged_memory.h"
5#include "softfloat.h"
6#include "internals.h"
7#include "config.h"
8
9DECLARE_EMULATION_FUNC(emulate_fp)
10{
11  asm (".pushsection .rodata\n"
12       "fp_emulation_table:\n"
13       "  .word emulate_fadd\n"
14       "  .word emulate_fsub\n"
15       "  .word emulate_fmul\n"
16       "  .word emulate_fdiv\n"
17       "  .word emulate_fsgnj\n"
18       "  .word emulate_fmin\n"
19       "  .word truly_illegal_insn\n"
20       "  .word truly_illegal_insn\n"
21       "  .word emulate_fcvt_ff\n"
22       "  .word truly_illegal_insn\n"
23       "  .word truly_illegal_insn\n"
24       "  .word emulate_fsqrt\n"
25       "  .word truly_illegal_insn\n"
26       "  .word truly_illegal_insn\n"
27       "  .word truly_illegal_insn\n"
28       "  .word truly_illegal_insn\n"
29       "  .word truly_illegal_insn\n"
30       "  .word truly_illegal_insn\n"
31       "  .word truly_illegal_insn\n"
32       "  .word truly_illegal_insn\n"
33       "  .word emulate_fcmp\n"
34       "  .word truly_illegal_insn\n"
35       "  .word truly_illegal_insn\n"
36       "  .word truly_illegal_insn\n"
37       "  .word emulate_fcvt_if\n"
38       "  .word truly_illegal_insn\n"
39       "  .word emulate_fcvt_fi\n"
40       "  .word truly_illegal_insn\n"
41       "  .word emulate_fmv_if\n"
42       "  .word truly_illegal_insn\n"
43       "  .word emulate_fmv_fi\n"
44       "  .word truly_illegal_insn\n"
45       "  .popsection");
46
47  // if FPU is disabled, punt back to the OS
48  if (unlikely((mstatus & MSTATUS_FS) == 0))
49    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
50
51  extern uint32_t fp_emulation_table[];
52  uint32_t* pf = (void*)fp_emulation_table + ((insn >> 25) & 0x7c);
53  emulation_func f = (emulation_func)(uintptr_t)*pf;
54
55  SETUP_STATIC_ROUNDING(insn);
56  return f(regs, mcause, mepc, mstatus, insn);
57}
58
59#define f32(x) ((float32_t){ .v = x })
60#define f64(x) ((float64_t){ .v = x })
61
62void emulate_any_fadd(uintptr_t* regs, uintptr_t mcause, uintptr_t mepc, uintptr_t mstatus, insn_t insn, int32_t neg_b)
63{
64  if (GET_PRECISION(insn) == PRECISION_S) {
65    uint32_t rs1 = GET_F32_RS1(insn, regs);
66    uint32_t rs2 = GET_F32_RS2(insn, regs) ^ neg_b;
67    SET_F32_RD(insn, regs, f32_add(f32(rs1), f32(rs2)).v);
68  } else if (GET_PRECISION(insn) == PRECISION_D) {
69    uint64_t rs1 = GET_F64_RS1(insn, regs);
70    uint64_t rs2 = GET_F64_RS2(insn, regs) ^ ((uint64_t)neg_b << 32);
71    SET_F64_RD(insn, regs, f64_add(f64(rs1), f64(rs2)).v);
72  } else {
73    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
74  }
75}
76
77DECLARE_EMULATION_FUNC(emulate_fadd)
78{
79  return emulate_any_fadd(regs, mcause, mepc, mstatus, insn, 0);
80}
81
82DECLARE_EMULATION_FUNC(emulate_fsub)
83{
84  return emulate_any_fadd(regs, mcause, mepc, mstatus, insn, INT32_MIN);
85}
86
87DECLARE_EMULATION_FUNC(emulate_fmul)
88{
89  if (GET_PRECISION(insn) == PRECISION_S) {
90    uint32_t rs1 = GET_F32_RS1(insn, regs);
91    uint32_t rs2 = GET_F32_RS2(insn, regs);
92    SET_F32_RD(insn, regs, f32_mul(f32(rs1), f32(rs2)).v);
93  } else if (GET_PRECISION(insn) == PRECISION_D) {
94    uint64_t rs1 = GET_F64_RS1(insn, regs);
95    uint64_t rs2 = GET_F64_RS2(insn, regs);
96    SET_F64_RD(insn, regs, f64_mul(f64(rs1), f64(rs2)).v);
97  } else {
98    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
99  }
100}
101
102DECLARE_EMULATION_FUNC(emulate_fdiv)
103{
104  if (GET_PRECISION(insn) == PRECISION_S) {
105    uint32_t rs1 = GET_F32_RS1(insn, regs);
106    uint32_t rs2 = GET_F32_RS2(insn, regs);
107    SET_F32_RD(insn, regs, f32_div(f32(rs1), f32(rs2)).v);
108  } else if (GET_PRECISION(insn) == PRECISION_D) {
109    uint64_t rs1 = GET_F64_RS1(insn, regs);
110    uint64_t rs2 = GET_F64_RS2(insn, regs);
111    SET_F64_RD(insn, regs, f64_div(f64(rs1), f64(rs2)).v);
112  } else {
113    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
114  }
115}
116
117DECLARE_EMULATION_FUNC(emulate_fsqrt)
118{
119  if ((insn >> 20) & 0x1f)
120    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
121
122  if (GET_PRECISION(insn) == PRECISION_S) {
123    SET_F32_RD(insn, regs, f32_sqrt(f32(GET_F32_RS1(insn, regs))).v);
124  } else if (GET_PRECISION(insn) == PRECISION_D) {
125    SET_F64_RD(insn, regs, f64_sqrt(f64(GET_F64_RS1(insn, regs))).v);
126  } else {
127    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
128  }
129}
130
131DECLARE_EMULATION_FUNC(emulate_fsgnj)
132{
133  int rm = GET_RM(insn);
134  if (rm >= 3)
135    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
136
137  #define DO_FSGNJ(rs1, rs2, rm) ({ \
138    typeof(rs1) rs1_sign = (rs1) >> (8*sizeof(rs1)-1); \
139    typeof(rs1) rs2_sign = (rs2) >> (8*sizeof(rs1)-1); \
140    rs1_sign &= (rm) >> 1; \
141    rs1_sign ^= (rm) ^ rs2_sign; \
142    ((rs1) << 1 >> 1) | (rs1_sign << (8*sizeof(rs1)-1)); })
143
144  if (GET_PRECISION(insn) == PRECISION_S) {
145    uint32_t rs1 = GET_F32_RS1(insn, regs);
146    uint32_t rs2 = GET_F32_RS2(insn, regs);
147    SET_F32_RD(insn, regs, DO_FSGNJ(rs1, rs2, rm));
148  } else if (GET_PRECISION(insn) == PRECISION_D) {
149    uint64_t rs1 = GET_F64_RS1(insn, regs);
150    uint64_t rs2 = GET_F64_RS2(insn, regs);
151    SET_F64_RD(insn, regs, DO_FSGNJ(rs1, rs2, rm));
152  } else {
153    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
154  }
155}
156
157DECLARE_EMULATION_FUNC(emulate_fmin)
158{
159  int rm = GET_RM(insn);
160  if (rm >= 2)
161    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
162
163  if (GET_PRECISION(insn) == PRECISION_S) {
164    uint32_t rs1 = GET_F32_RS1(insn, regs);
165    uint32_t rs2 = GET_F32_RS2(insn, regs);
166    uint32_t arg1 = rm ? rs2 : rs1;
167    uint32_t arg2 = rm ? rs1 : rs2;
168    int use_rs1 = f32_lt_quiet(f32(arg1), f32(arg2)) || isNaNF32UI(rs2);
169    SET_F32_RD(insn, regs, use_rs1 ? rs1 : rs2);
170  } else if (GET_PRECISION(insn) == PRECISION_D) {
171    uint64_t rs1 = GET_F64_RS1(insn, regs);
172    uint64_t rs2 = GET_F64_RS2(insn, regs);
173    uint64_t arg1 = rm ? rs2 : rs1;
174    uint64_t arg2 = rm ? rs1 : rs2;
175    int use_rs1 = f64_lt_quiet(f64(arg1), f64(arg2)) || isNaNF64UI(rs2);
176    SET_F64_RD(insn, regs, use_rs1 ? rs1 : rs2);
177  } else {
178    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
179  }
180}
181
182DECLARE_EMULATION_FUNC(emulate_fcvt_ff)
183{
184  int rs2_num = (insn >> 20) & 0x1f;
185  if (GET_PRECISION(insn) == PRECISION_S) {
186    if (rs2_num != 1)
187      return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
188    SET_F32_RD(insn, regs, f64_to_f32(f64(GET_F64_RS1(insn, regs))).v);
189  } else if (GET_PRECISION(insn) == PRECISION_D) {
190    if (rs2_num != 0)
191      return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
192    SET_F64_RD(insn, regs, f32_to_f64(f32(GET_F32_RS1(insn, regs))).v);
193  } else {
194    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
195  }
196}
197
198DECLARE_EMULATION_FUNC(emulate_fcvt_fi)
199{
200  if (GET_PRECISION(insn) != PRECISION_S && GET_PRECISION(insn) != PRECISION_D)
201    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
202
203  int negative = 0;
204  uint64_t uint_val = GET_RS1(insn, regs);
205
206  switch ((insn >> 20) & 0x1f)
207  {
208    case 0: // int32
209      negative = (int32_t)uint_val < 0;
210      uint_val = (uint32_t)(negative ? -uint_val : uint_val);
211      break;
212    case 1: // uint32
213      uint_val = (uint32_t)uint_val;
214      break;
215#if __riscv_xlen == 64
216    case 2: // int64
217      negative = (int64_t)uint_val < 0;
218      uint_val = negative ? -uint_val : uint_val;
219    case 3: // uint64
220      break;
221#endif
222    default:
223      return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
224  }
225
226  uint64_t float64 = ui64_to_f64(uint_val).v;
227  if (negative)
228    float64 ^= INT64_MIN;
229
230  if (GET_PRECISION(insn) == PRECISION_S)
231    SET_F32_RD(insn, regs, f64_to_f32(f64(float64)).v);
232  else
233    SET_F64_RD(insn, regs, float64);
234}
235
236DECLARE_EMULATION_FUNC(emulate_fcvt_if)
237{
238  int rs2_num = (insn >> 20) & 0x1f;
239#if __riscv_xlen == 64
240  if (rs2_num >= 4)
241    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
242#else
243  if (rs2_num >= 2)
244    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
245#endif
246
247  int64_t float64;
248  if (GET_PRECISION(insn) == PRECISION_S)
249    float64 = f32_to_f64(f32(GET_F32_RS1(insn, regs))).v;
250  else if (GET_PRECISION(insn) == PRECISION_D)
251    float64 = GET_F64_RS1(insn, regs);
252  else
253    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
254
255  int negative = 0;
256  if (float64 < 0) {
257    negative = 1;
258    float64 ^= INT64_MIN;
259  }
260  uint64_t uint_val = f64_to_ui64(f64(float64), softfloat_roundingMode, true);
261  uint64_t result, limit, limit_result;
262
263  switch (rs2_num)
264  {
265    case 0: // int32
266      if (negative) {
267        result = (int32_t)-uint_val;
268        limit_result = limit = (uint32_t)INT32_MIN;
269      } else {
270        result = (int32_t)uint_val;
271        limit_result = limit = INT32_MAX;
272      }
273      break;
274
275    case 1: // uint32
276      limit = limit_result = UINT32_MAX;
277      if (negative)
278        result = limit = 0;
279      else
280        result = (uint32_t)uint_val;
281      break;
282
283    case 2: // int32
284      if (negative) {
285        result = (int64_t)-uint_val;
286        limit_result = limit = (uint64_t)INT64_MIN;
287      } else {
288        result = (int64_t)uint_val;
289        limit_result = limit = INT64_MAX;
290      }
291      break;
292
293    case 3: // uint64
294      limit = limit_result = UINT64_MAX;
295      if (negative)
296        result = limit = 0;
297      else
298        result = (uint64_t)uint_val;
299      break;
300
301    default:
302      __builtin_unreachable();
303  }
304
305  if (uint_val > limit) {
306    result = limit_result;
307    softfloat_raiseFlags(softfloat_flag_invalid);
308  }
309
310  SET_FS_DIRTY();
311  SET_RD(insn, regs, result);
312}
313
314DECLARE_EMULATION_FUNC(emulate_fcmp)
315{
316  int rm = GET_RM(insn);
317  if (rm >= 3)
318    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
319
320  uintptr_t result;
321  if (GET_PRECISION(insn) == PRECISION_S) {
322    uint32_t rs1 = GET_F32_RS1(insn, regs);
323    uint32_t rs2 = GET_F32_RS2(insn, regs);
324    if (rm != 1)
325      result = f32_eq(f32(rs1), f32(rs2));
326    if (rm == 1 || (rm == 0 && !result))
327      result = f32_lt(f32(rs1), f32(rs2));
328    goto success;
329  } else if (GET_PRECISION(insn) == PRECISION_D) {
330    uint64_t rs1 = GET_F64_RS1(insn, regs);
331    uint64_t rs2 = GET_F64_RS2(insn, regs);
332    if (rm != 1)
333      result = f64_eq(f64(rs1), f64(rs2));
334    if (rm == 1 || (rm == 0 && !result))
335      result = f64_lt(f64(rs1), f64(rs2));
336    goto success;
337  }
338  return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
339success:
340  SET_FS_DIRTY();
341  SET_RD(insn, regs, result);
342}
343
344DECLARE_EMULATION_FUNC(emulate_fmv_if)
345{
346  uintptr_t result;
347  if ((insn >> 20) & 0x1f)
348    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
349
350  if (GET_PRECISION(insn) == PRECISION_S) {
351    result = GET_F32_RS1(insn, regs);
352    switch (GET_RM(insn)) {
353      case GET_RM(MATCH_FMV_X_W): break;
354      case GET_RM(MATCH_FCLASS_S): result = f32_classify(f32(result)); break;
355      default: return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
356    }
357  } else if (GET_PRECISION(insn) == PRECISION_D) {
358    result = GET_F64_RS1(insn, regs);
359    switch (GET_RM(insn)) {
360      case GET_RM(MATCH_FMV_X_D): break;
361      case GET_RM(MATCH_FCLASS_D): result = f64_classify(f64(result)); break;
362      default: return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
363    }
364  } else {
365    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
366  }
367
368  SET_FS_DIRTY();
369  SET_RD(insn, regs, result);
370}
371
372DECLARE_EMULATION_FUNC(emulate_fmv_fi)
373{
374  uintptr_t rs1 = GET_RS1(insn, regs);
375
376  if ((insn & MASK_FMV_W_X) == MATCH_FMV_W_X)
377    SET_F32_RD(insn, regs, rs1);
378#if __riscv_xlen == 64
379  else if ((insn & MASK_FMV_D_X) == MATCH_FMV_D_X)
380    SET_F64_RD(insn, regs, rs1);
381#endif
382  else
383    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
384}
385
386DECLARE_EMULATION_FUNC(emulate_fmadd)
387{
388  // if FPU is disabled, punt back to the OS
389  if (unlikely((mstatus & MSTATUS_FS) == 0))
390    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
391
392  bool negA = (insn >> 3) & 1;
393  bool negC = (insn >> 2) & 1;
394  SETUP_STATIC_ROUNDING(insn);
395  if (GET_PRECISION(insn) == PRECISION_S) {
396    uint32_t rs1 = GET_F32_RS1(insn, regs) ^ (negA ? INT32_MIN : 0);
397    uint32_t rs2 = GET_F32_RS2(insn, regs);
398    uint32_t rs3 = GET_F32_RS3(insn, regs) ^ (negC ? INT32_MIN : 0);
399    SET_F32_RD(insn, regs, softfloat_mulAddF32(rs1, rs2, rs3, 0).v);
400  } else if (GET_PRECISION(insn) == PRECISION_D) {
401    uint64_t rs1 = GET_F64_RS1(insn, regs) ^ (negA ? INT64_MIN : 0);
402    uint64_t rs2 = GET_F64_RS2(insn, regs);
403    uint64_t rs3 = GET_F64_RS3(insn, regs) ^ (negC ? INT64_MIN : 0);
404    SET_F64_RD(insn, regs, softfloat_mulAddF64(rs1, rs2, rs3, 0).v);
405  } else {
406    return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
407  }
408}
409