softfloat: Allow 2-operand NaN propagation rule to be set at runtime
IEEE 758 does not define a fixed rule for which NaN to pick as the result if both operands of a 2-operand operation are NaNs. As a result different architectures have ended up with different rules for propagating NaNs. QEMU currently hardcodes the NaN propagation logic into the binary because pickNaN() has an ifdef ladder for different targets. We want to make the propagation rule instead be selectable at runtime, because: * this will let us have multiple targets in one QEMU binary * the Arm FEAT_AFP architectural feature includes letting the guest select a NaN propagation rule at runtime * x86 specifies different propagation rules for x87 FPU ops and for SSE ops, and specifying the rule in the float_status would let us emulate this, instead of wrongly using the x87 rules everywhere In this commit we add an enum for the propagation rule, the field in float_status, and the corresponding getters and setters. We change pickNaN to honour this, but because all targets still leave this field at its default 0 value, the fallback logic will pick the rule type with the old ifdef ladder. It's valid not to set a propagation rule if default_nan_mode is enabled, because in that case there's no need to pick a NaN; all the callers of pickNaN() catch this case and skip calling it. So we can already assert that we don't get into the "no rule defined" codepath for our four targets which always set default_nan_mode: Hexagon, RiscV, SH4 and Tricore, and for the one target which does not have FP at all: avr. These targets will not need to be updated to call set_float_2nan_prop_rule(). Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20241025141254.2141506-2-peter.maydell@linaro.org
This commit is contained in:
parent
11b8920ed2
commit
8a132968b2
@ -390,118 +390,153 @@ bool float32_is_signaling_nan(float32 a_, float_status *status)
|
|||||||
static int pickNaN(FloatClass a_cls, FloatClass b_cls,
|
static int pickNaN(FloatClass a_cls, FloatClass b_cls,
|
||||||
bool aIsLargerSignificand, float_status *status)
|
bool aIsLargerSignificand, float_status *status)
|
||||||
{
|
{
|
||||||
#if defined(TARGET_ARM) || defined(TARGET_MIPS) || defined(TARGET_HPPA) || \
|
Float2NaNPropRule rule = status->float_2nan_prop_rule;
|
||||||
defined(TARGET_LOONGARCH64) || defined(TARGET_S390X)
|
|
||||||
/* ARM mandated NaN propagation rules (see FPProcessNaNs()), take
|
|
||||||
* the first of:
|
|
||||||
* 1. A if it is signaling
|
|
||||||
* 2. B if it is signaling
|
|
||||||
* 3. A (quiet)
|
|
||||||
* 4. B (quiet)
|
|
||||||
* A signaling NaN is always quietened before returning it.
|
|
||||||
*/
|
|
||||||
/* According to MIPS specifications, if one of the two operands is
|
|
||||||
* a sNaN, a new qNaN has to be generated. This is done in
|
|
||||||
* floatXX_silence_nan(). For qNaN inputs the specifications
|
|
||||||
* says: "When possible, this QNaN result is one of the operand QNaN
|
|
||||||
* values." In practice it seems that most implementations choose
|
|
||||||
* the first operand if both operands are qNaN. In short this gives
|
|
||||||
* the following rules:
|
|
||||||
* 1. A if it is signaling
|
|
||||||
* 2. B if it is signaling
|
|
||||||
* 3. A (quiet)
|
|
||||||
* 4. B (quiet)
|
|
||||||
* A signaling NaN is always silenced before returning it.
|
|
||||||
*/
|
|
||||||
if (is_snan(a_cls)) {
|
|
||||||
return 0;
|
|
||||||
} else if (is_snan(b_cls)) {
|
|
||||||
return 1;
|
|
||||||
} else if (is_qnan(a_cls)) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
#elif defined(TARGET_PPC) || defined(TARGET_M68K)
|
|
||||||
/* PowerPC propagation rules:
|
|
||||||
* 1. A if it sNaN or qNaN
|
|
||||||
* 2. B if it sNaN or qNaN
|
|
||||||
* A signaling NaN is always silenced before returning it.
|
|
||||||
*/
|
|
||||||
/* M68000 FAMILY PROGRAMMER'S REFERENCE MANUAL
|
|
||||||
* 3.4 FLOATING-POINT INSTRUCTION DETAILS
|
|
||||||
* If either operand, but not both operands, of an operation is a
|
|
||||||
* nonsignaling NaN, then that NaN is returned as the result. If both
|
|
||||||
* operands are nonsignaling NaNs, then the destination operand
|
|
||||||
* nonsignaling NaN is returned as the result.
|
|
||||||
* If either operand to an operation is a signaling NaN (SNaN), then the
|
|
||||||
* SNaN bit is set in the FPSR EXC byte. If the SNaN exception enable bit
|
|
||||||
* is set in the FPCR ENABLE byte, then the exception is taken and the
|
|
||||||
* destination is not modified. If the SNaN exception enable bit is not
|
|
||||||
* set, setting the SNaN bit in the operand to a one converts the SNaN to
|
|
||||||
* a nonsignaling NaN. The operation then continues as described in the
|
|
||||||
* preceding paragraph for nonsignaling NaNs.
|
|
||||||
*/
|
|
||||||
if (is_nan(a_cls)) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
#elif defined(TARGET_SPARC)
|
|
||||||
/* Prefer SNaN over QNaN, order B then A. */
|
|
||||||
if (is_snan(b_cls)) {
|
|
||||||
return 1;
|
|
||||||
} else if (is_snan(a_cls)) {
|
|
||||||
return 0;
|
|
||||||
} else if (is_qnan(b_cls)) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#elif defined(TARGET_XTENSA)
|
|
||||||
/*
|
/*
|
||||||
* Xtensa has two NaN propagation modes.
|
* We guarantee not to require the target to tell us how to
|
||||||
* Which one is active is controlled by float_status::use_first_nan.
|
* pick a NaN if we're always returning the default NaN.
|
||||||
*/
|
*/
|
||||||
if (status->use_first_nan) {
|
assert(!status->default_nan_mode);
|
||||||
|
|
||||||
|
if (rule == float_2nan_prop_none) {
|
||||||
|
/* target didn't set the rule: fall back to old ifdef choices */
|
||||||
|
#if defined(TARGET_AVR) || defined(TARGET_HEXAGON) \
|
||||||
|
|| defined(TARGET_RISCV) || defined(TARGET_SH4) \
|
||||||
|
|| defined(TARGET_TRICORE)
|
||||||
|
g_assert_not_reached();
|
||||||
|
#elif defined(TARGET_ARM) || defined(TARGET_MIPS) || defined(TARGET_HPPA) || \
|
||||||
|
defined(TARGET_LOONGARCH64) || defined(TARGET_S390X)
|
||||||
|
/*
|
||||||
|
* ARM mandated NaN propagation rules (see FPProcessNaNs()), take
|
||||||
|
* the first of:
|
||||||
|
* 1. A if it is signaling
|
||||||
|
* 2. B if it is signaling
|
||||||
|
* 3. A (quiet)
|
||||||
|
* 4. B (quiet)
|
||||||
|
* A signaling NaN is always quietened before returning it.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* According to MIPS specifications, if one of the two operands is
|
||||||
|
* a sNaN, a new qNaN has to be generated. This is done in
|
||||||
|
* floatXX_silence_nan(). For qNaN inputs the specifications
|
||||||
|
* says: "When possible, this QNaN result is one of the operand QNaN
|
||||||
|
* values." In practice it seems that most implementations choose
|
||||||
|
* the first operand if both operands are qNaN. In short this gives
|
||||||
|
* the following rules:
|
||||||
|
* 1. A if it is signaling
|
||||||
|
* 2. B if it is signaling
|
||||||
|
* 3. A (quiet)
|
||||||
|
* 4. B (quiet)
|
||||||
|
* A signaling NaN is always silenced before returning it.
|
||||||
|
*/
|
||||||
|
rule = float_2nan_prop_s_ab;
|
||||||
|
#elif defined(TARGET_PPC) || defined(TARGET_M68K)
|
||||||
|
/*
|
||||||
|
* PowerPC propagation rules:
|
||||||
|
* 1. A if it sNaN or qNaN
|
||||||
|
* 2. B if it sNaN or qNaN
|
||||||
|
* A signaling NaN is always silenced before returning it.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* M68000 FAMILY PROGRAMMER'S REFERENCE MANUAL
|
||||||
|
* 3.4 FLOATING-POINT INSTRUCTION DETAILS
|
||||||
|
* If either operand, but not both operands, of an operation is a
|
||||||
|
* nonsignaling NaN, then that NaN is returned as the result. If both
|
||||||
|
* operands are nonsignaling NaNs, then the destination operand
|
||||||
|
* nonsignaling NaN is returned as the result.
|
||||||
|
* If either operand to an operation is a signaling NaN (SNaN), then the
|
||||||
|
* SNaN bit is set in the FPSR EXC byte. If the SNaN exception enable bit
|
||||||
|
* is set in the FPCR ENABLE byte, then the exception is taken and the
|
||||||
|
* destination is not modified. If the SNaN exception enable bit is not
|
||||||
|
* set, setting the SNaN bit in the operand to a one converts the SNaN to
|
||||||
|
* a nonsignaling NaN. The operation then continues as described in the
|
||||||
|
* preceding paragraph for nonsignaling NaNs.
|
||||||
|
*/
|
||||||
|
rule = float_2nan_prop_ab;
|
||||||
|
#elif defined(TARGET_SPARC)
|
||||||
|
/* Prefer SNaN over QNaN, order B then A. */
|
||||||
|
rule = float_2nan_prop_s_ba;
|
||||||
|
#elif defined(TARGET_XTENSA)
|
||||||
|
/*
|
||||||
|
* Xtensa has two NaN propagation modes.
|
||||||
|
* Which one is active is controlled by float_status::use_first_nan.
|
||||||
|
*/
|
||||||
|
if (status->use_first_nan) {
|
||||||
|
rule = float_2nan_prop_ab;
|
||||||
|
} else {
|
||||||
|
rule = float_2nan_prop_ba;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
rule = float_2nan_prop_x87;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (rule) {
|
||||||
|
case float_2nan_prop_s_ab:
|
||||||
|
if (is_snan(a_cls)) {
|
||||||
|
return 0;
|
||||||
|
} else if (is_snan(b_cls)) {
|
||||||
|
return 1;
|
||||||
|
} else if (is_qnan(a_cls)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case float_2nan_prop_s_ba:
|
||||||
|
if (is_snan(b_cls)) {
|
||||||
|
return 1;
|
||||||
|
} else if (is_snan(a_cls)) {
|
||||||
|
return 0;
|
||||||
|
} else if (is_qnan(b_cls)) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case float_2nan_prop_ab:
|
||||||
if (is_nan(a_cls)) {
|
if (is_nan(a_cls)) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
} else {
|
break;
|
||||||
|
case float_2nan_prop_ba:
|
||||||
if (is_nan(b_cls)) {
|
if (is_nan(b_cls)) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
#else
|
case float_2nan_prop_x87:
|
||||||
/* This implements x87 NaN propagation rules:
|
/*
|
||||||
* SNaN + QNaN => return the QNaN
|
* This implements x87 NaN propagation rules:
|
||||||
* two SNaNs => return the one with the larger significand, silenced
|
* SNaN + QNaN => return the QNaN
|
||||||
* two QNaNs => return the one with the larger significand
|
* two SNaNs => return the one with the larger significand, silenced
|
||||||
* SNaN and a non-NaN => return the SNaN, silenced
|
* two QNaNs => return the one with the larger significand
|
||||||
* QNaN and a non-NaN => return the QNaN
|
* SNaN and a non-NaN => return the SNaN, silenced
|
||||||
*
|
* QNaN and a non-NaN => return the QNaN
|
||||||
* If we get down to comparing significands and they are the same,
|
*
|
||||||
* return the NaN with the positive sign bit (if any).
|
* If we get down to comparing significands and they are the same,
|
||||||
*/
|
* return the NaN with the positive sign bit (if any).
|
||||||
if (is_snan(a_cls)) {
|
*/
|
||||||
if (is_snan(b_cls)) {
|
if (is_snan(a_cls)) {
|
||||||
return aIsLargerSignificand ? 0 : 1;
|
if (is_snan(b_cls)) {
|
||||||
}
|
return aIsLargerSignificand ? 0 : 1;
|
||||||
return is_qnan(b_cls) ? 1 : 0;
|
}
|
||||||
} else if (is_qnan(a_cls)) {
|
return is_qnan(b_cls) ? 1 : 0;
|
||||||
if (is_snan(b_cls) || !is_qnan(b_cls)) {
|
} else if (is_qnan(a_cls)) {
|
||||||
return 0;
|
if (is_snan(b_cls) || !is_qnan(b_cls)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return aIsLargerSignificand ? 0 : 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return aIsLargerSignificand ? 0 : 1;
|
return 1;
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
return 1;
|
g_assert_not_reached();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*----------------------------------------------------------------------------
|
/*----------------------------------------------------------------------------
|
||||||
|
@ -75,6 +75,12 @@ static inline void set_floatx80_rounding_precision(FloatX80RoundPrec val,
|
|||||||
status->floatx80_rounding_precision = val;
|
status->floatx80_rounding_precision = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void set_float_2nan_prop_rule(Float2NaNPropRule rule,
|
||||||
|
float_status *status)
|
||||||
|
{
|
||||||
|
status->float_2nan_prop_rule = rule;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void set_flush_to_zero(bool val, float_status *status)
|
static inline void set_flush_to_zero(bool val, float_status *status)
|
||||||
{
|
{
|
||||||
status->flush_to_zero = val;
|
status->flush_to_zero = val;
|
||||||
@ -126,6 +132,11 @@ get_floatx80_rounding_precision(float_status *status)
|
|||||||
return status->floatx80_rounding_precision;
|
return status->floatx80_rounding_precision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline Float2NaNPropRule get_float_2nan_prop_rule(float_status *status)
|
||||||
|
{
|
||||||
|
return status->float_2nan_prop_rule;
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool get_flush_to_zero(float_status *status)
|
static inline bool get_flush_to_zero(float_status *status)
|
||||||
{
|
{
|
||||||
return status->flush_to_zero;
|
return status->flush_to_zero;
|
||||||
|
@ -170,6 +170,47 @@ typedef enum __attribute__((__packed__)) {
|
|||||||
floatx80_precision_s,
|
floatx80_precision_s,
|
||||||
} FloatX80RoundPrec;
|
} FloatX80RoundPrec;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 2-input NaN propagation rule. Individual architectures have
|
||||||
|
* different rules for which input NaN is propagated to the output
|
||||||
|
* when there is more than one NaN on the input.
|
||||||
|
*
|
||||||
|
* If default_nan_mode is enabled then it is valid not to set a
|
||||||
|
* NaN propagation rule, because the softfloat code guarantees
|
||||||
|
* not to try to pick a NaN to propagate in default NaN mode.
|
||||||
|
*
|
||||||
|
* For transition, currently the 'none' rule will cause us to
|
||||||
|
* fall back to picking the propagation rule based on the existing
|
||||||
|
* ifdef ladder. When all targets are converted it will be an error
|
||||||
|
* not to set the rule in float_status unless in default_nan_mode,
|
||||||
|
* and we will assert if we need to handle an input NaN and no
|
||||||
|
* rule was selected.
|
||||||
|
*/
|
||||||
|
typedef enum __attribute__((__packed__)) {
|
||||||
|
/* No propagation rule specified */
|
||||||
|
float_2nan_prop_none = 0,
|
||||||
|
/* Prefer SNaN over QNaN, then operand A over B */
|
||||||
|
float_2nan_prop_s_ab,
|
||||||
|
/* Prefer SNaN over QNaN, then operand B over A */
|
||||||
|
float_2nan_prop_s_ba,
|
||||||
|
/* Prefer A over B regardless of SNaN vs QNaN */
|
||||||
|
float_2nan_prop_ab,
|
||||||
|
/* Prefer B over A regardless of SNaN vs QNaN */
|
||||||
|
float_2nan_prop_ba,
|
||||||
|
/*
|
||||||
|
* This implements x87 NaN propagation rules:
|
||||||
|
* SNaN + QNaN => return the QNaN
|
||||||
|
* two SNaNs => return the one with the larger significand, silenced
|
||||||
|
* two QNaNs => return the one with the larger significand
|
||||||
|
* SNaN and a non-NaN => return the SNaN, silenced
|
||||||
|
* QNaN and a non-NaN => return the QNaN
|
||||||
|
*
|
||||||
|
* If we get down to comparing significands and they are the same,
|
||||||
|
* return the NaN with the positive sign bit (if any).
|
||||||
|
*/
|
||||||
|
float_2nan_prop_x87,
|
||||||
|
} Float2NaNPropRule;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Floating Point Status. Individual architectures may maintain
|
* Floating Point Status. Individual architectures may maintain
|
||||||
* several versions of float_status for different functions. The
|
* several versions of float_status for different functions. The
|
||||||
@ -181,6 +222,7 @@ typedef struct float_status {
|
|||||||
uint16_t float_exception_flags;
|
uint16_t float_exception_flags;
|
||||||
FloatRoundMode float_rounding_mode;
|
FloatRoundMode float_rounding_mode;
|
||||||
FloatX80RoundPrec floatx80_rounding_precision;
|
FloatX80RoundPrec floatx80_rounding_precision;
|
||||||
|
Float2NaNPropRule float_2nan_prop_rule;
|
||||||
bool tininess_before_rounding;
|
bool tininess_before_rounding;
|
||||||
/* should denormalised results go to zero and set the inexact flag? */
|
/* should denormalised results go to zero and set the inexact flag? */
|
||||||
bool flush_to_zero;
|
bool flush_to_zero;
|
||||||
|
Loading…
Reference in New Issue
Block a user