[libunwind] 69f90dd - [libunwind][MIPS] Add o32 single-float support (#218994)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 12 05:12:22 PDT 2026
Author: Alberto
Date: 2026-10-12T13:12:17+01:00
New Revision: 69f90dd74332a117fd7988309ea00a1cba2aca3d
URL: https://github.com/llvm/llvm-project/commit/69f90dd74332a117fd7988309ea00a1cba2aca3d
DIFF: https://github.com/llvm/llvm-project/commit/69f90dd74332a117fd7988309ea00a1cba2aca3d.diff
LOG: [libunwind][MIPS] Add o32 single-float support (#218994)
On FPUs that only support single precision, such as the PSP's Allegrex,
libunwind fails to assemble for o32 because the save/restore code uses
sdc1/ldc1. Save and restore all 32 FPRs with swc1/lwc1 instead, keeping
each register in an 8-byte slot so the context layout is the same for
every o32 FPU configuration.
Check __mips_single_float before __mips_fpr: GCC defines __mips_fpr as
32 for single-float targets and Clang defines it as 0, but both define
__mips_single_float.
In Registers.hpp, treat each FPR as an independent 32-bit value when
__mips_single_float is set, rather than pairing even/odd registers.
Add a compile-only test that checks the size of the o32 context.
Added:
libunwind/test/mips_o32_context_layout.compile.pass.cpp
libunwind/test/mips_o32_single_float_registers.pass.cpp
Modified:
libunwind/src/Registers.hpp
libunwind/src/UnwindRegistersRestore.S
libunwind/src/UnwindRegistersSave.S
Removed:
################################################################################
diff --git a/libunwind/src/Registers.hpp b/libunwind/src/Registers.hpp
index 7101bb21abda5..4870e315962e0 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>
@@ -3068,6 +3069,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));
}
@@ -3091,9 +3101,11 @@ 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)
+#if defined(__mips_single_float) || __mips_fpr == 32
if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31)
return true;
+#endif
#endif
// FIXME: DSP accumulator registers, MSA registers
return false;
@@ -3102,7 +3114,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)
+#if 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 __mips_fpr == 32
if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31) {
uint32_t *p;
@@ -3112,6 +3132,7 @@ inline uint32_t Registers_mips_o32::getRegister(int regNum) const {
p = (uint32_t *)&_floats[(regNum - 1) - UNW_MIPS_F0] + 1;
return *p;
}
+#endif
#endif
switch (regNum) {
@@ -3134,7 +3155,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)
+#if defined(__mips_single_float)
+ if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31) {
+ memcpy(&_floats[regNum - UNW_MIPS_F0], &value, sizeof(value));
+ return;
+ }
+#elif __mips_fpr == 32
if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31) {
uint32_t *p;
@@ -3145,6 +3172,7 @@ inline void Registers_mips_o32::setRegister(int regNum, uint32_t value) {
*p = value;
return;
}
+#endif
#endif
switch (regNum) {
@@ -3167,35 +3195,49 @@ 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)
+#if defined(__mips_single_float) || __mips_fpr == 64
if (regNum >= UNW_MIPS_F0 && regNum <= UNW_MIPS_F31)
return true;
-#else
- (void)regNum;
#endif
+#endif
+ (void)regNum;
return false;
}
inline double Registers_mips_o32::getFloatRegister(int regNum) const {
-#if defined(__mips_hard_float) && __mips_fpr == 64
+#if defined(__mips_hard_float)
+#if defined(__mips_single_float)
+ assert(validFloatRegister(regNum));
+ float result;
+ memcpy(&result, &_floats[regNum - UNW_MIPS_F0], sizeof(result));
+ return result;
+#elif __mips_fpr == 64
assert(validFloatRegister(regNum));
return _floats[regNum - UNW_MIPS_F0];
-#else
+#endif
+#endif
(void)regNum;
_LIBUNWIND_ABORT("mips_o32 float support not implemented");
-#endif
}
inline void Registers_mips_o32::setFloatRegister(int regNum,
double value) {
-#if defined(__mips_hard_float) && __mips_fpr == 64
+#if defined(__mips_hard_float)
+#if defined(__mips_single_float)
+ assert(validFloatRegister(regNum));
+ float single = static_cast<float>(value);
+ memcpy(&_floats[regNum - UNW_MIPS_F0], &single, sizeof(single));
+ return;
+#elif __mips_fpr == 64
assert(validFloatRegister(regNum));
_floats[regNum - UNW_MIPS_F0] = value;
-#else
+ return;
+#endif
+#endif
(void)regNum;
(void)value;
_LIBUNWIND_ABORT("mips_o32 float support not implemented");
-#endif
}
inline bool Registers_mips_o32::validVectorRegister(int /* regNum */) const {
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