mirror of
https://github.com/SerenityOS/serenity
synced 2024-07-21 18:15:58 +00:00
LibC: Start on fenv support for RISC-V
Co-Authored-By: Sönke Holz <sholz8530@gmail.com>
This commit is contained in:
parent
a4a8dd29ba
commit
24af4f1882
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 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<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(¤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;
|
||||
}
|
||||
}
|
||||
|
|
37
Userland/Libraries/LibC/arch/riscv64/fenv.h
Normal file
37
Userland/Libraries/LibC/arch/riscv64/fenv.h
Normal 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
|
|
@ -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);
|
||||
|
|
39
Userland/Libraries/LibC/arch/x86_64/fenv.h
Normal file
39
Userland/Libraries/LibC/arch/x86_64/fenv.h
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue