LibC: Start on fenv support for RISC-V

Co-Authored-By: Sönke Holz <sholz8530@gmail.com>
This commit is contained in:
kleines Filmröllchen 2024-02-27 22:27:57 +01:00 committed by Andrew Kaster
parent a4a8dd29ba
commit 24af4f1882
7 changed files with 286 additions and 55 deletions

View file

@ -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;
}

View file

@ -5,9 +5,166 @@
*/
#include <AK/Assertions.h>
#include <AK/EnumBits.h>
#include <AK/StdLibExtras.h>
#include <AK/Types.h>
#include <fenv.h>
static_assert(AssertSize<fenv_t, 4>());
// 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 instructions 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<RoundingMode>(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<RoundingMode>(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<AccruedExceptions>(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<AccruedExceptions>(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(&current_env);
(void)env;
TODO_RISCV64();
fesetenv(&current_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(&current_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(&current_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(&current_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(&current_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;
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <stdint.h>
#include <sys/cdefs.h>
#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

View file

@ -7,6 +7,9 @@
#include <AK/Types.h>
#include <fenv.h>
// 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);

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2024, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <stdint.h>
#include <sys/cdefs.h>
#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

View file

@ -7,9 +7,6 @@
#include <AK/Types.h>
#include <fenv.h>
// 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)

View file

@ -9,29 +9,22 @@
#include <stdint.h>
#include <sys/cdefs.h>
__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 <arch/x86_64/fenv.h>
#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 <arch/riscv64/fenv.h>
#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);