This commit provides the implementation defined behavior flags and the basic
operation support for the OCP float8 data types(E4M3 & E5M2).
Signed-off-by: Max Chou <max.chou@sifive.com>
---
fpu/softfloat-specialize.c.inc | 57 ++++++++++++++++++++++++++-
include/fpu/softfloat-helpers.h | 20 ++++++++++
include/fpu/softfloat-types.h | 23 +++++++++++
include/fpu/softfloat.h | 70 +++++++++++++++++++++++++++++++++
4 files changed, 169 insertions(+), 1 deletion(-)
diff --git a/fpu/softfloat-specialize.c.inc b/fpu/softfloat-specialize.c.inc
index ba4fa08b7b..3a3bcd22ae 100644
--- a/fpu/softfloat-specialize.c.inc
+++ b/fpu/softfloat-specialize.c.inc
@@ -226,6 +226,30 @@ floatx80 floatx80_default_inf(bool zSign, float_status *status)
return packFloatx80(zSign, 0x7fff, z ? 0 : (1ULL << 63));
}
+/*----------------------------------------------------------------------------
+| Returns 1 if the OCP(Open Compute Platform) FP8 value `a' is a quiet NaN;
+| otherwise returns 0.
+*----------------------------------------------------------------------------*/
+
+bool float8_e4m3_is_quiet_nan(float8_e4m3 a_, float_status *status)
+{
+ return float8_e4m3_is_any_nan(a_);
+}
+
+bool float8_e5m2_is_quiet_nan(float8_e5m2 a_, float_status *status)
+{
+ if (no_signaling_nans(status) || status->ocp_fp8e5m2_no_signal_nan) {
+ return float8_e5m2_is_any_nan(a_);
+ } else {
+ uint8_t a = float8_e5m2_val(a_);
+ if (snan_bit_is_one(status)) {
+ return (((a >> 1) & 0x3F) == 0x3E) && (a & 0x1);
+ } else {
+ return ((a >> 1) & 0x3F) == 0x3F;
+ }
+ }
+}
+
/*----------------------------------------------------------------------------
| Returns 1 if the half-precision floating-point value `a' is a quiet
| NaN; otherwise returns 0.
@@ -240,7 +264,6 @@ bool float16_is_quiet_nan(float16 a_, float_status *status)
if (snan_bit_is_one(status)) {
return (((a >> 9) & 0x3F) == 0x3E) && (a & 0x1FF);
} else {
-
return ((a >> 9) & 0x3F) == 0x3F;
}
}
@@ -265,6 +288,38 @@ bool bfloat16_is_quiet_nan(bfloat16 a_, float_status *status)
}
}
+/*----------------------------------------------------------------------------
+| Returns 1 if the OCP(Open Compute Platform) FP8 value `a' is a signaling NaN;
+| otherwise returns 0.
+*----------------------------------------------------------------------------*/
+
+bool float8_e4m3_is_signaling_nan(float8_e4m3 a_, float_status *status)
+{
+ if (no_signaling_nans(status)) {
+ return false;
+ } else {
+ if (snan_bit_is_one(status)) {
+ return float8_e4m3_is_any_nan(a_);
+ } else {
+ return false;
+ }
+ }
+}
+
+bool float8_e5m2_is_signaling_nan(float8_e5m2 a_, float_status *status)
+{
+ if (no_signaling_nans(status)) {
+ return false;
+ } else {
+ uint8_t a = float8_e5m2_val(a_);
+ if (snan_bit_is_one(status)) {
+ return ((a >> 1) & 0x3F) == 0x3F;
+ } else {
+ return (((a >> 1) & 0x3F) == 0x3E && (a & 0x1));
+ }
+ }
+}
+
/*----------------------------------------------------------------------------
| Returns 1 if the half-precision floating-point value `a' is a signaling
| NaN; otherwise returns 0.
diff --git a/include/fpu/softfloat-helpers.h b/include/fpu/softfloat-helpers.h
index 90862f5cd2..4e278a3ee3 100644
--- a/include/fpu/softfloat-helpers.h
+++ b/include/fpu/softfloat-helpers.h
@@ -136,6 +136,26 @@ static inline void set_no_signaling_nans(bool val, float_status *status)
status->no_signaling_nans = val;
}
+static inline void set_ocp_fp8e5m2_no_signal_nan(bool val, float_status *status)
+{
+ status->ocp_fp8e5m2_no_signal_nan = val;
+}
+
+static inline bool get_ocp_fp8e5m2_no_signal_nan(const float_status *status)
+{
+ return status->ocp_fp8e5m2_no_signal_nan;
+}
+
+static inline void set_ocp_fp8_same_canonical_nan(bool val, float_status *status)
+{
+ status->ocp_fp8_same_canonical_nan = val;
+}
+
+static inline bool get_ocp_fp8_same_canonical_nan(const float_status *status)
+{
+ return status->ocp_fp8_same_canonical_nan;
+}
+
static inline bool get_float_detect_tininess(const float_status *status)
{
return status->tininess_before_rounding;
diff --git a/include/fpu/softfloat-types.h b/include/fpu/softfloat-types.h
index 8f82fdfc97..835dd33bf1 100644
--- a/include/fpu/softfloat-types.h
+++ b/include/fpu/softfloat-types.h
@@ -119,6 +119,18 @@ typedef struct {
*/
typedef uint16_t bfloat16;
+/*
+ * Software OCP(Open Compute Project) 8-bit floating point types
+ */
+typedef uint8_t float8_e4m3;
+typedef uint8_t float8_e5m2;
+#define float8_e4m3_val(x) (x)
+#define float8_e5m2_val(x) (x)
+#define make_float8_e4m3(x) (x)
+#define make_float8_e5m2(x) (x)
+#define const_float8_e4m3(x) (x)
+#define const_float8_e5m2(x) (x)
+
/*
* Software IEC/IEEE floating-point underflow tininess-detection mode.
*/
@@ -410,6 +422,17 @@ typedef struct float_status {
*/
bool snan_bit_is_one;
bool no_signaling_nans;
+ /*
+ * When true, OCP FP8 E5M2 format does not generate signaling NaNs.
+ * RISC-V uses only quiet NaNs in its OCP FP8 implementation.
+ */
+ bool ocp_fp8e5m2_no_signal_nan;
+ /*
+ * When true, OCP FP8 formats use the same canonical NaN representation
+ * (0x7F) for all NaN outputs. RISC-V specifies a single canonical NaN
+ * for both E4M3 and E5M2.
+ */
+ bool ocp_fp8_same_canonical_nan;
/* should overflowed results subtract re_bias to its exponent? */
bool rebias_overflow;
/* should underflowed results add re_bias to its exponent? */
diff --git a/include/fpu/softfloat.h b/include/fpu/softfloat.h
index c18ab2cb60..6f7259f9dd 100644
--- a/include/fpu/softfloat.h
+++ b/include/fpu/softfloat.h
@@ -189,6 +189,76 @@ float128 int128_to_float128(Int128, float_status *status);
float128 uint64_to_float128(uint64_t, float_status *status);
float128 uint128_to_float128(Int128, float_status *status);
+/*----------------------------------------------------------------------------
+| Software OCP FP8 operations.
+*----------------------------------------------------------------------------*/
+
+bool float8_e4m3_is_quiet_nan(float8_e4m3, float_status *status);
+bool float8_e4m3_is_signaling_nan(float8_e4m3, float_status *status);
+bool float8_e5m2_is_quiet_nan(float8_e5m2, float_status *status);
+bool float8_e5m2_is_signaling_nan(float8_e5m2, float_status *status);
+
+static inline bool float8_e4m3_is_any_nan(float8_e4m3 a)
+{
+ return ((float8_e4m3_val(a) & ~0x80) == 0x7f);
+}
+
+static inline bool float8_e5m2_is_any_nan(float8_e5m2 a)
+{
+ return ((float8_e5m2_val(a) & ~0x80) > 0x7c);
+}
+
+static inline bool float8_e4m3_is_neg(float8_e4m3 a)
+{
+ return float8_e4m3_val(a) >> 7;
+}
+
+static inline bool float8_e5m2_is_neg(float8_e5m2 a)
+{
+ return float8_e5m2_val(a) >> 7;
+}
+
+static inline bool float8_e4m3_is_infinity(float8_e4m3 a)
+{
+ return false;
+}
+
+static inline bool float8_e5m2_is_infinity(float8_e5m2 a)
+{
+ return (float8_e5m2_val(a) & 0x7f) == 0x7c;
+}
+
+static inline bool float8_e4m3_is_zero(float8_e4m3 a)
+{
+ return (float8_e4m3_val(a) & 0x7f) == 0;
+}
+
+static inline bool float8_e5m2_is_zero(float8_e5m2 a)
+{
+ return (float8_e5m2_val(a) & 0x7f) == 0;
+}
+
+static inline bool float8_e4m3_is_zero_or_denormal(float8_e4m3 a)
+{
+ return (float8_e4m3_val(a) & 0x78) == 0;
+}
+
+static inline bool float8_e5m2_is_zero_or_denormal(float8_e5m2 a)
+{
+ return (float8_e5m2_val(a) & 0x7c) == 0;
+}
+
+static inline bool float8_e4m3_is_normal(float8_e4m3 a)
+{
+ uint8_t em = float8_e4m3_val(a) & 0x7f;
+ return em >= 0x8 && em <= 0x7e;
+}
+
+static inline bool float8_e5m2_is_normal(float8_e5m2 a)
+{
+ return (((float8_e5m2_val(a) >> 2) + 1) & 0x1f) >= 2;
+}
+
/*----------------------------------------------------------------------------
| Software half-precision conversion routines.
*----------------------------------------------------------------------------*/
--
2.43.7
On 1/9/26 02:16, Max Chou wrote:
> +bool float8_e4m3_is_quiet_nan(float8_e4m3 a_, float_status *status)
> +{
> + return float8_e4m3_is_any_nan(a_);
> +}
> +
> +bool float8_e5m2_is_quiet_nan(float8_e5m2 a_, float_status *status)
> +{
> + if (no_signaling_nans(status) || status->ocp_fp8e5m2_no_signal_nan) {
What is this new thing?
> + return float8_e5m2_is_any_nan(a_);
> + } else {
> + uint8_t a = float8_e5m2_val(a_);
> + if (snan_bit_is_one(status)) {
> + return (((a >> 1) & 0x3F) == 0x3E) && (a & 0x1);
> + } else {
> + return ((a >> 1) & 0x3F) == 0x3F;
> + }
> + }
> +}
...
> +bool float8_e4m3_is_signaling_nan(float8_e4m3 a_, float_status *status)
> +{
> + if (no_signaling_nans(status)) {
> + return false;
> + } else {
> + if (snan_bit_is_one(status)) {
> + return float8_e4m3_is_any_nan(a_);
> + } else {
> + return false;
> + }
> + }
> +}
> +
> +bool float8_e5m2_is_signaling_nan(float8_e5m2 a_, float_status *status)
> +{
> + if (no_signaling_nans(status)) {
... which is not also reflected here?
> + return false;
> + } else {
> + uint8_t a = float8_e5m2_val(a_);
> + if (snan_bit_is_one(status)) {
> + return ((a >> 1) & 0x3F) == 0x3F;
> + } else {
> + return (((a >> 1) & 0x3F) == 0x3E && (a & 0x1));
> + }
> + }
> +}
(0) We really should clean up this code so that there's not so much duplication.
FOO_is_quiet_nan and FOO_is_signaling_nan really should share code.
That would have caught the above.
(1) RISC-V always uses default nan mode, the OCP spec declines to define SNaN vs QNaN,
leaving the 8 unique NaN encodings unspecified, and RISC-V does not do so either. You
assert later:
+ * RISC-V uses only quiet NaNs in its OCP FP8 implementation.
Is this out-of-band discussion with engineers?
Because it's missing from the (remarkably short) document.
(2) Arm does specify (see FP8Unpack in the ARM pseudocode), doing the usual thing in
taking the msb of the mantissa for SNaN. Which means that E4M3 is *always* SNaN.
Both architectures then immediately convert to FP16 default nan, however Arm *does* raise
invalid operand exception for the SNaN, so we can't just ignore it.
Given that there's exactly one RISC-V instruction for which this matters,
vfwcvtbf16.f.f.v, it seems like it might be better to simply adjust
float_status.no_signaling_nans within the helper rather than introduce
ocp_fp8e5m2_no_signal_nan.
> + /*
> + * When true, OCP FP8 formats use the same canonical NaN representation
> + * (0x7F) for all NaN outputs. RISC-V specifies a single canonical NaN
> + * for both E4M3 and E5M2.
> + */
> + bool ocp_fp8_same_canonical_nan;
Similarly you could adjust the canonical nan around the 4 FP16->FP8 conversion insn helpers:
/* Default NaN value: sign bit clear, all frac bits set */
set_float_default_nan_pattern(0b01111111, &env->fp_status);
In either case, "bool" doesn't seem appropriate.
FWIW, Arm retains the msb set pattern as for all other fp formats (FP8DefaultNaN).
r~
On 2026-01-10 13:57, Richard Henderson wrote: > (0) We really should clean up this code so that there's not so much duplication. > FOO_is_quiet_nan and FOO_is_signaling_nan really should share code. > That would have caught the above. > Thanks for the suggestion and I think that maybe we can remove the FOO_is_[quiet|signaling]_nan functions here. These OCP FP8 nan checkings should be different implemntation defined behaviors. > (1) RISC-V always uses default nan mode, the OCP spec declines to define > SNaN vs QNaN, leaving the 8 unique NaN encodings unspecified, and RISC-V > does not do so either. You assert later: > > + * RISC-V uses only quiet NaNs in its OCP FP8 implementation. > > Is this out-of-band discussion with engineers? > Because it's missing from the (remarkably short) document. > The RISC-V Zvfofp8min extension specification (v0.2.1) explicitly states the NaN handling behavior for OFP8 conversions: 1. Canonical NaN Definition (Section: Zvfofp8min): "The canonical NaN for both E4M3 and E5M2 is 0x7f." 2. Widening Conversion Behavior (vfwcvtbf16.f.f.v instruction): "No rounding occurs, and no floating-point exception flags are set." The specification's explicit statement that "no floating-point exception flags are set" for vfwcvtbf16.f.f.v provides clear justification for treating all OFP8 NaNs as quiet in this specific context. 3. Narrowing Conversion Behavior (vfncvtbf16.f.f.w instruction): "Since E4M3 cannot represent infinity, infinite results are converted to the canonical NaN, 0x7f." This demonstrates that RISC-V uses quiet NaN propagation semantics throughout the OFP8 conversion pipeline. > (2) Arm does specify (see FP8Unpack in the ARM pseudocode), doing the usual > thing in taking the msb of the mantissa for SNaN. Which means that E4M3 is > *always* SNaN. > > Both architectures then immediately convert to FP16 default nan, however Arm > *does* raise invalid operand exception for the SNaN, so we can't just ignore > it. > > Given that there's exactly one RISC-V instruction for which this matters, > vfwcvtbf16.f.f.v, it seems like it might be better to simply adjust > float_status.no_signaling_nans within the helper rather than introduce > ocp_fp8e5m2_no_signal_nan. > > > + /* > > + * When true, OCP FP8 formats use the same canonical NaN representation > > + * (0x7F) for all NaN outputs. RISC-V specifies a single canonical NaN > > + * for both E4M3 and E5M2. > > + */ > > + bool ocp_fp8_same_canonical_nan; > > Similarly you could adjust the canonical nan around the 4 FP16->FP8 conversion insn helpers: > > /* Default NaN value: sign bit clear, all frac bits set */ > set_float_default_nan_pattern(0b01111111, &env->fp_status); > > In either case, "bool" doesn't seem appropriate. > > FWIW, Arm retains the msb set pattern as for all other fp formats (FP8DefaultNaN). > > > r~ Thank you for the review feedbacks and suggestions. The suggestion to handle the canonical nan and quiet nan within the helper function rather than adding global state to float_status is the cleaner solution. I will incorporate this change in v2 of the patchset. Thanks a lot, rnax
Hi, Max:
On 1/8/2026 11:16 PM, Max Chou wrote:
> This commit provides the implementation defined behavior flags and the basic
> operation support for the OCP float8 data types(E4M3 & E5M2).
>
> Signed-off-by: Max Chou <max.chou@sifive.com>
> ---
> fpu/softfloat-specialize.c.inc | 57 ++++++++++++++++++++++++++-
> include/fpu/softfloat-helpers.h | 20 ++++++++++
> include/fpu/softfloat-types.h | 23 +++++++++++
> include/fpu/softfloat.h | 70 +++++++++++++++++++++++++++++++++
> 4 files changed, 169 insertions(+), 1 deletion(-)
>
> diff --git a/fpu/softfloat-specialize.c.inc b/fpu/softfloat-specialize.c.inc
> index ba4fa08b7b..3a3bcd22ae 100644
> --- a/fpu/softfloat-specialize.c.inc
> +++ b/fpu/softfloat-specialize.c.inc
> @@ -226,6 +226,30 @@ floatx80 floatx80_default_inf(bool zSign, float_status *status)
> return packFloatx80(zSign, 0x7fff, z ? 0 : (1ULL << 63));
> }
>
> +/*----------------------------------------------------------------------------
> +| Returns 1 if the OCP(Open Compute Platform) FP8 value `a' is a quiet NaN;
Open Compute Platform -> Open Compute Project
> +| otherwise returns 0.
> +*----------------------------------------------------------------------------*/
> +
> +bool float8_e4m3_is_quiet_nan(float8_e4m3 a_, float_status *status)
> +{
> + return float8_e4m3_is_any_nan(a_);
> +}
> +
...
> +/*----------------------------------------------------------------------------
> +| Returns 1 if the OCP(Open Compute Platform) FP8 value `a' is a signaling NaN;
Open Compute Platform -> Open Compute Project
Thanks,
Chao
> +| otherwise returns 0.
> +*----------------------------------------------------------------------------*/
> +
> +bool float8_e4m3_is_signaling_nan(float8_e4m3 a_, float_status *status)
> +{
> + if (no_signaling_nans(status)) {
> + return false;
> + } else {
> + if (snan_bit_is_one(status)) {
> + return float8_e4m3_is_any_nan(a_);
> + } else {
> + return false;
> + }
> + }
> +}
> +
> +bool float8_e5m2_is_signaling_nan(float8_e5m2 a_, float_status *status)
> +{
> + if (no_signaling_nans(status)) {
> + return false;
> + } else {
> + uint8_t a = float8_e5m2_val(a_);
> + if (snan_bit_is_one(status)) {
> + return ((a >> 1) & 0x3F) == 0x3F;
> + } else {
> + return (((a >> 1) & 0x3F) == 0x3E && (a & 0x1));
> + }
> + }
> +}
> +
> /*----------------------------------------------------------------------------
> | Returns 1 if the half-precision floating-point value `a' is a signaling
> | NaN; otherwise returns 0.
> diff --git a/include/fpu/softfloat-helpers.h b/include/fpu/softfloat-helpers.h
> index 90862f5cd2..4e278a3ee3 100644
> --- a/include/fpu/softfloat-helpers.h
> +++ b/include/fpu/softfloat-helpers.h
> @@ -136,6 +136,26 @@ static inline void set_no_signaling_nans(bool val, float_status *status)
> status->no_signaling_nans = val;
> }
>
> +static inline void set_ocp_fp8e5m2_no_signal_nan(bool val, float_status *status)
> +{
> + status->ocp_fp8e5m2_no_signal_nan = val;
> +}
> +
> +static inline bool get_ocp_fp8e5m2_no_signal_nan(const float_status *status)
> +{
> + return status->ocp_fp8e5m2_no_signal_nan;
> +}
> +
> +static inline void set_ocp_fp8_same_canonical_nan(bool val, float_status *status)
> +{
> + status->ocp_fp8_same_canonical_nan = val;
> +}
> +
> +static inline bool get_ocp_fp8_same_canonical_nan(const float_status *status)
> +{
> + return status->ocp_fp8_same_canonical_nan;
> +}
> +
> static inline bool get_float_detect_tininess(const float_status *status)
> {
> return status->tininess_before_rounding;
> diff --git a/include/fpu/softfloat-types.h b/include/fpu/softfloat-types.h
> index 8f82fdfc97..835dd33bf1 100644
> --- a/include/fpu/softfloat-types.h
> +++ b/include/fpu/softfloat-types.h
> @@ -119,6 +119,18 @@ typedef struct {
> */
> typedef uint16_t bfloat16;
>
> +/*
> + * Software OCP(Open Compute Project) 8-bit floating point types
> + */
> +typedef uint8_t float8_e4m3;
> +typedef uint8_t float8_e5m2;
> +#define float8_e4m3_val(x) (x)
> +#define float8_e5m2_val(x) (x)
> +#define make_float8_e4m3(x) (x)
> +#define make_float8_e5m2(x) (x)
> +#define const_float8_e4m3(x) (x)
> +#define const_float8_e5m2(x) (x)
> +
> /*
> * Software IEC/IEEE floating-point underflow tininess-detection mode.
> */
> @@ -410,6 +422,17 @@ typedef struct float_status {
> */
> bool snan_bit_is_one;
> bool no_signaling_nans;
> + /*
> + * When true, OCP FP8 E5M2 format does not generate signaling NaNs.
> + * RISC-V uses only quiet NaNs in its OCP FP8 implementation.
> + */
> + bool ocp_fp8e5m2_no_signal_nan;
> + /*
> + * When true, OCP FP8 formats use the same canonical NaN representation
> + * (0x7F) for all NaN outputs. RISC-V specifies a single canonical NaN
> + * for both E4M3 and E5M2.
> + */
> + bool ocp_fp8_same_canonical_nan;
> /* should overflowed results subtract re_bias to its exponent? */
> bool rebias_overflow;
> /* should underflowed results add re_bias to its exponent? */
> diff --git a/include/fpu/softfloat.h b/include/fpu/softfloat.h
> index c18ab2cb60..6f7259f9dd 100644
> --- a/include/fpu/softfloat.h
> +++ b/include/fpu/softfloat.h
> @@ -189,6 +189,76 @@ float128 int128_to_float128(Int128, float_status *status);
> float128 uint64_to_float128(uint64_t, float_status *status);
> float128 uint128_to_float128(Int128, float_status *status);
>
> +/*----------------------------------------------------------------------------
> +| Software OCP FP8 operations.
> +*----------------------------------------------------------------------------*/
> +
> +bool float8_e4m3_is_quiet_nan(float8_e4m3, float_status *status);
> +bool float8_e4m3_is_signaling_nan(float8_e4m3, float_status *status);
> +bool float8_e5m2_is_quiet_nan(float8_e5m2, float_status *status);
> +bool float8_e5m2_is_signaling_nan(float8_e5m2, float_status *status);
> +
> +static inline bool float8_e4m3_is_any_nan(float8_e4m3 a)
> +{
> + return ((float8_e4m3_val(a) & ~0x80) == 0x7f);
> +}
> +
> +static inline bool float8_e5m2_is_any_nan(float8_e5m2 a)
> +{
> + return ((float8_e5m2_val(a) & ~0x80) > 0x7c);
> +}
> +
> +static inline bool float8_e4m3_is_neg(float8_e4m3 a)
> +{
> + return float8_e4m3_val(a) >> 7;
> +}
> +
> +static inline bool float8_e5m2_is_neg(float8_e5m2 a)
> +{
> + return float8_e5m2_val(a) >> 7;
> +}
> +
> +static inline bool float8_e4m3_is_infinity(float8_e4m3 a)
> +{
> + return false;
> +}
> +
> +static inline bool float8_e5m2_is_infinity(float8_e5m2 a)
> +{
> + return (float8_e5m2_val(a) & 0x7f) == 0x7c;
> +}
> +
> +static inline bool float8_e4m3_is_zero(float8_e4m3 a)
> +{
> + return (float8_e4m3_val(a) & 0x7f) == 0;
> +}
> +
> +static inline bool float8_e5m2_is_zero(float8_e5m2 a)
> +{
> + return (float8_e5m2_val(a) & 0x7f) == 0;
> +}
> +
> +static inline bool float8_e4m3_is_zero_or_denormal(float8_e4m3 a)
> +{
> + return (float8_e4m3_val(a) & 0x78) == 0;
> +}
> +
> +static inline bool float8_e5m2_is_zero_or_denormal(float8_e5m2 a)
> +{
> + return (float8_e5m2_val(a) & 0x7c) == 0;
> +}
> +
> +static inline bool float8_e4m3_is_normal(float8_e4m3 a)
> +{
> + uint8_t em = float8_e4m3_val(a) & 0x7f;
> + return em >= 0x8 && em <= 0x7e;
> +}
> +
> +static inline bool float8_e5m2_is_normal(float8_e5m2 a)
> +{
> + return (((float8_e5m2_val(a) >> 2) + 1) & 0x1f) >= 2;
> +}
> +
> /*----------------------------------------------------------------------------
> | Software half-precision conversion routines.
> *----------------------------------------------------------------------------*/
© 2016 - 2026 Red Hat, Inc.