qemu/target/hppa/fpu_helper.c
Peter Maydell 28f13bccbe fpu: allow flushing of output denormals to be after rounding
Currently we handle flushing of output denormals in uncanon_normal
always before we deal with rounding.  This works for architectures
that detect tininess before rounding, but is usually not the right
place when the architecture detects tininess after rounding.  For
example, for x86 the SDM states that the MXCSR FTZ control bit causes
outputs to be flushed to zero "when it detects a floating-point
underflow condition".  This means that we mustn't flush to zero if
the input is such that after rounding it is no longer tiny.

At least one of our guest architectures does underflow detection
after rounding but flushing of denormals before rounding (MIPS MSA);
this means we need to have a config knob for this that is separate
from our existing tininess_before_rounding setting.

Add an ftz_detection flag.  For consistency with
tininess_before_rounding, we make it default to "detect ftz after
rounding"; this means that we need to explicitly set the flag to
"detect ftz before rounding" on every existing architecture that sets
flush_to_zero, so that this commit has no behaviour change.
(This means more code change here but for the long term a less
confusing API.)

For several architectures the current behaviour is either
definitely or possibly wrong; annotate those with TODO comments.
These architectures are definitely wrong (and should detect
ftz after rounding):
 * x86
 * Alpha

For these architectures the spec is unclear:
 * MIPS (for non-MSA)
 * RX
 * SH4

PA-RISC makes ftz detection IMPDEF, but we aren't setting the
"tininess before rounding" setting that we ought to.

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
2025-02-11 16:22:07 +00:00

479 lines
13 KiB
C

/*
* Helpers for HPPA FPU instructions.
*
* Copyright (c) 2016 Richard Henderson <rth@twiddle.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "cpu.h"
#include "exec/exec-all.h"
#include "exec/helper-proto.h"
#include "fpu/softfloat.h"
void HELPER(loaded_fr0)(CPUHPPAState *env)
{
uint32_t shadow = env->fr[0] >> 32;
int rm, d;
env->fr0_shadow = shadow;
switch (FIELD_EX32(shadow, FPSR, RM)) {
default:
rm = float_round_nearest_even;
break;
case 1:
rm = float_round_to_zero;
break;
case 2:
rm = float_round_up;
break;
case 3:
rm = float_round_down;
break;
}
set_float_rounding_mode(rm, &env->fp_status);
d = FIELD_EX32(shadow, FPSR, D);
set_flush_to_zero(d, &env->fp_status);
set_flush_inputs_to_zero(d, &env->fp_status);
/*
* TODO: we only need to do this at CPU reset, but currently
* HPPA does note implement a CPU reset method at all...
*/
set_float_2nan_prop_rule(float_2nan_prop_s_ab, &env->fp_status);
/*
* TODO: The HPPA architecture reference only documents its NaN
* propagation rule for 2-operand operations. Testing on real hardware
* might be necessary to confirm whether this order for muladd is correct.
* Not preferring the SNaN is almost certainly incorrect as it diverges
* from the documented rules for 2-operand operations.
*/
set_float_3nan_prop_rule(float_3nan_prop_abc, &env->fp_status);
/* For inf * 0 + NaN, return the input NaN */
set_float_infzeronan_rule(float_infzeronan_dnan_never, &env->fp_status);
/* Default NaN: sign bit clear, msb-1 frac bit set */
set_float_default_nan_pattern(0b00100000, &env->fp_status);
/*
* "PA-RISC 2.0 Architecture" says it is IMPDEF whether the flushing
* enabled by FPSR.D happens before or after rounding. We pick "before"
* for consistency with tininess detection.
*/
set_float_ftz_detection(float_ftz_before_rounding, &env->fp_status);
/*
* TODO: "PA-RISC 2.0 Architecture" chapter 10 says that we should
* detect tininess before rounding, but we don't set that here so we
* get the default tininess after rounding.
*/
}
void cpu_hppa_loaded_fr0(CPUHPPAState *env)
{
helper_loaded_fr0(env);
}
#define CONVERT_BIT(X, SRC, DST) \
((unsigned)(SRC) > (unsigned)(DST) \
? (X) / ((SRC) / (DST)) & (DST) \
: ((X) & (SRC)) * ((DST) / (SRC)))
static void update_fr0_op(CPUHPPAState *env, uintptr_t ra)
{
uint32_t soft_exp = get_float_exception_flags(&env->fp_status);
uint32_t hard_exp = 0;
uint32_t shadow = env->fr0_shadow;
if (likely(soft_exp == 0)) {
env->fr[0] = (uint64_t)shadow << 32;
return;
}
set_float_exception_flags(0, &env->fp_status);
hard_exp |= CONVERT_BIT(soft_exp, float_flag_inexact, R_FPSR_ENA_I_MASK);
hard_exp |= CONVERT_BIT(soft_exp, float_flag_underflow, R_FPSR_ENA_U_MASK);
hard_exp |= CONVERT_BIT(soft_exp, float_flag_overflow, R_FPSR_ENA_O_MASK);
hard_exp |= CONVERT_BIT(soft_exp, float_flag_divbyzero, R_FPSR_ENA_Z_MASK);
hard_exp |= CONVERT_BIT(soft_exp, float_flag_invalid, R_FPSR_ENA_V_MASK);
shadow |= hard_exp << (R_FPSR_FLAGS_SHIFT - R_FPSR_ENABLES_SHIFT);
env->fr0_shadow = shadow;
env->fr[0] = (uint64_t)shadow << 32;
if (hard_exp & shadow) {
hppa_dynamic_excp(env, EXCP_ASSIST, ra);
}
}
float32 HELPER(fsqrt_s)(CPUHPPAState *env, float32 arg)
{
float32 ret = float32_sqrt(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(frnd_s)(CPUHPPAState *env, float32 arg)
{
float32 ret = float32_round_to_int(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(fadd_s)(CPUHPPAState *env, float32 a, float32 b)
{
float32 ret = float32_add(a, b, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(fsub_s)(CPUHPPAState *env, float32 a, float32 b)
{
float32 ret = float32_sub(a, b, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(fmpy_s)(CPUHPPAState *env, float32 a, float32 b)
{
float32 ret = float32_mul(a, b, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(fdiv_s)(CPUHPPAState *env, float32 a, float32 b)
{
float32 ret = float32_div(a, b, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fsqrt_d)(CPUHPPAState *env, float64 arg)
{
float64 ret = float64_sqrt(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(frnd_d)(CPUHPPAState *env, float64 arg)
{
float64 ret = float64_round_to_int(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fadd_d)(CPUHPPAState *env, float64 a, float64 b)
{
float64 ret = float64_add(a, b, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fsub_d)(CPUHPPAState *env, float64 a, float64 b)
{
float64 ret = float64_sub(a, b, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fmpy_d)(CPUHPPAState *env, float64 a, float64 b)
{
float64 ret = float64_mul(a, b, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fdiv_d)(CPUHPPAState *env, float64 a, float64 b)
{
float64 ret = float64_div(a, b, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fcnv_s_d)(CPUHPPAState *env, float32 arg)
{
float64 ret = float32_to_float64(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(fcnv_d_s)(CPUHPPAState *env, float64 arg)
{
float32 ret = float64_to_float32(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(fcnv_w_s)(CPUHPPAState *env, int32_t arg)
{
float32 ret = int32_to_float32(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(fcnv_dw_s)(CPUHPPAState *env, int64_t arg)
{
float32 ret = int64_to_float32(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fcnv_w_d)(CPUHPPAState *env, int32_t arg)
{
float64 ret = int32_to_float64(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fcnv_dw_d)(CPUHPPAState *env, int64_t arg)
{
float64 ret = int64_to_float64(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
int32_t HELPER(fcnv_s_w)(CPUHPPAState *env, float32 arg)
{
int32_t ret = float32_to_int32(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
int32_t HELPER(fcnv_d_w)(CPUHPPAState *env, float64 arg)
{
int32_t ret = float64_to_int32(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
int64_t HELPER(fcnv_s_dw)(CPUHPPAState *env, float32 arg)
{
int64_t ret = float32_to_int64(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
int64_t HELPER(fcnv_d_dw)(CPUHPPAState *env, float64 arg)
{
int64_t ret = float64_to_int64(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
int32_t HELPER(fcnv_t_s_w)(CPUHPPAState *env, float32 arg)
{
int32_t ret = float32_to_int32_round_to_zero(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
int32_t HELPER(fcnv_t_d_w)(CPUHPPAState *env, float64 arg)
{
int32_t ret = float64_to_int32_round_to_zero(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
int64_t HELPER(fcnv_t_s_dw)(CPUHPPAState *env, float32 arg)
{
int64_t ret = float32_to_int64_round_to_zero(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
int64_t HELPER(fcnv_t_d_dw)(CPUHPPAState *env, float64 arg)
{
int64_t ret = float64_to_int64_round_to_zero(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(fcnv_uw_s)(CPUHPPAState *env, uint32_t arg)
{
float32 ret = uint32_to_float32(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(fcnv_udw_s)(CPUHPPAState *env, uint64_t arg)
{
float32 ret = uint64_to_float32(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fcnv_uw_d)(CPUHPPAState *env, uint32_t arg)
{
float64 ret = uint32_to_float64(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fcnv_udw_d)(CPUHPPAState *env, uint64_t arg)
{
float64 ret = uint64_to_float64(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
uint32_t HELPER(fcnv_s_uw)(CPUHPPAState *env, float32 arg)
{
uint32_t ret = float32_to_uint32(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
uint32_t HELPER(fcnv_d_uw)(CPUHPPAState *env, float64 arg)
{
uint32_t ret = float64_to_uint32(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
uint64_t HELPER(fcnv_s_udw)(CPUHPPAState *env, float32 arg)
{
uint64_t ret = float32_to_uint64(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
uint64_t HELPER(fcnv_d_udw)(CPUHPPAState *env, float64 arg)
{
uint64_t ret = float64_to_uint64(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
uint32_t HELPER(fcnv_t_s_uw)(CPUHPPAState *env, float32 arg)
{
uint32_t ret = float32_to_uint32_round_to_zero(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
uint32_t HELPER(fcnv_t_d_uw)(CPUHPPAState *env, float64 arg)
{
uint32_t ret = float64_to_uint32_round_to_zero(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
uint64_t HELPER(fcnv_t_s_udw)(CPUHPPAState *env, float32 arg)
{
uint64_t ret = float32_to_uint64_round_to_zero(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
uint64_t HELPER(fcnv_t_d_udw)(CPUHPPAState *env, float64 arg)
{
uint64_t ret = float64_to_uint64_round_to_zero(arg, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
static void update_fr0_cmp(CPUHPPAState *env, uint32_t y,
uint32_t c, FloatRelation r)
{
uint32_t shadow = env->fr0_shadow;
switch (r) {
case float_relation_greater:
c = extract32(c, 4, 1);
break;
case float_relation_less:
c = extract32(c, 3, 1);
break;
case float_relation_equal:
c = extract32(c, 2, 1);
break;
case float_relation_unordered:
c = extract32(c, 1, 1);
break;
default:
g_assert_not_reached();
}
if (y) {
/* targeted comparison */
/* set fpsr[ca[y - 1]] to current compare */
shadow = deposit32(shadow, R_FPSR_CA0_SHIFT - (y - 1), 1, c);
} else {
/* queued comparison */
/* shift cq right by one place */
shadow = (shadow & ~R_FPSR_CQ_MASK) | ((shadow >> 1) & R_FPSR_CQ_MASK);
/* move fpsr[c] to fpsr[cq[0]] */
shadow = FIELD_DP32(shadow, FPSR, CQ0, FIELD_EX32(shadow, FPSR, C));
/* set fpsr[c] to current compare */
shadow = FIELD_DP32(shadow, FPSR, C, c);
}
env->fr0_shadow = shadow;
env->fr[0] = (uint64_t)shadow << 32;
}
void HELPER(fcmp_s)(CPUHPPAState *env, float32 a, float32 b,
uint32_t y, uint32_t c)
{
FloatRelation r;
if (c & 1) {
r = float32_compare(a, b, &env->fp_status);
} else {
r = float32_compare_quiet(a, b, &env->fp_status);
}
update_fr0_op(env, GETPC());
update_fr0_cmp(env, y, c, r);
}
void HELPER(fcmp_d)(CPUHPPAState *env, float64 a, float64 b,
uint32_t y, uint32_t c)
{
FloatRelation r;
if (c & 1) {
r = float64_compare(a, b, &env->fp_status);
} else {
r = float64_compare_quiet(a, b, &env->fp_status);
}
update_fr0_op(env, GETPC());
update_fr0_cmp(env, y, c, r);
}
float32 HELPER(fmpyfadd_s)(CPUHPPAState *env, float32 a, float32 b, float32 c)
{
float32 ret = float32_muladd(a, b, c, 0, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float32 HELPER(fmpynfadd_s)(CPUHPPAState *env, float32 a, float32 b, float32 c)
{
float32 ret = float32_muladd(a, b, c, float_muladd_negate_product,
&env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fmpyfadd_d)(CPUHPPAState *env, float64 a, float64 b, float64 c)
{
float64 ret = float64_muladd(a, b, c, 0, &env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}
float64 HELPER(fmpynfadd_d)(CPUHPPAState *env, float64 a, float64 b, float64 c)
{
float64 ret = float64_muladd(a, b, c, float_muladd_negate_product,
&env->fp_status);
update_fr0_op(env, GETPC());
return ret;
}