diff --git a/Userland/Libraries/LibC/arch/aarch64/fenv.cpp b/Userland/Libraries/LibC/arch/aarch64/fenv.cpp index d9ef2de799c..c85a56ff7db 100644 --- a/Userland/Libraries/LibC/arch/aarch64/fenv.cpp +++ b/Userland/Libraries/LibC/arch/aarch64/fenv.cpp @@ -69,9 +69,12 @@ int fegetround() int fesetround(int rounding_mode) { - if (rounding_mode < FE_TONEAREST || rounding_mode > FE_TOWARDZERO) + if (rounding_mode < FE_TONEAREST || rounding_mode > FE_TOMAXMAGNITUDE) return 1; + if (rounding_mode == FE_TOMAXMAGNITUDE) + rounding_mode = FE_TONEAREST; + TODO_AARCH64(); return 0; } diff --git a/Userland/Libraries/LibC/arch/riscv64/fenv.cpp b/Userland/Libraries/LibC/arch/riscv64/fenv.cpp index 3aedf717eb0..68e7bcea02d 100644 --- a/Userland/Libraries/LibC/arch/riscv64/fenv.cpp +++ b/Userland/Libraries/LibC/arch/riscv64/fenv.cpp @@ -5,9 +5,166 @@ */ #include +#include +#include #include #include +static_assert(AssertSize()); + +// RISC-V F extension version 2.2 +// Table 11.1 (frm rounding mode encoding) +enum class RoundingMode : u8 { + // Round to Nearest, ties to Even + RNE = 0b000, + // Round towards Zero + RTZ = 0b001, + // Round Down (towards −∞) + RDN = 0b010, + // Round Up (towards +∞) + RUP = 0b011, + // Round to Nearest, ties to Max Magnitude + RMM = 0b100, + // Reserved for future use. + Reserved5 = 0b101, + Reserved6 = 0b110, + // In instruction’s rm field, selects dynamic rounding mode; In Rounding Mode register, Invalid. + DYN = 0b111, +}; + +static RoundingMode frm_from_feround(int c_rounding_mode) +{ + switch (c_rounding_mode) { + case FE_TONEAREST: + return RoundingMode::RNE; + case FE_TOWARDZERO: + return RoundingMode::RTZ; + case FE_DOWNWARD: + return RoundingMode::RDN; + case FE_UPWARD: + return RoundingMode::RUP; + case FE_TOMAXMAGNITUDE: + return RoundingMode::RMM; + default: + VERIFY_NOT_REACHED(); + } +} + +static int feround_from_frm(RoundingMode frm) +{ + switch (frm) { + case RoundingMode::RNE: + return FE_TONEAREST; + case RoundingMode::RTZ: + return FE_TOWARDZERO; + case RoundingMode::RDN: + return FE_DOWNWARD; + case RoundingMode::RUP: + return FE_UPWARD; + case RoundingMode::RMM: + return FE_TOMAXMAGNITUDE; + default: + // DYN is invalid in the frm register and therefore should never appear here. + case RoundingMode::DYN: + VERIFY_NOT_REACHED(); + } +} + +static RoundingMode get_rounding_mode() +{ + size_t rounding_mode; + asm volatile("frrm %0" + : "=r"(rounding_mode)); + return static_cast(rounding_mode); +} + +// Returns the old rounding mode, since we get that for free. +static RoundingMode set_rounding_mode(RoundingMode frm) +{ + size_t old_rounding_mode; + size_t const new_rounding_mode = to_underlying(frm); + asm volatile("fsrm %0, %1" + : "=r"(old_rounding_mode) + : "r"(new_rounding_mode)); + return static_cast(old_rounding_mode); +} + +// Figure 11.2 (fflags) +enum class AccruedExceptions : u8 { + None = 0, + // Inexact + NX = 1 << 0, + // Underflow + UF = 1 << 1, + // Overflow + OF = 1 << 2, + // Divide by Zero + DZ = 1 << 3, + // Invalid Operation + NV = 1 << 4, + All = NX | UF | OF | DZ | NV, +}; + +AK_ENUM_BITWISE_OPERATORS(AccruedExceptions); + +static AccruedExceptions fflags_from_fexcept(fexcept_t c_exceptions) +{ + AccruedExceptions exceptions = AccruedExceptions::None; + if ((c_exceptions & FE_INEXACT) != 0) + exceptions |= AccruedExceptions::NX; + if ((c_exceptions & FE_UNDERFLOW) != 0) + exceptions |= AccruedExceptions::UF; + if ((c_exceptions & FE_OVERFLOW) != 0) + exceptions |= AccruedExceptions::OF; + if ((c_exceptions & FE_DIVBYZERO) != 0) + exceptions |= AccruedExceptions::DZ; + if ((c_exceptions & FE_INVALID) != 0) + exceptions |= AccruedExceptions::NV; + + return exceptions; +} + +static fexcept_t fexcept_from_fflags(AccruedExceptions fflags) +{ + fexcept_t c_exceptions = 0; + if ((fflags & AccruedExceptions::NX) != AccruedExceptions::None) + c_exceptions |= FE_INEXACT; + if ((fflags & AccruedExceptions::UF) != AccruedExceptions::None) + c_exceptions |= FE_UNDERFLOW; + if ((fflags & AccruedExceptions::OF) != AccruedExceptions::None) + c_exceptions |= FE_OVERFLOW; + if ((fflags & AccruedExceptions::DZ) != AccruedExceptions::None) + c_exceptions |= FE_DIVBYZERO; + if ((fflags & AccruedExceptions::NV) != AccruedExceptions::None) + c_exceptions |= FE_INVALID; + + return c_exceptions; +} + +static AccruedExceptions get_accrued_exceptions() +{ + size_t fflags; + asm volatile("frflags %0" + : "=r"(fflags)); + return static_cast(fflags); +} + +// Returns the old exceptions, since we get them for free. +static AccruedExceptions set_accrued_exceptions(AccruedExceptions exceptions) +{ + size_t old_exceptions; + size_t const new_exceptions = to_underlying(exceptions); + asm volatile("fsflags %0, %1" + : "=r"(old_exceptions) + : "r"(new_exceptions)); + return static_cast(old_exceptions); +} + +static void clear_accrued_exceptions(AccruedExceptions exceptions) +{ + asm volatile("csrc fcsr, %0" ::"r"(to_underlying(exceptions))); +} + extern "C" { int fegetenv(fenv_t* env) @@ -15,8 +172,11 @@ int fegetenv(fenv_t* env) if (!env) return 1; - (void)env; - TODO_RISCV64(); + FlatPtr fcsr; + asm volatile("csrr %0, fcsr" + : "=r"(fcsr)); + env->fcsr = fcsr; + return 0; } @@ -25,8 +185,8 @@ int fesetenv(fenv_t const* env) if (!env) return 1; - (void)env; - TODO_RISCV64(); + FlatPtr fcsr = env->fcsr; + asm volatile("csrw fcsr, %0" ::"r"(fcsr)); return 0; } @@ -34,13 +194,8 @@ int feholdexcept(fenv_t* env) { fegetenv(env); - fenv_t current_env; - fegetenv(¤t_env); - - (void)env; - TODO_RISCV64(); - - fesetenv(¤t_env); + // RISC-V does not have trapping floating point exceptions. Therefore, feholdexcept just clears fflags. + clear_accrued_exceptions(AccruedExceptions::All); return 0; } @@ -49,30 +204,28 @@ int fesetexceptflag(fexcept_t const* except, int exceptions) if (!except) return 1; - fenv_t current_env; - fegetenv(¤t_env); - exceptions &= FE_ALL_EXCEPT; - (void)exceptions; - (void)except; - TODO_RISCV64(); + auto exceptions_to_set = fflags_from_fexcept(*except) & fflags_from_fexcept(exceptions); + set_accrued_exceptions(exceptions_to_set); - fesetenv(¤t_env); return 0; } int fegetround() { - TODO_RISCV64(); + auto rounding_mode = get_rounding_mode(); + return feround_from_frm(rounding_mode); } int fesetround(int rounding_mode) { - if (rounding_mode < FE_TONEAREST || rounding_mode > FE_TOWARDZERO) + if (rounding_mode < FE_TONEAREST || rounding_mode > FE_TOMAXMAGNITUDE) return 1; - TODO_RISCV64(); + auto frm = frm_from_feround(rounding_mode); + set_rounding_mode(frm); + return 0; } @@ -80,20 +233,19 @@ int feclearexcept(int exceptions) { exceptions &= FE_ALL_EXCEPT; - fenv_t current_env; - fegetenv(¤t_env); + auto exception_clear_flag = fflags_from_fexcept(exceptions); + // Use CSRRC to directly clear exception flags in fcsr which is faster. + // Conveniently, the exception flags are the lower bits, so we don't need to shift anything around. + clear_accrued_exceptions(exception_clear_flag); - (void)exceptions; - TODO_RISCV64(); - - fesetenv(¤t_env); return 0; } int fetestexcept(int exceptions) { - (void)exceptions; - TODO_RISCV64(); + auto fflags = get_accrued_exceptions(); + auto mask = fflags_from_fexcept(exceptions); + return fexcept_from_fflags(fflags & mask); } int feraiseexcept(int exceptions) @@ -103,7 +255,9 @@ int feraiseexcept(int exceptions) exceptions &= FE_ALL_EXCEPT; - (void)exceptions; - TODO_RISCV64(); + // RISC-V does not have trapping floating-point exceptions, so this function behaves as a simple exception setter. + set_accrued_exceptions(fflags_from_fexcept(exceptions)); + + return 0; } } diff --git a/Userland/Libraries/LibC/arch/riscv64/fenv.h b/Userland/Libraries/LibC/arch/riscv64/fenv.h new file mode 100644 index 00000000000..805a362d627 --- /dev/null +++ b/Userland/Libraries/LibC/arch/riscv64/fenv.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +#if !defined(__riscv) || __riscv_xlen != 64 +# error "This file should not be included on architectures other than riscv64." +#endif + +__BEGIN_DECLS + +// Chapter numbers from RISC-V Unprivileged ISA V20191213 +// RISC-V F extension version 2.2, Figure 11.1 +typedef struct fenv_t { + union { + // 11.2: fcsr is always 32 bits, even for the D and Q extensions, since only the lowest byte of data is in use. + uint32_t fcsr; + struct { + // Accrued exceptions (fflags). + uint8_t inexact : 1; // NX + uint8_t underflow : 1; // UF + uint8_t overflow : 1; // OF + uint8_t divide_by_zero : 1; // DZ + uint8_t invalid_operation : 1; // NV + uint8_t rounding_mode : 3; // frm + uint32_t reserved : 24; + }; + }; +} fenv_t; + +__END_DECLS diff --git a/Userland/Libraries/LibC/arch/x86_64/fenv.cpp b/Userland/Libraries/LibC/arch/x86_64/fenv.cpp index fb82e14fabd..55d70cf5c36 100644 --- a/Userland/Libraries/LibC/arch/x86_64/fenv.cpp +++ b/Userland/Libraries/LibC/arch/x86_64/fenv.cpp @@ -7,6 +7,9 @@ #include #include +// This is the size of the floating point environment image in protected mode +static_assert(sizeof(__x87_floating_point_environment) == 28); + static u16 read_status_register() { u16 status_register; @@ -114,9 +117,12 @@ int fegetround() int fesetround(int rounding_mode) { - if (rounding_mode < FE_TONEAREST || rounding_mode > FE_TOWARDZERO) + if (rounding_mode < FE_TONEAREST || rounding_mode > FE_TOMAXMAGNITUDE) return 1; + if (rounding_mode == FE_TOMAXMAGNITUDE) + rounding_mode = FE_TONEAREST; + auto control_word = read_control_word(); control_word &= ~(3 << 10); diff --git a/Userland/Libraries/LibC/arch/x86_64/fenv.h b/Userland/Libraries/LibC/arch/x86_64/fenv.h new file mode 100644 index 00000000000..68d67d7958b --- /dev/null +++ b/Userland/Libraries/LibC/arch/x86_64/fenv.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +#if !defined(__x86_64__) +# error "This file should not be included on architectures other than x86_64." +#endif + +__BEGIN_DECLS + +struct __x87_floating_point_environment { + uint16_t __control_word; + uint16_t __reserved1; + uint16_t __status_word; + uint16_t __reserved2; + uint16_t __tag_word; + uint16_t __reserved3; + uint32_t __fpu_ip_offset; + uint16_t __fpu_ip_selector; + uint16_t __opcode : 11; + uint16_t __reserved4 : 5; + uint32_t __fpu_data_offset; + uint16_t __fpu_data_selector; + uint16_t __reserved5; +}; + +typedef struct fenv_t { + struct __x87_floating_point_environment __x87_fpu_env; + uint32_t __mxcsr; +} fenv_t; + +__END_DECLS diff --git a/Userland/Libraries/LibC/fenv.cpp b/Userland/Libraries/LibC/fenv.cpp index 12e7782b362..d3906f5086b 100644 --- a/Userland/Libraries/LibC/fenv.cpp +++ b/Userland/Libraries/LibC/fenv.cpp @@ -7,9 +7,6 @@ #include #include -// This is the size of the floating point environment image in protected mode -static_assert(sizeof(__x87_floating_point_environment) == 28); - extern "C" { int feupdateenv(fenv_t const* env) diff --git a/Userland/Libraries/LibC/fenv.h b/Userland/Libraries/LibC/fenv.h index f5f1d8f7e63..34b14ddfa8f 100644 --- a/Userland/Libraries/LibC/fenv.h +++ b/Userland/Libraries/LibC/fenv.h @@ -9,29 +9,22 @@ #include #include -__BEGIN_DECLS - -struct __x87_floating_point_environment { - uint16_t __control_word; - uint16_t __reserved1; - uint16_t __status_word; - uint16_t __reserved2; - uint16_t __tag_word; - uint16_t __reserved3; - uint32_t __fpu_ip_offset; - uint16_t __fpu_ip_selector; - uint16_t __opcode : 11; - uint16_t __reserved4 : 5; - uint32_t __fpu_data_offset; - uint16_t __fpu_data_selector; - uint16_t __reserved5; -}; +#if defined(__x86_64__) +# include +#elif defined(__aarch64__) +// TODO: Implement this. typedef struct fenv_t { - struct __x87_floating_point_environment __x87_fpu_env; - uint32_t __mxcsr; } fenv_t; +#elif defined(__riscv) && __riscv_xlen == 64 +# include +#else +# error "Unknown architecture" +#endif + +__BEGIN_DECLS + #define FE_DFL_ENV ((fenv_t const*)-1) int fegetenv(fenv_t*); @@ -58,6 +51,8 @@ int feraiseexcept(int exceptions); #define FE_DOWNWARD 1 #define FE_UPWARD 2 #define FE_TOWARDZERO 3 +// Only exists in RISC-V at the moment; on other architectures this is replaced with FE_TONEAREST. +#define FE_TOMAXMAGNITUDE 4 int fesetround(int round); int fegetround(void);