Implement and use generic mips libco (#155)

* libco mips

* Add VFP saving and restore

* libco/mips: Optimize storage for 32-bit CPUs

* Clarify license status

* Increase sp alignment to 16 bytes for safety

* libco/mips: preserve registers according to the target ISA and ABI

Distinguish MIPS32 from 64-bit GPR ISAs, and sign-extend 32-bit ABI pointers when initializing 64-bit context slots. Save full double-precision FP state, including the n64 odd callee-saved registers, and omit FP instructions for soft-float builds. Reserve aligned, non-overlapping FP and VFPU areas.

* libco/mips: initialize t9 when entering a coroutine

PIC entry functions use t9 to establish their global pointer. Set it to the restored entry address before jumping, and keep the handwritten context-switch assembly in its own text section with explicit delay-slot scheduling.

* libco/mips: reject overflowing stack allocations

Reject sizes that overflow when context overhead and alignment are added. Also check the documented nonzero error return from posix_memalign.

* libco/mips: add coroutine and floating-point regression coverage

---------

Co-authored-by: Codex <[email protected]>
This commit is contained in:
Vladimir SerbinenkoandCodex authored and GitHub committed 2026-10-11 13:29:21 +02:00
1 parent 4c1ae8b2f1
commit 9863d6937a
4 files changed
+454

No files matched your search

+2
View File
@@ -35,6 +35,8 @@ void genode_free_secondary_stack(void *stack);
#include "psp1.c"
#elif defined VITA
#include "scefiber.c"
#elif defined(__mips__)
#include "mips.c"
#elif defined(__ARM_EABI__) || defined(__arm__)
#include "armeabi.c"
#else
+299
View File
@@ -0,0 +1,299 @@
/*
libco.mips (2020-04-20)
author: phcoder
copyright: Google
license: MIT
*/
#define LIBCO_C
#include <libco.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>
#ifndef __APPLE__
#include <malloc.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* Use the compiler's register model, not the ISA revision: MIPS32 is
* numbered 32, and even MIPS III/64 uses 32-bit GPR operations with o32.
* n32 still has 32-bit pointers but uses 64-bit GPRs. */
#if defined(__mips64)
#define STORE_REG "sd"
#define LOAD_REG "ld"
#define _GPR_OFF(x) (x * 8)
typedef uint64_t gpr_t;
#else
/* 32-bit only variant. */
#define STORE_REG "sw"
#define LOAD_REG "lw"
#define _GPR_OFF(x) (x * 4)
typedef uint32_t gpr_t;
#endif
#define _STR(x) #x
#define STR(x) _STR(x)
#define GPR_OFF(x) STR(_GPR_OFF(x))
/* Reserve twelve eight-byte FP slots even with soft float. The even slots
* hold doubles (paired registers with FR=0); n64 also saves odd registers.
* The following VFPU area stays sixteen-byte aligned in both GPR layouts. */
#define _FPR_OFF(x) ((x * 8) + _GPR_OFF(12))
#define FPR_OFF(x) STR(_FPR_OFF(x))
#define _VFP_OFF(x) ((x * 16) + _FPR_OFF(12))
#define VFP_OFF(x) STR(_VFP_OFF(x))
#if defined(__mips_hard_float)
#define HAVE_FP 1
#else
#define HAVE_FP 0
#endif
#ifdef __psp__
#define HAVE_VFP 1
#else
#define HAVE_VFP 0
#endif
#define CONTEXT_SIZE 0x300
static thread_local uint64_t co_active_buffer[CONTEXT_SIZE / 8] __attribute__((__aligned__((16))));
static thread_local cothread_t co_active_handle;
__asm__ (
".text\n"
".set push\n"
".set noreorder\n"
".align 4\n"
".globl co_switch_mips\n"
".globl _co_switch_mips\n"
"co_switch_mips:\n"
"_co_switch_mips:\n"
STORE_REG " $s0, " GPR_OFF(0) "($a1)\n"
STORE_REG " $s1, " GPR_OFF(1) "($a1)\n"
STORE_REG " $s2, " GPR_OFF(2) "($a1)\n"
STORE_REG " $s3, " GPR_OFF(3) "($a1)\n"
STORE_REG " $s4, " GPR_OFF(4) "($a1)\n"
STORE_REG " $s5, " GPR_OFF(5) "($a1)\n"
STORE_REG " $s6, " GPR_OFF(6) "($a1)\n"
STORE_REG " $s7, " GPR_OFF(7) "($a1)\n"
STORE_REG " $gp, " GPR_OFF(8) "($a1)\n"
STORE_REG " $sp, " GPR_OFF(9) "($a1)\n"
STORE_REG " $fp, " GPR_OFF(10) "($a1)\n"
STORE_REG " $ra, " GPR_OFF(11) "($a1)\n"
#if HAVE_FP
#if __mips == 1
/* MIPS I has only word FP transfers; save both halves of each pair. */
" swc1 $f20, " FPR_OFF(0) "($a1)\n"
" swc1 $f21, " FPR_OFF(1) "($a1)\n"
" swc1 $f22, " FPR_OFF(2) "($a1)\n"
" swc1 $f23, " FPR_OFF(3) "($a1)\n"
" swc1 $f24, " FPR_OFF(4) "($a1)\n"
" swc1 $f25, " FPR_OFF(5) "($a1)\n"
" swc1 $f26, " FPR_OFF(6) "($a1)\n"
" swc1 $f27, " FPR_OFF(7) "($a1)\n"
" swc1 $f28, " FPR_OFF(8) "($a1)\n"
" swc1 $f29, " FPR_OFF(9) "($a1)\n"
" swc1 $f30, " FPR_OFF(10) "($a1)\n"
" swc1 $f31, " FPR_OFF(11) "($a1)\n"
#else
" sdc1 $f20, " FPR_OFF(0) "($a1)\n"
" sdc1 $f22, " FPR_OFF(2) "($a1)\n"
" sdc1 $f24, " FPR_OFF(4) "($a1)\n"
" sdc1 $f26, " FPR_OFF(6) "($a1)\n"
" sdc1 $f28, " FPR_OFF(8) "($a1)\n"
" sdc1 $f30, " FPR_OFF(10) "($a1)\n"
#if defined(_ABI64) && _MIPS_SIM == _ABI64
/* n64 preserves all eight f24..f31 registers, not just the evens. */
" sdc1 $f25, " FPR_OFF(5) "($a1)\n"
" sdc1 $f27, " FPR_OFF(7) "($a1)\n"
" sdc1 $f29, " FPR_OFF(9) "($a1)\n"
" sdc1 $f31, " FPR_OFF(11) "($a1)\n"
#endif
#endif
#endif
#if HAVE_VFP
" sv.q c000, " VFP_OFF(0) "($a1), wt\n"
" sv.q c010, " VFP_OFF(1) "($a1), wt\n"
" sv.q c020, " VFP_OFF(2) "($a1), wt\n"
" sv.q c030, " VFP_OFF(3) "($a1), wt\n"
" sv.q c100, " VFP_OFF(4) "($a1), wt\n"
" sv.q c110, " VFP_OFF(5) "($a1), wt\n"
" sv.q c120, " VFP_OFF(6) "($a1), wt\n"
" sv.q c130, " VFP_OFF(7) "($a1), wt\n"
" sv.q c200, " VFP_OFF(8) "($a1), wt\n"
" sv.q c210, " VFP_OFF(9) "($a1), wt\n"
" sv.q c220, " VFP_OFF(10) "($a1), wt\n"
" sv.q c230, " VFP_OFF(11) "($a1), wt\n"
" sv.q c300, " VFP_OFF(12) "($a1), wt\n"
" sv.q c310, " VFP_OFF(13) "($a1), wt\n"
" sv.q c320, " VFP_OFF(14) "($a1), wt\n"
" sv.q c330, " VFP_OFF(15) "($a1), wt\n"
" sv.q c400, " VFP_OFF(16) "($a1), wt\n"
" sv.q c410, " VFP_OFF(17) "($a1), wt\n"
" sv.q c420, " VFP_OFF(18) "($a1), wt\n"
" sv.q c430, " VFP_OFF(19) "($a1), wt\n"
" sv.q c500, " VFP_OFF(20) "($a1), wt\n"
" sv.q c510, " VFP_OFF(21) "($a1), wt\n"
" sv.q c520, " VFP_OFF(22) "($a1), wt\n"
" sv.q c530, " VFP_OFF(23) "($a1), wt\n"
" sv.q c600, " VFP_OFF(24) "($a1), wt\n"
" sv.q c610, " VFP_OFF(25) "($a1), wt\n"
" sv.q c620, " VFP_OFF(26) "($a1), wt\n"
" sv.q c630, " VFP_OFF(27) "($a1), wt\n"
" sv.q c700, " VFP_OFF(28) "($a1), wt\n"
" sv.q c710, " VFP_OFF(29) "($a1), wt\n"
" sv.q c720, " VFP_OFF(30) "($a1), wt\n"
" sv.q c730, " VFP_OFF(31) "($a1), wt\n"
#endif
LOAD_REG " $s0, " GPR_OFF(0) "($a0)\n"
LOAD_REG " $s1, " GPR_OFF(1) "($a0)\n"
LOAD_REG " $s2, " GPR_OFF(2) "($a0)\n"
LOAD_REG " $s3, " GPR_OFF(3) "($a0)\n"
LOAD_REG " $s4, " GPR_OFF(4) "($a0)\n"
LOAD_REG " $s5, " GPR_OFF(5) "($a0)\n"
LOAD_REG " $s6, " GPR_OFF(6) "($a0)\n"
LOAD_REG " $s7, " GPR_OFF(7) "($a0)\n"
LOAD_REG " $gp, " GPR_OFF(8) "($a0)\n"
LOAD_REG " $sp, " GPR_OFF(9) "($a0)\n"
LOAD_REG " $fp, " GPR_OFF(10) "($a0)\n"
LOAD_REG " $ra, " GPR_OFF(11) "($a0)\n"
" nop\n" /* MIPS I load delay before using ra, including soft float. */
#if HAVE_FP
#if __mips == 1
/* MIPS I has only word FP transfers; save both halves of each pair. */
" lwc1 $f20, " FPR_OFF(0) "($a0)\n"
" lwc1 $f21, " FPR_OFF(1) "($a0)\n"
" lwc1 $f22, " FPR_OFF(2) "($a0)\n"
" lwc1 $f23, " FPR_OFF(3) "($a0)\n"
" lwc1 $f24, " FPR_OFF(4) "($a0)\n"
" lwc1 $f25, " FPR_OFF(5) "($a0)\n"
" lwc1 $f26, " FPR_OFF(6) "($a0)\n"
" lwc1 $f27, " FPR_OFF(7) "($a0)\n"
" lwc1 $f28, " FPR_OFF(8) "($a0)\n"
" lwc1 $f29, " FPR_OFF(9) "($a0)\n"
" lwc1 $f30, " FPR_OFF(10) "($a0)\n"
" lwc1 $f31, " FPR_OFF(11) "($a0)\n"
#else
" ldc1 $f20, " FPR_OFF(0) "($a0)\n"
" ldc1 $f22, " FPR_OFF(2) "($a0)\n"
" ldc1 $f24, " FPR_OFF(4) "($a0)\n"
" ldc1 $f26, " FPR_OFF(6) "($a0)\n"
" ldc1 $f28, " FPR_OFF(8) "($a0)\n"
" ldc1 $f30, " FPR_OFF(10) "($a0)\n"
#if defined(_ABI64) && _MIPS_SIM == _ABI64
/* n64 preserves all eight f24..f31 registers, not just the evens. */
" ldc1 $f25, " FPR_OFF(5) "($a0)\n"
" ldc1 $f27, " FPR_OFF(7) "($a0)\n"
" ldc1 $f29, " FPR_OFF(9) "($a0)\n"
" ldc1 $f31, " FPR_OFF(11) "($a0)\n"
#endif
#endif
#endif
#if HAVE_VFP
" lv.q c000, " VFP_OFF(0) "($a0)\n"
" lv.q c010, " VFP_OFF(1) "($a0)\n"
" lv.q c020, " VFP_OFF(2) "($a0)\n"
" lv.q c030, " VFP_OFF(3) "($a0)\n"
" lv.q c100, " VFP_OFF(4) "($a0)\n"
" lv.q c110, " VFP_OFF(5) "($a0)\n"
" lv.q c120, " VFP_OFF(6) "($a0)\n"
" lv.q c130, " VFP_OFF(7) "($a0)\n"
" lv.q c200, " VFP_OFF(8) "($a0)\n"
" lv.q c210, " VFP_OFF(9) "($a0)\n"
" lv.q c220, " VFP_OFF(10) "($a0)\n"
" lv.q c230, " VFP_OFF(11) "($a0)\n"
" lv.q c300, " VFP_OFF(12) "($a0)\n"
" lv.q c310, " VFP_OFF(13) "($a0)\n"
" lv.q c320, " VFP_OFF(14) "($a0)\n"
" lv.q c330, " VFP_OFF(15) "($a0)\n"
" lv.q c400, " VFP_OFF(16) "($a0)\n"
" lv.q c410, " VFP_OFF(17) "($a0)\n"
" lv.q c420, " VFP_OFF(18) "($a0)\n"
" lv.q c430, " VFP_OFF(19) "($a0)\n"
" lv.q c500, " VFP_OFF(20) "($a0)\n"
" lv.q c510, " VFP_OFF(21) "($a0)\n"
" lv.q c520, " VFP_OFF(22) "($a0)\n"
" lv.q c530, " VFP_OFF(23) "($a0)\n"
" lv.q c600, " VFP_OFF(24) "($a0)\n"
" lv.q c610, " VFP_OFF(25) "($a0)\n"
" lv.q c620, " VFP_OFF(26) "($a0)\n"
" lv.q c630, " VFP_OFF(27) "($a0)\n"
" lv.q c700, " VFP_OFF(28) "($a0)\n"
" lv.q c710, " VFP_OFF(29) "($a0)\n"
" lv.q c720, " VFP_OFF(30) "($a0)\n"
" lv.q c730, " VFP_OFF(31) "($a0)\n"
#endif
/* PIC entrypoints derive their GOT pointer from t9. On resumption
* t9 is caller-saved, so setting it to the continuation is harmless. */
" move $t9, $ra\n"
" jr $ra\n"
" nop\n"
".align 4\n"
".globl store_gp\n"
".globl _store_gp\n"
"store_gp:\n"
"_store_gp:\n"
STORE_REG " $gp, 0($a0)\n"
" jr $ra\n"
" nop\n"
".set pop\n"
);
/* ASM */
void co_switch_mips(cothread_t handle, cothread_t current);
void store_gp(gpr_t *s);
cothread_t co_create(unsigned int size, void (*entrypoint)(void))
{
cothread_t handle = 0;
gpr_t *ptr;
/* Do not let context overhead or alignment wrap the allocation size. */
if (size > UINT_MAX - CONTEXT_SIZE - 1023)
return 0;
size = (size + CONTEXT_SIZE + 1023) & ~1023;
#if defined(__APPLE__) || HAVE_POSIX_MEMALIGN >= 1
if (posix_memalign(&handle, 1024, size) != 0)
return 0;
#else
handle = memalign(1024, size);
#endif
if (!handle)
return handle;
ptr = (gpr_t*)handle;
memset(ptr, 0, CONTEXT_SIZE);
/* Non-volatiles. */
/* ptr[0],..., ptr[7] -> s0,..., s7 */
store_gp(&ptr[8]); /* gp */
ptr[9] = (gpr_t)(intptr_t)((uintptr_t)ptr + size - 16); /* sp */
/* ptr[10] is fp */
ptr[11] = (gpr_t)(intptr_t)entrypoint; /* ra */
return handle;
}
cothread_t co_active(void)
{
if (!co_active_handle)
co_active_handle = co_active_buffer;
return co_active_handle;
}
void co_delete(cothread_t handle)
{
free(handle);
}
void co_switch(cothread_t handle)
{
cothread_t co_previous_handle = co_active();
co_switch_mips(co_active_handle = handle, co_previous_handle);
}
#ifdef __cplusplus
}
#endif
+22
View File
@@ -0,0 +1,22 @@
# MIPS coroutine regression
Build from the repository root with a native MIPS compiler, or cross-compile
and run the executable on the matching target (for example with QEMU user mode):
```
cc -O2 -Iinclude libco/libco.c samples/libco/mips/mips_test.c -o mips-test
./mips-test
cc -O2 -fPIC -Iinclude libco/libco.c samples/libco/mips/mips_test.c -o mips-test-pic
./mips-test-pic
```
A zero exit status means that coroutine creation, primary-context identity,
repeated switches in both directions, floating-point register preservation,
PIC global access, and allocation overflow rejection passed. The test does
not require game content. Entry functions deliberately never return.
Run both builds for big- and little-endian o32, n32 and n64 targets, with
hard and soft float where supported by the toolchain and target libraries.
For o32 hard float, include both FP32 and FP64/FPXX modes where available.
The test uses the compiler's ABI macros to select the required FP registers.
PSP and PS2 select their dedicated backends and are outside this test's scope.
+131
View File
@@ -0,0 +1,131 @@
/* MIPS libco regression: return zero only after both contexts retain their
* register state through repeated switches. See README.md for build commands. */
#include <libco.h>
#include <stdint.h>
#include <limits.h>
static cothread_t primary;
static volatile unsigned int progress;
static volatile int failed;
#if defined(__mips_hard_float)
#if defined(_ABI64) && _MIPS_SIM == _ABI64
#define FP_EVEN(OP) \
OP " $f24, 0(%0)\n" OP " $f26, 8(%0)\n" \
OP " $f28, 16(%0)\n" OP " $f30, 24(%0)\n"
#define FP_ODD(OP) \
OP " $f25, 32(%0)\n" OP " $f27, 40(%0)\n" \
OP " $f29, 48(%0)\n" OP " $f31, 56(%0)\n"
#define FP_CLOBBERS "f24", "f26", "f28", "f30", "f25", "f27", "f29", "f31"
#define FP_COUNT 8
#else
#if __mips == 1
#define FP_EVEN(OP) \
OP " $f20, 0(%0)\n" \
OP " $f21, 4(%0)\n" \
OP " $f22, 8(%0)\n" \
OP " $f23, 12(%0)\n" \
OP " $f24, 16(%0)\n" \
OP " $f25, 20(%0)\n" \
OP " $f26, 24(%0)\n" \
OP " $f27, 28(%0)\n" \
OP " $f28, 32(%0)\n" \
OP " $f29, 36(%0)\n" \
OP " $f30, 40(%0)\n" \
OP " $f31, 44(%0)\n"
#else
#define FP_EVEN(OP) \
OP " $f20, 0(%0)\n" OP " $f22, 8(%0)\n" \
OP " $f24, 16(%0)\n" OP " $f26, 24(%0)\n" \
OP " $f28, 32(%0)\n" OP " $f30, 40(%0)\n"
#endif
#define FP_ODD(OP) ""
#define FP_CLOBBERS "f20", "f22", "f24", "f26", "f28", "f30"
#define FP_COUNT 6
#endif
#if __mips == 1
#define SET_FP(P) __asm__ volatile(FP_EVEN("lwc1") "nop\n" \
: : "r"(P) : FP_CLOBBERS, "memory")
#define GET_FP(P) __asm__ volatile(FP_EVEN("swc1") \
: : "r"(P) : "memory")
#else
#define SET_FP(P) __asm__ volatile(FP_EVEN("ldc1") FP_ODD("ldc1") \
: : "r"(P) : FP_CLOBBERS, "memory")
#define GET_FP(P) __asm__ volatile(FP_EVEN("sdc1") FP_ODD("sdc1") \
: : "r"(P) : "memory")
#endif
#else
#define FP_COUNT 1
#define SET_FP(P) ((void)(P))
#define GET_FP(P) ((void)(P))
#endif
static void seed(uint64_t *values, unsigned int salt)
{
unsigned int i;
for (i = 0; i < FP_COUNT; ++i)
values[i] = UINT64_C(0x3ff123456789abcd) + ((uint64_t)salt << 32) + salt + i;
}
static int same(const uint64_t *a, const uint64_t *b)
{
#if defined(__mips_hard_float)
unsigned int i;
for (i = 0; i < FP_COUNT; ++i)
if (a[i] != b[i])
return 0;
#else
(void)a;
(void)b;
#endif
return 1;
}
static void worker(void)
{
uint64_t expected[FP_COUNT], actual[FP_COUNT];
unsigned int i;
seed(expected, 0x100);
SET_FP(expected);
for (i = 0; i < 16; ++i)
{
/* Accessing globals here also exercises a freshly entered PIC function. */
progress = i + 1;
co_switch(primary);
GET_FP(actual);
if (!same(actual, expected))
failed = 1;
}
progress = 17;
co_switch(primary);
/* Entry functions must never return. */
for (;;)
co_switch(primary);
}
int main(void)
{
uint64_t expected[FP_COUNT], actual[FP_COUNT];
unsigned int i;
cothread_t child;
primary = co_active();
if (!primary || co_active() != primary)
return 1;
if (co_create(UINT_MAX, worker))
return 2;
child = co_create(16384, worker);
if (!child)
return 3;
seed(expected, 0);
SET_FP(expected);
for (i = 0; i < 17; ++i)
{
co_switch(child);
GET_FP(actual);
if (!same(actual, expected) || progress != i + 1 || failed ||
co_active() != primary)
return 4;
}
co_delete(child);
return 0;
}