blob: cb5ba50c76371ad2b3b59cca9ffdd60a299dc598 [file]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstdint>
#include "mpact/sim/generic/type_helpers.h"
#include "riscv/riscv_fp_host.h"
#include "riscv/riscv_fp_info.h"
#include "riscv/riscv_fp_state.h"
// This file implements the x86 versions of the methods/classes that interact
// with the host floating point hw.
namespace mpact {
namespace sim {
namespace riscv {
using ::mpact::sim::generic::operator*; // NOLINT: is used below (clang error).
// x86 flags.
constexpr uint8_t kPE = 32; // Inexact result
constexpr uint8_t kUE = 16; // Underflow
constexpr uint8_t kOE = 8; // Overflow
constexpr uint8_t kZE = 4; // Divide by zero
constexpr uint8_t kIE = 1; // Illegal operation
// RiscV flags.
constexpr uint8_t kNV = static_cast<uint8_t>(FPExceptions::kInvalidOp);
constexpr uint8_t kDZ = static_cast<uint8_t>(FPExceptions::kDivByZero);
constexpr uint8_t kOF = static_cast<uint8_t>(FPExceptions::kOverflow);
constexpr uint8_t kUF = static_cast<uint8_t>(FPExceptions::kUnderflow);
constexpr uint8_t kNX = static_cast<uint8_t>(FPExceptions::kInexact);
// This map converts the x86 fp flags to RiscV fp flags. The x86 layout is:
// PE, UE, OE, ZE, DE, IE, whereas the RiscV layout is
// IE, ZE, OE, UE, PE (RiscV terminology: NV, DZ, OF, UF, NX)
// The x86 DE flag bit is not used.
constexpr uint8_t kX86FlagsToRiscvMap[64] = {0,
kNV,
0,
kNV,
kDZ,
kDZ | kNV,
kDZ,
kNV | kDZ,
kOF,
kOF | kNV,
kOF,
kOF | kNV,
kOF | kDZ,
kOF | kDZ | kNV,
kOF | kDZ,
kOF | kDZ | kNV,
kUF,
kUF | kNV,
kUF,
kUF | kNV,
kUF | kDZ,
kUF | kDZ | kNV,
kUF | kDZ,
kUF | kDZ | kNV,
kUF | kOF,
kUF | kOF | kNV,
kUF | kOF,
kUF | kOF | kNV,
kUF | kOF | kDZ,
kUF | kOF | kDZ | kNV,
kUF | kOF | kDZ,
kUF | kOF | kDZ | kNV,
kNX,
kNX | kNV,
kNX,
kNX | kNV,
kNX | kDZ,
kNX | kDZ | kNV,
kNX | kDZ,
kNX | kNV | kDZ,
kNX | kOF,
kNX | kOF | kNV,
kNX | kOF,
kNX | kOF | kNV,
kNX | kOF | kDZ,
kNX | kOF | kDZ | kNV,
kNX | kOF | kDZ,
kNX | kOF | kDZ | kNV,
kNX | kUF,
kNX | kUF | kNV,
kNX | kUF,
kNX | kUF | kNV,
kNX | kUF | kDZ,
kNX | kUF | kDZ | kNV,
kNX | kUF | kDZ,
kNX | kUF | kDZ | kNV,
kNX | kUF | kOF,
kNX | kUF | kOF | kNV,
kNX | kUF | kOF,
kNX | kUF | kOF | kNV,
kNX | kUF | kOF | kDZ,
kNX | kUF | kOF | kDZ | kNV,
kNX | kUF | kOF | kDZ,
kNX | kUF | kOF | kDZ | kNV};
// This map converts RiscV flags to x86 flags.
// The RiscV layout is PE
constexpr uint8_t kRiscVFlagsToX86[32]{
0,
kPE,
kUE,
kUE | kPE,
kOE,
kOE | kPE,
kOE | kUE,
kOE | kUE | kPE,
kZE,
kZE | kPE,
kZE | kUE,
kZE | kUE | kPE,
kZE | kOE,
kZE | kOE | kPE,
kZE | kOE | kUE,
kZE | kOE | kUE | kPE,
kIE,
kIE | kPE,
kIE | kUE,
kIE | kUE | kPE,
kIE | kOE,
kIE | kOE | kPE,
kIE | kOE | kUE,
kIE | kOE | kUE | kPE,
kIE | kZE,
kIE | kZE | kPE,
kIE | kZE | kUE,
kIE | kZE | kUE | kPE,
kIE | kZE | kOE,
kIE | kZE | kOE | kPE,
kIE | kZE | kOE | kUE,
kIE | kZE | kOE | kUE | kPE,
};
constexpr uint32_t kX86ToNearest = 0x0000;
constexpr uint32_t kX86ToNegInf = 0x2000;
constexpr uint32_t kX86ToPosInf = 0x4000;
constexpr uint32_t kX86ToZero = 0x6000;
constexpr uint32_t kRiscVToX86RoundingModeMap[8] = {
kX86ToNearest, kX86ToZero, kX86ToNegInf, kX86ToPosInf,
kX86ToNearest, kX86ToNearest, kX86ToNearest, kX86ToNearest,
};
// Map from x86 rounding mode to RiscV rounding mode.
static constexpr FPRoundingMode kX86ToRiscVRoundingModeMap[] = {
FPRoundingMode::kRoundToNearest, FPRoundingMode::kRoundDown,
FPRoundingMode::kRoundUp, FPRoundingMode::kRoundTowardsZero};
static constexpr uint32_t kX86ExceptionFlags = 0x1f80;
// This class provides the translations between RiscV and x86 floating point
// state. It also stores the x86 version of the simulated RISCV state
// between uses in simulated floating point instructions.
class X86FloatingPointInterface : public HostFloatingPointInterface {
public:
X86FloatingPointInterface() = default;
~X86FloatingPointInterface() override = default;
uint32_t GetRiscVFcsr() override {
auto x86_rm = (x86_status_ >> 13) & 0x3;
uint32_t rm = *kX86ToRiscVRoundingModeMap[x86_rm];
uint32_t flags = kX86FlagsToRiscvMap[x86_status_ & 0b11'1111];
uint32_t value = (rm << 5) | (flags & 0x1f);
return value;
}
void SetRiscVFcsr(uint32_t riscv_fcsr) override {
uint32_t rm = (riscv_fcsr >> 5) & 0x7;
uint32_t x86_rm = kRiscVToX86RoundingModeMap[rm];
uint32_t flags = kRiscVFlagsToX86[riscv_fcsr & 0b1'1111];
x86_status_ = kX86ExceptionFlags | x86_rm | flags;
}
uint32_t x86_status() const { return x86_status_; }
void set_x86_status(uint32_t x86_status) {
x86_status_ = kX86ExceptionFlags | x86_status;
}
private:
uint32_t x86_status_ = kX86ExceptionFlags;
};
// Factory function.
HostFloatingPointInterface* GetHostFloatingPointInterface() {
return new X86FloatingPointInterface();
}
#pragma STDC FENV_ACCESS ON
ScopedFPStatus::ScopedFPStatus(HostFloatingPointInterface* fp_interface)
: fp_interface_(fp_interface) {
// The host processor status is saved in cpu_fp_status_.
uint32_t sim_status = 0;
asm volatile("stmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
: "=m"(cpu_fp_status_), "+X"(sim_status));
auto* host_fp_interface =
static_cast<X86FloatingPointInterface*>(fp_interface_);
// Get the translated version of the simulated RiscV status.
sim_status = host_fp_interface->x86_status();
// Save current "dynamic" rounding mode.
host_rm_ = sim_status & 0x6000;
// Set the host status to the translated RiscV status.
asm volatile("ldmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
:
: "m"(sim_status));
}
ScopedFPStatus::ScopedFPStatus(HostFloatingPointInterface* fp_interface,
uint32_t rm)
: fp_interface_(fp_interface) {
// The host processor status is saved in cpu_fp_status_.
uint32_t sim_status = 0;
asm volatile("stmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
: "=m"(cpu_fp_status_), "=X"(sim_status));
auto* host_fp_interface =
static_cast<X86FloatingPointInterface*>(fp_interface_);
sim_status = host_fp_interface->x86_status();
// Save current "dynamic" rounding mode.
host_rm_ = sim_status & 0x6000;
if (rm != 0b111) {
// Override rounding mode with that specified in the instruction.
uint32_t new_x86_rm = kRiscVToX86RoundingModeMap[rm];
sim_status = new_x86_rm | (sim_status & 0x1fff);
}
// Set the host status to the translated RiscV status.
asm volatile("ldmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
:
: "m"(sim_status));
}
ScopedFPStatus::ScopedFPStatus(HostFloatingPointInterface* fp_interface,
FPRoundingMode rm)
: fp_interface_(fp_interface) {
// The host processor status is saved in cpu_fp_status_.
uint32_t sim_status = 0;
asm volatile("stmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
: "=m"(cpu_fp_status_), "=X"(sim_status));
auto* host_fp_interface =
static_cast<X86FloatingPointInterface*>(fp_interface);
sim_status = host_fp_interface->x86_status();
// Save current "dynamic" rounding mode.
host_rm_ = sim_status & 0x6000;
auto rm_value = static_cast<uint32_t>(rm);
if (rm_value != 0b111) {
// Override rounding mode with that specified in the instruction.
uint32_t new_x86_rm = kRiscVToX86RoundingModeMap[rm_value];
sim_status = new_x86_rm | (sim_status & 0x1fff);
}
// Set the host status to the translated RiscV status.
asm volatile("ldmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
:
: "m"(sim_status));
}
ScopedFPStatus::~ScopedFPStatus() {
uint32_t x86_sim_status = 0;
// Get the current x86 status.
asm volatile( // NOLINT(google3-runtime-inline-assembly)
"stmxcsr %0\n"
: "=m"(x86_sim_status));
auto* host_fp_interface =
static_cast<X86FloatingPointInterface*>(fp_interface_);
// Save the fp status of the simulated instructions.
uint32_t sim_status = ((x86_sim_status) & 0x1fff) | host_rm_;
// Save the new status as the x86 version of the RiscV status.
host_fp_interface->set_x86_status(x86_sim_status);
// Restore host cpu status.
asm volatile("ldmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
:
: "m"(cpu_fp_status_), "X"(sim_status));
}
ScopedFPRoundingMode::ScopedFPRoundingMode(
HostFloatingPointInterface* fp_interface) {
uint32_t sim_status = 0;
// Get current x86 status and save it in cpu_fp_status_.
asm volatile("stmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
: "=m"(cpu_fp_status_), "=X"(sim_status));
auto* host_fp_interface =
static_cast<X86FloatingPointInterface*>(fp_interface);
sim_status = host_fp_interface->x86_status();
asm volatile("ldmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
:
: "m"(sim_status));
}
ScopedFPRoundingMode::ScopedFPRoundingMode(
HostFloatingPointInterface* fp_interface, uint32_t rm) {
uint32_t sim_status = 0;
// Get current x86 status and save it in cpu_fp_status_.
asm volatile("stmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
: "=m"(cpu_fp_status_), "=X"(sim_status));
auto* host_fp_interface =
static_cast<X86FloatingPointInterface*>(fp_interface);
sim_status = host_fp_interface->x86_status();
if (rm != 0b111) {
// Override rounding mode with that specified in the instruction.
uint32_t new_x86_rm = kRiscVToX86RoundingModeMap[rm];
sim_status = new_x86_rm | (sim_status & 0x1fff);
}
asm volatile("ldmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
:
: "m"(sim_status));
}
ScopedFPRoundingMode::ScopedFPRoundingMode(
HostFloatingPointInterface* fp_interface, FPRoundingMode rm) {
uint32_t sim_status = 0;
// Get current x86 status and save it in cpu_fp_status_.
asm volatile("stmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
: "=m"(cpu_fp_status_), "=X"(sim_status));
auto* host_fp_interface =
static_cast<X86FloatingPointInterface*>(fp_interface);
sim_status = host_fp_interface->x86_status();
auto rm_value = static_cast<uint32_t>(rm);
if (rm_value != 0b111) {
// Override rounding mode with that specified in the instruction.
uint32_t new_x86_rm = kRiscVToX86RoundingModeMap[rm_value];
sim_status = new_x86_rm | (sim_status & 0x1fff);
}
asm volatile("ldmxcsr %0\n" // NOLINT(google3-runtime-inline-assembly)
:
: "m"(sim_status));
}
ScopedFPRoundingMode::~ScopedFPRoundingMode() {
// No need to save the state, for RoundingMode we only needed the rounding
// mode be set. Just restore the previous x86 fp status.
asm volatile( // NOLINT(google3-runtime-inline-assembly)
"ldmxcsr %0\n"
:
: "m"(cpu_fp_status_));
}
#pragma STDC FENV_ACCESS OFF
} // namespace riscv
} // namespace sim
} // namespace mpact