Source code
Revision control
Copy as Markdown
Other Tools
// Copyright 2024 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "jit/riscv64/extension/extension-riscv-zfa.h"
#include "mozilla/Casting.h"
#include <algorithm>
#include <cmath>
#include "jit/riscv64/base/Integer.h"
namespace js::jit {
// =============================================================================
// Single-Precision Zfa Instructions
// =============================================================================
void AssemblerRISCVZfa::fli_s(FPURegister rd, uint8_t imm5) {
// fli.s: funct7=0b1111000, funct3=0, rs2=1, rs1=imm5
MOZ_ASSERT(is_uint5(imm5));
GenInstrALUFP_rr(0b1111000, 0b000, rd, Register::FromCode(imm5),
Register::FromCode(1));
}
void AssemblerRISCVZfa::fminm_s(FPURegister rd, FPURegister rs1,
FPURegister rs2) {
// fminm.s: funct7=0b0010100, funct3=2
GenInstrALUFP_rr(0b0010100, 0b010, rd, rs1, rs2);
}
void AssemblerRISCVZfa::fmaxm_s(FPURegister rd, FPURegister rs1,
FPURegister rs2) {
// fmaxm.s: funct7=0b0010100, funct3=3
GenInstrALUFP_rr(0b0010100, 0b011, rd, rs1, rs2);
}
void AssemblerRISCVZfa::fround_s(FPURegister rd, FPURegister rs1,
FPURoundingMode frm) {
// fround.s: funct7=0b0100000, funct3=rm, rs2=4
GenInstrALUFP_rr(0b0100000, frm, rd, rs1, Register::FromCode(4));
}
void AssemblerRISCVZfa::froundnx_s(FPURegister rd, FPURegister rs1,
FPURoundingMode frm) {
// froundnx.s: funct7=0b0100000, funct3=rm, rs2=5
GenInstrALUFP_rr(0b0100000, frm, rd, rs1, Register::FromCode(5));
}
void AssemblerRISCVZfa::fleq_s(Register rd, FPURegister rs1, FPURegister rs2) {
// fleq.s: funct7=0b1010000, funct3=4
GenInstrALUFP_rr(0b1010000, 0b100, rd, rs1, rs2);
}
void AssemblerRISCVZfa::fltq_s(Register rd, FPURegister rs1, FPURegister rs2) {
// fltq.s: funct7=0b1010000, funct3=5
GenInstrALUFP_rr(0b1010000, 0b101, rd, rs1, rs2);
}
// =============================================================================
// Double-Precision Zfa Instructions
// =============================================================================
void AssemblerRISCVZfa::fli_d(FPURegister rd, uint8_t imm5) {
// fli.d: funct7=0b1111001, funct3=0, rs2=1, rs1=imm5
MOZ_ASSERT(is_uint5(imm5));
GenInstrALUFP_rr(0b1111001, 0b000, rd, Register::FromCode(imm5),
Register::FromCode(1));
}
void AssemblerRISCVZfa::fminm_d(FPURegister rd, FPURegister rs1,
FPURegister rs2) {
// fminm.d: funct7=0b0010101, funct3=2
GenInstrALUFP_rr(0b0010101, 0b010, rd, rs1, rs2);
}
void AssemblerRISCVZfa::fmaxm_d(FPURegister rd, FPURegister rs1,
FPURegister rs2) {
// fmaxm.d: funct7=0b0010101, funct3=3
GenInstrALUFP_rr(0b0010101, 0b011, rd, rs1, rs2);
}
void AssemblerRISCVZfa::fround_d(FPURegister rd, FPURegister rs1,
FPURoundingMode frm) {
// fround.d: funct7=0b0100001, funct3=rm, rs2=4
GenInstrALUFP_rr(0b0100001, frm, rd, rs1, Register::FromCode(4));
}
void AssemblerRISCVZfa::froundnx_d(FPURegister rd, FPURegister rs1,
FPURoundingMode frm) {
// froundnx.d: funct7=0b0100001, funct3=rm, rs2=5
GenInstrALUFP_rr(0b0100001, frm, rd, rs1, Register::FromCode(5));
}
void AssemblerRISCVZfa::fcvtmod_w_d(Register rd, FPURegister rs1) {
// fcvtmod.w.d: funct7=0b1100001, funct3=rm, rs2=8
GenInstrALUFP_rr(0b1100001, RTZ, rd, rs1, Register::FromCode(8));
}
void AssemblerRISCVZfa::fleq_d(Register rd, FPURegister rs1, FPURegister rs2) {
// fleq.d: funct7=0b1010001, funct3=4
GenInstrALUFP_rr(0b1010001, 0b100, rd, rs1, rs2);
}
void AssemblerRISCVZfa::fltq_d(Register rd, FPURegister rs1, FPURegister rs2) {
// fltq.d: funct7=0b1010001, funct3=5
GenInstrALUFP_rr(0b1010001, 0b101, rd, rs1, rs2);
}
// =============================================================================
// FLI (Floating-Point Load Immediate) Constants
// =============================================================================
// The FLI instruction loads one of 32 predefined floating-point constants.
// The constant is selected by a 5-bit immediate (imm5) in the rs1 field.
//
// imm5 encoding:
// 0: -1.0
// 1: minimum positive normal (FLT_MIN / DBL_MIN)
// 2: 1.0 × 2⁻¹⁶
// 3: 1.0 × 2⁻¹⁵
// ...
// 16: 1.0
// ...
// 30: +∞
// 31: Canonical NaN
// FLI.S immediate values lookup table (entries 2-29).
// Each entry is a 32-bit IEEE 754 single-precision representation.
// Index 0 corresponds to imm5=2, index 27 corresponds to imm5=29.
// The table is sorted for binary search.
static constexpr uint32_t kLoadFP32ImmArr[] = {
0x37800000, // imm5=2: 1.0×2⁻¹⁶ = 1.52588e-05
0x38000000, // imm5=3: 1.0×2⁻¹⁵ = 3.05176e-05
0x3B800000, // imm5=4: 1.0×2⁻⁸
0x3C000000, // imm5=5: 1.0×2⁻⁷
0x3D800000, // imm5=6: 0.0625 (2⁻⁴)
0x3E000000, // imm5=7: 0.125 (2⁻³)
0x3E800000, // imm5=8: 0.25
0x3EA00000, // imm5=9: 0.3125
0x3EC00000, // imm5=10: 0.375
0x3EE00000, // imm5=11: 0.4375
0x3F000000, // imm5=12: 0.5
0x3F200000, // imm5=13: 0.625
0x3F400000, // imm5=14: 0.75
0x3F600000, // imm5=15: 0.875
0x3F800000, // imm5=16: 1.0
0x3FA00000, // imm5=17: 1.25
0x3FC00000, // imm5=18: 1.5
0x3FE00000, // imm5=19: 1.75
0x40000000, // imm5=20: 2.0
0x40200000, // imm5=21: 2.5
0x40400000, // imm5=22: 3.0
0x40800000, // imm5=23: 4.0
0x41000000, // imm5=24: 8.0
0x41800000, // imm5=25: 16.0
0x43000000, // imm5=26: 128.0 (2⁷)
0x43800000, // imm5=27: 256.0 (2⁸)
0x47000000, // imm5=28: 2¹⁵ = 32768.0
0x47800000, // imm5=29: 2¹⁶ = 65536.0
};
static constexpr uint64_t kFP64DefaultNaN = 0x7FF8000000000000UL;
static constexpr uint32_t kFP32DefaultNaN = 0x7FC00000;
static constexpr uint32_t kFP32FltMin = 0x00800000;
static constexpr uint64_t kFP64DblMin = 0x0010000000000000ULL;
// Returns the single-precision floating-point value for the given FLI.S
// immediate encoding (imm5).
// Precondition: imm5 <= 31
float GetFLISValue(uint8_t imm5) {
MOZ_ASSERT(is_uint5(imm5));
// imm5=0: -1.0 (the only negative value)
if (imm5 == 0) {
return -1.0f;
}
// imm5=1: minimum positive normal (FLT_MIN)
if (imm5 == 1) {
return 1.17549435e-38f; // FLT_MIN = 2^(-126)
}
// imm5=30: +infinity
if (imm5 == 30) {
uint32_t bits = 0x7f800000; // exp=255, mantissa=0
return mozilla::BitwiseCast<float>(bits);
}
// imm5=31: Canonical NaN
if (imm5 == 31) {
return mozilla::BitwiseCast<float>(kFP32DefaultNaN);
}
// imm5=2..29: use lookup table
return mozilla::BitwiseCast<float>(kLoadFP32ImmArr[imm5 - 2]);
}
// Returns the double-precision floating-point value for the given FLI.D
// immediate encoding (imm5).
// Precondition: imm5 <= 31
//
// For imm5 != 1, the values are the same as single-precision (just represented
// in double format). For imm5=1, DBL_MIN differs from FLT_MIN.
double GetFLIDValue(uint8_t imm5) {
MOZ_ASSERT(is_uint5(imm5));
if (imm5 == 1) {
return mozilla::BitwiseCast<double>(kFP64DblMin);
}
// For all other imm5 values, convert the single-precision value to double.
// The numerical value is the same, just with different representation.
return static_cast<double>(GetFLISValue(imm5));
}
// Returns the FLI.S immediate encoding (imm5) for the given single-precision
// floating-point value, or -1 if the value cannot be encoded by FLI.S.
// This is used by the compiler to determine if a constant can be loaded
// using the FLI.S instruction.
int GetImm5ForFLIS(float value) {
// Handle special values first
// Check for NaN (imm5=31: Canonical NaN)
if (std::isnan(value)) {
// Only canonical NaN is encodable
uint32_t bits = mozilla::BitwiseCast<uint32_t>(value);
return (bits == kFP32DefaultNaN) ? 31 : -1;
}
// Check for infinity (imm5=30: +∞)
if (std::isinf(value)) {
return (value > 0.0f) ? 30 : -1; // Only +∞ is encodable
}
// Check for minimum positive normal (imm5=1)
// FLT_MIN = 2^(-126)
if (mozilla::BitwiseCast<uint32_t>(value) == kFP32FltMin) {
return 1;
}
// Get the bit representation
uint32_t bits = mozilla::BitwiseCast<uint32_t>(value);
// Extract sign
uint32_t sign = (bits >> 31) & 1;
// For negative values, check the absolute value (clear sign bit)
uint32_t abs_bits = bits & 0x7FFFFFFF;
// FLI constants only have mantissa bits in the top 2 positions
// Check if lower 21 bits of mantissa are zero
uint32_t mantissa = abs_bits & 0x7FFFFF;
if ((mantissa & 0x1FFFFF) != 0) {
return -1;
}
// Binary search in the lookup table (using absolute value)
auto it = std::lower_bound(std::begin(kLoadFP32ImmArr),
std::end(kLoadFP32ImmArr), abs_bits);
if (it == std::end(kLoadFP32ImmArr) || *it != abs_bits) {
return -1; // Not found in table
}
int entry = static_cast<int>(it - std::begin(kLoadFP32ImmArr)) + 2;
// Handle negative values: only -1.0 (entry 16 with sign bit) is valid
if (sign) {
return (entry == 16) ? 0 : -1;
}
return entry;
}
// Returns the FLI.D immediate encoding (imm5) for the given double-precision
// floating-point value, or -1 if the value cannot be encoded by FLI.D.
//
int GetImm5ForFLID(double value) {
if (std::isnan(value)) {
uint64_t bits = mozilla::BitwiseCast<uint64_t>(value);
return (bits == kFP64DefaultNaN) ? 31 : -1;
}
if (mozilla::BitwiseCast<uint64_t>(value) == kFP64DblMin) { // DBL_MIN
return 1;
}
// This check is always false for all values in the kLoadFP32ImmArr constant
// table, since all FLI-defined values are exactly representable in both float
// and double.
float fvalue = static_cast<float>(value);
if (static_cast<double>(fvalue) != value) {
return -1;
}
int result = GetImm5ForFLIS(fvalue);
// imm5=1 for FLI.D is DBL_MIN, already handled above.
// If GetImm5ForFLIS returned 1 here, the value is FLT_MIN (2^-126)
// which is NOT encodable by FLI.D.
if (result == 1) {
return -1;
}
return result;
}
} // namespace js::jit