https://github.com/ItsNoHax updated https://github.com/llvm/llvm-project/pull/218994
>From c744a02e4c1ac5b476c290d00b92ce0838d9f7b5 Mon Sep 17 00:00:00 2001 From: ItsNoHax <[email protected]> Date: Wed, 26 Aug 2026 17:25:01 +0000 Subject: [PATCH] [libunwind][MIPS] Add o32 single-float support A single-precision-only FPU (__mips_single_float, e.g. the PSP's Allegrex) has no sdc1/ldc1, so both existing o32 register save/restore paths are unencodable there and libunwind fails to assemble for such a target. Add a single-float branch using swc1/lwc1 for all 32 registers. Each register keeps an 8-byte slot, so the o32 context layout and unw_context_t size are unchanged -- matching the existing note in Registers_mips_o32 about keeping a single context size across FPU configurations. Only the low half of each slot is used. The new branch is tested before the existing __mips_fpr checks because the two compilers disagree on __mips_fpr for this configuration: GCC reports 32 and Clang reports 0. Both define __mips_single_float, so keying on that first gives the same result under either. Also teach the accessors about this configuration. Previously validFloatRegister() returned false and get/setFloatRegister() aborted with "mips_o32 float support not implemented" for anything other than __mips_fpr == 64. Under single float each $fN is an independent 32-bit register with no even/odd pairing, so getRegister()/setRegister() address the slot directly instead of using the __mips_fpr == 32 pairing scheme. The float area offsets are duplicated in assembly, where the compiler cannot check them, so add a compile-only test pinning the o32 context layout that UnwindRegistersSave.S and UnwindRegistersRestore.S rely on. --- libunwind/src/Registers.hpp | 47 +++++++++++-- libunwind/src/UnwindRegistersRestore.S | 36 +++++++++- libunwind/src/UnwindRegistersSave.S | 38 ++++++++++- .../mips_o32_context_layout.compile.pass.cpp | 33 +++++++++ .../mips_o32_single_float_registers.pass.cpp | 67 +++++++++++++++++++ 5 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 libunwind/test/mips_o32_context_layout.compile.pass.cpp create mode 100644 libunwind/test/mips_o32_single_float_registers.pass.cpp diff --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp index d79d836d69250..d95a1e610a639 100644 --- a/libunwind/src/Registers.hpp +++ b/libunwind/src/Registers.hpp @@ -12,6 +12,7 @@ #ifndef __REGISTERS_HPP__ #define __REGISTERS_HPP__ +#include <stddef.h> #include <stdint.h> #include <string.h> @@ -3067,6 +3068,19 @@ class _LIBUNWIND_HIDDEN Registers_mips_o32 { inline Registers_mips_o32::Registers_mips_o32(const void *registers) { static_assert((check_fit<Registers_mips_o32, unw_context_t>::does_fit), "mips_o32 registers do not fit into unw_context_t"); +#ifdef __mips_hard_float + // UnwindRegistersSave.S and UnwindRegistersRestore.S address the floating + // point area as (4 * 36 + 8 * N)($4), for N in [0, 32). Those constants are + // duplicated in assembly, where the compiler cannot check them, so pin the + // layout they assume here. + static_assert(offsetof(Registers_mips_o32, _floats) == 4 * 36, + "float area base offset does not match the (4 * 36) used by " + "UnwindRegistersSave.S and UnwindRegistersRestore.S"); + static_assert(sizeof(_floats[0]) == 8, + "float slot stride does not match the (8 * N) used by " + "UnwindRegistersSave.S and UnwindRegistersRestore.S"); + static_assert(sizeof(_floats) == 8 * 32, "float area must cover f0-f31"); +#endif memcpy(&_registers, static_cast<const uint8_t *>(registers), sizeof(_registers)); } @@ -3090,7 +3104,8 @@ inline bool Registers_mips_o32::validRegister(int regNum) const { if (regNum == UNW_MIPS_LO) return true; #endif -#if defined(__mips_hard_float) && __mips_fpr == 32 +#if defined(__mips_hard_float) && \ + (__mips_fpr == 32 || defined(__mips_single_float)) if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31) return true; #endif @@ -3101,7 +3116,15 @@ inline bool Registers_mips_o32::validRegister(int regNum) const { inline uint32_t Registers_mips_o32::getRegister(int regNum) const { if (regNum >= UNW_MIPS_R0 && regNum <= UNW_MIPS_R31) return _registers.__r[regNum - UNW_MIPS_R0]; -#if defined(__mips_hard_float) && __mips_fpr == 32 +#if defined(__mips_hard_float) && defined(__mips_single_float) + // Every register is an independent 32-bit value held in the low half of its + // slot; there is no even/odd pairing. + if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31) { + uint32_t result; + memcpy(&result, &_floats[regNum - UNW_MIPS_F0], sizeof(result)); + return result; + } +#elif defined(__mips_hard_float) && __mips_fpr == 32 if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31) { uint32_t *p; @@ -3133,7 +3156,13 @@ inline void Registers_mips_o32::setRegister(int regNum, uint32_t value) { _registers.__r[regNum - UNW_MIPS_R0] = value; return; } -#if defined(__mips_hard_float) && __mips_fpr == 32 +#if defined(__mips_hard_float) && defined(__mips_single_float) + // See the matching comment in getRegister(). + if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31) { + memcpy(&_floats[regNum - UNW_MIPS_F0], &value, sizeof(value)); + return; + } +#elif defined(__mips_hard_float) && __mips_fpr == 32 if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31) { uint32_t *p; @@ -3166,7 +3195,8 @@ inline void Registers_mips_o32::setRegister(int regNum, uint32_t value) { } inline bool Registers_mips_o32::validFloatRegister(int regNum) const { -#if defined(__mips_hard_float) && __mips_fpr == 64 +#if defined(__mips_hard_float) && \ + (__mips_fpr == 64 || defined(__mips_single_float)) if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31) return true; #else @@ -3179,6 +3209,11 @@ inline double Registers_mips_o32::getFloatRegister(int regNum) const { #if defined(__mips_hard_float) && __mips_fpr == 64 assert(validFloatRegister(regNum)); return _floats[regNum - UNW_MIPS_F0]; +#elif defined(__mips_hard_float) && defined(__mips_single_float) + assert(validFloatRegister(regNum)); + float result; + memcpy(&result, &_floats[regNum - UNW_MIPS_F0], sizeof(result)); + return result; #else (void)regNum; _LIBUNWIND_ABORT("mips_o32 float support not implemented"); @@ -3190,6 +3225,10 @@ inline void Registers_mips_o32::setFloatRegister(int regNum, #if defined(__mips_hard_float) && __mips_fpr == 64 assert(validFloatRegister(regNum)); _floats[regNum - UNW_MIPS_F0] = value; +#elif defined(__mips_hard_float) && defined(__mips_single_float) + assert(validFloatRegister(regNum)); + float single = static_cast<float>(value); + memcpy(&_floats[regNum - UNW_MIPS_F0], &single, sizeof(single)); #else (void)regNum; (void)value; diff --git a/libunwind/src/UnwindRegistersRestore.S b/libunwind/src/UnwindRegistersRestore.S index 37e8156408cf2..b299bcd0a7594 100644 --- a/libunwind/src/UnwindRegistersRestore.S +++ b/libunwind/src/UnwindRegistersRestore.S @@ -1012,7 +1012,41 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind18Registers_mips_o326jumptoEv) .set noreorder .set nomacro #ifdef __mips_hard_float -#if __mips_fpr != 64 +#if defined(__mips_single_float) + // See the matching comment in UnwindRegistersSave.S. + lwc1 $f0, (4 * 36 + 8 * 0)($4) + lwc1 $f1, (4 * 36 + 8 * 1)($4) + lwc1 $f2, (4 * 36 + 8 * 2)($4) + lwc1 $f3, (4 * 36 + 8 * 3)($4) + lwc1 $f4, (4 * 36 + 8 * 4)($4) + lwc1 $f5, (4 * 36 + 8 * 5)($4) + lwc1 $f6, (4 * 36 + 8 * 6)($4) + lwc1 $f7, (4 * 36 + 8 * 7)($4) + lwc1 $f8, (4 * 36 + 8 * 8)($4) + lwc1 $f9, (4 * 36 + 8 * 9)($4) + lwc1 $f10, (4 * 36 + 8 * 10)($4) + lwc1 $f11, (4 * 36 + 8 * 11)($4) + lwc1 $f12, (4 * 36 + 8 * 12)($4) + lwc1 $f13, (4 * 36 + 8 * 13)($4) + lwc1 $f14, (4 * 36 + 8 * 14)($4) + lwc1 $f15, (4 * 36 + 8 * 15)($4) + lwc1 $f16, (4 * 36 + 8 * 16)($4) + lwc1 $f17, (4 * 36 + 8 * 17)($4) + lwc1 $f18, (4 * 36 + 8 * 18)($4) + lwc1 $f19, (4 * 36 + 8 * 19)($4) + lwc1 $f20, (4 * 36 + 8 * 20)($4) + lwc1 $f21, (4 * 36 + 8 * 21)($4) + lwc1 $f22, (4 * 36 + 8 * 22)($4) + lwc1 $f23, (4 * 36 + 8 * 23)($4) + lwc1 $f24, (4 * 36 + 8 * 24)($4) + lwc1 $f25, (4 * 36 + 8 * 25)($4) + lwc1 $f26, (4 * 36 + 8 * 26)($4) + lwc1 $f27, (4 * 36 + 8 * 27)($4) + lwc1 $f28, (4 * 36 + 8 * 28)($4) + lwc1 $f29, (4 * 36 + 8 * 29)($4) + lwc1 $f30, (4 * 36 + 8 * 30)($4) + lwc1 $f31, (4 * 36 + 8 * 31)($4) +#elif __mips_fpr != 64 ldc1 $f0, (4 * 36 + 8 * 0)($4) ldc1 $f2, (4 * 36 + 8 * 2)($4) ldc1 $f4, (4 * 36 + 8 * 4)($4) diff --git a/libunwind/src/UnwindRegistersSave.S b/libunwind/src/UnwindRegistersSave.S index 37acbf3c69322..c51b5b8d17664 100644 --- a/libunwind/src/UnwindRegistersSave.S +++ b/libunwind/src/UnwindRegistersSave.S @@ -229,7 +229,43 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) sw $8, (4 * 34)($4) #endif #ifdef __mips_hard_float -#if __mips_fpr != 64 +#if defined(__mips_single_float) + // A single-precision-only FPU has no sdc1. Each register still occupies + // an 8-byte slot so that the context layout matches the other o32 FPU + // configurations; only the low half of each slot is used. + swc1 $f0, (4 * 36 + 8 * 0)($4) + swc1 $f1, (4 * 36 + 8 * 1)($4) + swc1 $f2, (4 * 36 + 8 * 2)($4) + swc1 $f3, (4 * 36 + 8 * 3)($4) + swc1 $f4, (4 * 36 + 8 * 4)($4) + swc1 $f5, (4 * 36 + 8 * 5)($4) + swc1 $f6, (4 * 36 + 8 * 6)($4) + swc1 $f7, (4 * 36 + 8 * 7)($4) + swc1 $f8, (4 * 36 + 8 * 8)($4) + swc1 $f9, (4 * 36 + 8 * 9)($4) + swc1 $f10, (4 * 36 + 8 * 10)($4) + swc1 $f11, (4 * 36 + 8 * 11)($4) + swc1 $f12, (4 * 36 + 8 * 12)($4) + swc1 $f13, (4 * 36 + 8 * 13)($4) + swc1 $f14, (4 * 36 + 8 * 14)($4) + swc1 $f15, (4 * 36 + 8 * 15)($4) + swc1 $f16, (4 * 36 + 8 * 16)($4) + swc1 $f17, (4 * 36 + 8 * 17)($4) + swc1 $f18, (4 * 36 + 8 * 18)($4) + swc1 $f19, (4 * 36 + 8 * 19)($4) + swc1 $f20, (4 * 36 + 8 * 20)($4) + swc1 $f21, (4 * 36 + 8 * 21)($4) + swc1 $f22, (4 * 36 + 8 * 22)($4) + swc1 $f23, (4 * 36 + 8 * 23)($4) + swc1 $f24, (4 * 36 + 8 * 24)($4) + swc1 $f25, (4 * 36 + 8 * 25)($4) + swc1 $f26, (4 * 36 + 8 * 26)($4) + swc1 $f27, (4 * 36 + 8 * 27)($4) + swc1 $f28, (4 * 36 + 8 * 28)($4) + swc1 $f29, (4 * 36 + 8 * 29)($4) + swc1 $f30, (4 * 36 + 8 * 30)($4) + swc1 $f31, (4 * 36 + 8 * 31)($4) +#elif __mips_fpr != 64 sdc1 $f0, (4 * 36 + 8 * 0)($4) sdc1 $f2, (4 * 36 + 8 * 2)($4) sdc1 $f4, (4 * 36 + 8 * 4)($4) diff --git a/libunwind/test/mips_o32_context_layout.compile.pass.cpp b/libunwind/test/mips_o32_context_layout.compile.pass.cpp new file mode 100644 index 0000000000000..62c20b9e74e29 --- /dev/null +++ b/libunwind/test/mips_o32_context_layout.compile.pass.cpp @@ -0,0 +1,33 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: target={{mips.*}} + +// UnwindRegistersSave.S and UnwindRegistersRestore.S address the o32 floating +// point area as (4 * 36 + 8 * N)($4), for N in [0, 32). +// +// The base offset and the per-register stride are asserted in Registers.hpp, +// where _floats is in scope. This is the outer check on the context as a +// whole, and is what fails first if the floating point area is narrowed to the +// width the FPU actually uses: the 8-byte stride is deliberate, so that a +// single context size covers every o32 FPU configuration. + +#include "../src/config.h" + +#if defined(_LIBUNWIND_TARGET_MIPS_O32) && defined(__mips_hard_float) + +#include "../src/Registers.hpp" + +using namespace libunwind; + +static_assert(sizeof(Registers_mips_o32) == 4 * 36 + 8 * 32, + "the o32 context layout no longer matches the offsets hard-coded " + "in UnwindRegistersSave.S and UnwindRegistersRestore.S"); + +#endif // _LIBUNWIND_TARGET_MIPS_O32 && __mips_hard_float diff --git a/libunwind/test/mips_o32_single_float_registers.pass.cpp b/libunwind/test/mips_o32_single_float_registers.pass.cpp new file mode 100644 index 0000000000000..c70391c611b27 --- /dev/null +++ b/libunwind/test/mips_o32_single_float_registers.pass.cpp @@ -0,0 +1,67 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: target={{mips.*}} + +// Exercise the o32 single-float register accessors. Before single float was +// supported these paths either reported the floating point registers as +// invalid or aborted with "mips_o32 float support not implemented". + +#include "../src/config.h" + +#if defined(_LIBUNWIND_TARGET_MIPS_O32) && defined(__mips_hard_float) && \ + defined(__mips_single_float) + +#include "../src/Registers.hpp" + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +using namespace libunwind; + +int main(int, char **) { + Registers_mips_o32 regs; + + for (int n = UNW_MIPS_F0; n <= UNW_MIPS_F31; ++n) { + if (!regs.validRegister(n)) + abort(); + if (!regs.validFloatRegister(n)) + abort(); + } + + // Under single float each register is independent, with no even/odd pairing, + // so a distinct value per register has to survive a round trip. + for (int n = UNW_MIPS_F0; n <= UNW_MIPS_F31; ++n) + regs.setRegister(n, 0xf0000000u + static_cast<uint32_t>(n - UNW_MIPS_F0)); + for (int n = UNW_MIPS_F0; n <= UNW_MIPS_F31; ++n) { + if (regs.getRegister(n) != + 0xf0000000u + static_cast<uint32_t>(n - UNW_MIPS_F0)) + abort(); + } + + // 2.5 is exactly representable in single precision, so this round trip is + // not subject to rounding. + regs.setFloatRegister(UNW_MIPS_F7, 2.5); + if (regs.getFloatRegister(UNW_MIPS_F7) != 2.5) + abort(); + + // The raw and floating point views address the same storage. + float single = 2.5f; + uint32_t bits; + memcpy(&bits, &single, sizeof(bits)); + if (regs.getRegister(UNW_MIPS_F7) != bits) + abort(); + + return 0; +} + +#else +int main(int, char **) { return 0; } +#endif _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
