@@ -81,6 +81,12 @@ static inline void set_float_2nan_prop_rule(Float2NaNPropRule rule,
status->float_2nan_prop_rule = rule;
}
+static inline void set_float_3nan_prop_rule(Float3NaNPropRule rule,
+ float_status *status)
+{
+ status->float_3nan_prop_rule = rule;
+}
+
static inline void set_float_infzeronan_rule(FloatInfZeroNaNRule rule,
float_status *status)
{
@@ -143,6 +149,11 @@ static inline Float2NaNPropRule get_float_2nan_prop_rule(float_status *status)
return status->float_2nan_prop_rule;
}
+static inline Float3NaNPropRule get_float_3nan_prop_rule(float_status *status)
+{
+ return status->float_3nan_prop_rule;
+}
+
static inline FloatInfZeroNaNRule get_float_infzeronan_rule(float_status *status)
{
return status->float_infzeronan_rule;
@@ -207,6 +207,42 @@ typedef enum __attribute__((__packed__)) {
float_2nan_prop_x87,
} Float2NaNPropRule;
+/*
+ * 3-input NaN propagation rule, for fused multiply-add. 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. When not in
+ * default-NaN mode, it is an error for the target not to set the rule
+ * in float_status if it uses a muladd, and we will assert if we need
+ * to handle an input NaN and no rule was selected.
+ *
+ * For QEMU, the multiply-add operation is A * B + C.
+ *
+ * NB: we don't list all 12 possibilities here or implement them
+ * in pickNaNMulAdd; if your architecture needs one of the missing
+ * combinations you should add it.
+ */
+typedef enum __attribute__((__packed__)) {
+ /* No propagation rule specified */
+ float_3nan_prop_none = 0,
+ /* Prefer SNaN over QNaN, then operand A over B over C */
+ float_3nan_prop_s_abc,
+ /* Prefer SNaN over QNaN, then operand C over A over B */
+ float_3nan_prop_s_cab,
+ /* Prefer SNaN over QNaN, then operand C over B over A */
+ float_3nan_prop_s_cba,
+ /* Prefer A over B over C regardless of SNaN vs QNaN */
+ float_3nan_prop_abc,
+ /* Prefer A over C over B regardless of SNaN vs QNaN */
+ float_3nan_prop_acb,
+ /* Prefer C over B over A regardless of SNaN vs QNaN */
+ float_3nan_prop_cba,
+} Float3NaNPropRule;
+
/*
* Rule for result of fused multiply-add 0 * Inf + NaN.
* This must be a NaN, but implementations differ on whether this
@@ -241,6 +277,7 @@ typedef struct float_status {
FloatRoundMode float_rounding_mode;
FloatX80RoundPrec floatx80_rounding_precision;
Float2NaNPropRule float_2nan_prop_rule;
+ Float3NaNPropRule float_3nan_prop_rule;
FloatInfZeroNaNRule float_infzeronan_rule;
bool tininess_before_rounding;
/* should denormalised results go to zero and set the inexact flag? */
@@ -475,6 +475,7 @@ static int pickNaN(FloatClass a_cls, FloatClass b_cls,
static int pickNaNMulAdd(FloatClass a_cls, FloatClass b_cls, FloatClass c_cls,
bool infzero, float_status *status)
{
+ Float3NaNPropRule rule = status->float_3nan_prop_rule;
/*
* We guarantee not to require the target to tell us how to
* pick a NaN if we're always returning the default NaN.
@@ -500,27 +501,44 @@ static int pickNaNMulAdd(FloatClass a_cls, FloatClass b_cls, FloatClass c_cls,
}
}
+ if (rule == float_3nan_prop_none) {
#if defined(TARGET_ARM)
-
- /* This looks different from the ARM ARM pseudocode, because the ARM ARM
- * puts the operands to a fused mac operation (a*b)+c in the order c,a,b.
- */
- if (is_snan(c_cls)) {
- return 2;
- } else if (is_snan(a_cls)) {
- return 0;
- } else if (is_snan(b_cls)) {
- return 1;
- } else if (is_qnan(c_cls)) {
- return 2;
- } else if (is_qnan(a_cls)) {
- return 0;
- } else {
- return 1;
- }
+ /*
+ * This looks different from the ARM ARM pseudocode, because the ARM ARM
+ * puts the operands to a fused mac operation (a*b)+c in the order c,a,b
+ */
+ rule = float_3nan_prop_s_cab;
#elif defined(TARGET_MIPS)
- if (snan_bit_is_one(status)) {
- /* Prefer sNaN over qNaN, in the a, b, c order. */
+ if (snan_bit_is_one(status)) {
+ rule = float_3nan_prop_s_abc;
+ } else {
+ rule = float_3nan_prop_s_cab;
+ }
+#elif defined(TARGET_LOONGARCH64)
+ rule = float_3nan_prop_s_cab;
+#elif defined(TARGET_PPC)
+ /*
+ * If fRA is a NaN return it; otherwise if fRB is a NaN return it;
+ * otherwise return fRC. Note that muladd on PPC is (fRA * fRC) + frB
+ */
+ rule = float_3nan_prop_acb;
+#elif defined(TARGET_S390X)
+ rule = float_3nan_prop_s_abc;
+#elif defined(TARGET_SPARC)
+ rule = float_3nan_prop_s_cba;
+#elif defined(TARGET_XTENSA)
+ if (status->use_first_nan) {
+ rule = float_3nan_prop_abc;
+ } else {
+ rule = float_3nan_prop_cba;
+ }
+#else
+ rule = float_3nan_prop_abc;
+#endif
+ }
+
+ switch (rule) {
+ case float_3nan_prop_s_abc:
if (is_snan(a_cls)) {
return 0;
} else if (is_snan(b_cls)) {
@@ -534,8 +552,7 @@ static int pickNaNMulAdd(FloatClass a_cls, FloatClass b_cls, FloatClass c_cls,
} else {
return 2;
}
- } else {
- /* Prefer sNaN over qNaN, in the c, a, b order. */
+ case float_3nan_prop_s_cab:
if (is_snan(c_cls)) {
return 2;
} else if (is_snan(a_cls)) {
@@ -549,68 +566,21 @@ static int pickNaNMulAdd(FloatClass a_cls, FloatClass b_cls, FloatClass c_cls,
} else {
return 1;
}
- }
-#elif defined(TARGET_LOONGARCH64)
- /* Prefer sNaN over qNaN, in the c, a, b order. */
- if (is_snan(c_cls)) {
- return 2;
- } else if (is_snan(a_cls)) {
- return 0;
- } else if (is_snan(b_cls)) {
- return 1;
- } else if (is_qnan(c_cls)) {
- return 2;
- } else if (is_qnan(a_cls)) {
- return 0;
- } else {
- return 1;
- }
-#elif defined(TARGET_PPC)
- /* If fRA is a NaN return it; otherwise if fRB is a NaN return it;
- * otherwise return fRC. Note that muladd on PPC is (fRA * fRC) + frB
- */
- if (is_nan(a_cls)) {
- return 0;
- } else if (is_nan(c_cls)) {
- return 2;
- } else {
- return 1;
- }
-#elif defined(TARGET_S390X)
- if (is_snan(a_cls)) {
- return 0;
- } else if (is_snan(b_cls)) {
- return 1;
- } else if (is_snan(c_cls)) {
- return 2;
- } else if (is_qnan(a_cls)) {
- return 0;
- } else if (is_qnan(b_cls)) {
- return 1;
- } else {
- return 2;
- }
-#elif defined(TARGET_SPARC)
- /* Prefer SNaN over QNaN, order C, B, A. */
- if (is_snan(c_cls)) {
- return 2;
- } else if (is_snan(b_cls)) {
- return 1;
- } else if (is_snan(a_cls)) {
- return 0;
- } else if (is_qnan(c_cls)) {
- return 2;
- } else if (is_qnan(b_cls)) {
- return 1;
- } else {
- return 0;
- }
-#elif defined(TARGET_XTENSA)
- /*
- * For Xtensa, the (inf,zero,nan) case sets InvalidOp and returns
- * an input NaN if we have one (ie c).
- */
- if (status->use_first_nan) {
+ case float_3nan_prop_s_cba:
+ if (is_snan(c_cls)) {
+ return 2;
+ } else if (is_snan(b_cls)) {
+ return 1;
+ } else if (is_snan(a_cls)) {
+ return 0;
+ } else if (is_qnan(c_cls)) {
+ return 2;
+ } else if (is_qnan(b_cls)) {
+ return 1;
+ } else {
+ return 0;
+ }
+ case float_3nan_prop_abc:
if (is_nan(a_cls)) {
return 0;
} else if (is_nan(b_cls)) {
@@ -618,7 +588,15 @@ static int pickNaNMulAdd(FloatClass a_cls, FloatClass b_cls, FloatClass c_cls,
} else {
return 2;
}
- } else {
+ case float_3nan_prop_acb:
+ if (is_nan(a_cls)) {
+ return 0;
+ } else if (is_nan(c_cls)) {
+ return 2;
+ } else {
+ return 1;
+ }
+ case float_3nan_prop_cba:
if (is_nan(c_cls)) {
return 2;
} else if (is_nan(b_cls)) {
@@ -626,19 +604,9 @@ static int pickNaNMulAdd(FloatClass a_cls, FloatClass b_cls, FloatClass c_cls,
} else {
return 0;
}
+ default:
+ g_assert_not_reached();
}
-#else
- /* A default implementation: prefer a to b to c.
- * This is unlikely to actually match any real implementation.
- */
- if (is_nan(a_cls)) {
- return 0;
- } else if (is_nan(b_cls)) {
- return 1;
- } else {
- return 2;
- }
-#endif
}
/*----------------------------------------------------------------------------
IEEE 758 does not define a fixed rule for which NaN to pick as the result if both operands of a 3-operand fused multiply-add 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 pickNaNMulAdd() 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 In this commit we add an enum for the propagation rule, the field in float_status, and the corresponding getters and setters. We change pickNaNMulAdd 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 pickNaNMulAdd() catch this case and skip calling it. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- include/fpu/softfloat-helpers.h | 11 +++ include/fpu/softfloat-types.h | 37 ++++++++ fpu/softfloat-specialize.c.inc | 160 +++++++++++++------------------- 3 files changed, 112 insertions(+), 96 deletions(-)