[libunwind] [libunwind][MIPS] Add o32 single-float support (PR #218994)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 30 02:20:10 PDT 2026
https://github.com/ItsNoHax updated https://github.com/llvm/llvm-project/pull/218994
>From 53d4819cff14c76d2522baaab9b2af870f8e23b4 Mon Sep 17 00:00:00 2001
From: ItsNoHax <itsnohax at gmail.com>
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 | 41 ++++++++++--
libunwind/src/UnwindRegistersRestore.S | 36 +++++++++-
libunwind/src/UnwindRegistersSave.S | 38 ++++++++++-
.../mips_o32_context_layout.compile.pass.cpp | 29 ++++++++
.../mips_o32_single_float_registers.pass.cpp | 67 +++++++++++++++++++
5 files changed, 205 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..920184699ca6d 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,15 @@ 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
+ // UnwindRegisters{Save,Restore}.S address this area as (4 * 36 + 8 * N)($4).
+ static_assert(offsetof(Registers_mips_o32, _floats) == 4 * 36,
+ "mips_o32 float area offset does not match the .S files");
+ static_assert(sizeof(_floats[0]) == 8,
+ "mips_o32 float slot stride does not match the .S files");
+ static_assert(sizeof(_floats) == 8 * 32,
+ "mips_o32 float area must cover f0-f31");
+#endif
memcpy(&_registers, static_cast<const uint8_t *>(registers),
sizeof(_registers));
}
@@ -3090,7 +3100,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 +3112,14 @@ 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)
+ // Single float has 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 +3151,12 @@ 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)
+ 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 +3189,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 +3203,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 +3219,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..8e0cb23b95763
--- /dev/null
+++ b/libunwind/test/mips_o32_context_layout.compile.pass.cpp
@@ -0,0 +1,29 @@
+// -*- 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.*}}
+
+// The o32 context is sized for 32 double-width float slots even when the FPU
+// only holds 32-bit values, so that one context size covers every o32 FPU
+// configuration. Registers.hpp asserts the base offset and stride that
+// UnwindRegisters{Save,Restore}.S depend on; this is the outer size check.
+
+#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
More information about the cfe-commits
mailing list