Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions include/fastcontext/riscv-ucontext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* Portions of this file are Copyright (c) 2025 Tactical Computing Labs, LLC; see COPYING */
#include "qthread/common.h"

#include <stddef.h>
#include <stdint.h>

#include "qt_visibility.h"

#define setcontext(u) qt_setmctxt(&(u)->mc)
#define getcontext(u) qt_getmctxt(&(u)->mc)
typedef struct mctxt mctxt_t;
typedef struct uctxt uctxt_t;

struct mctxt {
/* Saved main processor registers. */
#ifdef NEEDARMA64CONTEXT
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just re-using the same mctxt that's used for ARM, and I'm not sure that's right. The NEEDARMA64CONTEXT macro should never be set here and it appears the corresponding assembly is saving more than 16 registers (like it would in the 32-bit case) so this seems wrong.

uint64_t regs[32]; /* callee saves x0-x30, SP */
uint64_t fpu_regs[32]; /* 32 64 bit FPU Registers */
#else
uint32_t regs[16]; /* callee saves r0-r15 */
#endif
char first;
};

struct uctxt {
struct {
void *ss_sp;
size_t ss_size;
} uc_stack;

// sigset_t uc_sigmask;
mctxt_t mc;
struct uctxt *uc_link; /* unused */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't worry about this for this PR, but I need to remember to come back and remove these uc_link struct members. At a glance it looks like they're only ever used if we're using the system swapcontext. If that's actually the case, we should only include them in that case.

};

int INTERNAL qt_swapctxt(uctxt_t *, uctxt_t *);
void INTERNAL qt_makectxt(uctxt_t *, void (*)(void), int, ...);
int INTERNAL qt_getmctxt(mctxt_t *);
void INTERNAL qt_setmctxt(mctxt_t *);
/* vim:set expandtab: */
7 changes: 7 additions & 0 deletions include/fastcontext/taskimpl.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* Portions of this file are Copyright (c) 2025 Tactical Computing Labs, LLC; see COPYING */

#ifndef TASKIMPL_H
#define TASKIMPL_H

Expand All @@ -22,6 +24,11 @@
#define NEEDARMMAKECONTEXT
#define NEEDSWAPCONTEXT
#include "arm-ucontext.h"
#elif (QTHREAD_ASSEMBLY_ARCH == QTHREAD_ARM)
#include <stdarg.h>
#define NEEDRISCVMAKECONTEXT
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused macro?

#define NEEDSWAPCONTEXT
#include "riscv-ucontext.h"
#elif (QTHREAD_ASSEMBLY_ARCH == QTHREAD_ARMV8_A64)
#include <stdarg.h>
#define NEEDARMA64MAKECONTEXT
Expand Down
5 changes: 5 additions & 0 deletions include/qt_atomics.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* Portions of this file are Copyright (c) 2025 Tactical Computing Labs, LLC; see COPYING */

#ifndef QT_ATOMICS_H
#define QT_ATOMICS_H

Expand Down Expand Up @@ -29,6 +31,9 @@
QTHREAD_ASSEMBLY_ARCH == QTHREAD_ARMV8_A64
#define SPINLOCK_BODY() \
do { __asm__ __volatile__("yield" ::: "memory"); } while (0)
#elif QTHREAD_ASSEMBLY_ARCH == QTHREAD_RISCV
#define SPINLOCK_BODY() \
do { atomic_thread_fence(memory_order_acq_rel); } while (0)
Copy link
Collaborator

@insertinterestingnamehere insertinterestingnamehere Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not right. It should be some kind of pause instruction, not a memory fence. The fences are handled elsewhere.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chased this down a bit more. The correct assembly seems to be the same as the AMD64 branch. The only catch is that clang needs explicit permission to use the zihintpause extension so -march=rv64g_zihintpause or something like it needs to be spun into the CMake file. I'm fine taking care of this later, but I figured I'd mention of it in case you're using clang.

#elif QTHREAD_ASSEMBLY_ARCH == QTHREAD_POWERPC64 || \
QTHREAD_ASSEMBLY_ARCH == QTHREAD_POWERPC32
// For whatever reason the 29 (mdoio) version of this instruction performed
Expand Down
4 changes: 4 additions & 0 deletions include/qthread/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* ------------------------------------------------------------------
*/

/* Portions of this file are Copyright (c) 2025 Tactical Computing Labs, LLC; see COPYING */

#ifndef QTHREAD_COMMON_H
#define QTHREAD_COMMON_H

Expand Down Expand Up @@ -53,6 +55,8 @@
#define QTHREAD_ASSEMBLY_ARCH QTHREAD_ARM
#elif defined(_ARCHPPC)
#define QTHREAD_ASSEMBLY_ARCH QTHREAD_POWERPC32
#elif defined(__riscv)
#define QTHREAD_ASSEMBLY_ARCH QTHREAD_RISCV
#else
#error "Unsupported architecture"
#endif
Expand Down
25 changes: 25 additions & 0 deletions src/cacheline.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* Portions of this file are Copyright (c) 2025 Tactical Computing Labs, LLC; see COPYING */

#ifdef DEBUG_CPUID
#include <stdio.h>
#endif
Expand All @@ -14,6 +16,23 @@ enum vendor { AMD, Intel, Unknown };
static int cacheline_bytes = 0;

#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#if defined(__riscv)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please include this as a part of the big if/elif/elif... below instead of nesting the ifdefs further


#include <unistd.h>

// https://matrix89.github.io/writes/writes/experiments-in-riscv/
//
static unsigned int cpuid() {
register unsigned int hart_id = 0;
__asm__ __volatile__("csrr %0, mhartid" : "=r" (hart_id) : : );
return hart_id;
}

static void figure_out_cacheline_size(void) {
cacheline_bytes = sysconf (_SC_LEVEL1_DCACHE_LINESIZE);
}

#else

#if ((QTHREAD_ASSEMBLY_ARCH == QTHREAD_IA32) || \
(QTHREAD_ASSEMBLY_ARCH == QTHREAD_AMD64)) && \
Expand All @@ -23,6 +42,8 @@ static void cpuid(unsigned int const op,
unsigned int *ebx_ptr,
unsigned int *ecx_ptr,
unsigned int *edx_ptr) {


#if (QTHREAD_ASSEMBLY_ARCH == QTHREAD_IA32) && defined(__PIC__)
unsigned int eax, ebx, ecx, edx;
unsigned int pic_ebx = 0;
Expand All @@ -43,13 +64,15 @@ static void cpuid(unsigned int const op,
: "=a"(*eax_ptr), "=b"(*ebx_ptr), "=c"(*ecx_ptr), "=d"(*edx_ptr)
: "a"(op));
#endif /* if (QTHREAD_ASSEMBLY_ARCH == QTHREAD_IA32) && defined(__PIC__) */

}

static void cpuid4(int const cache,
unsigned int *eax_ptr,
unsigned int *ebx_ptr,
unsigned int *ecx_ptr,
unsigned int *edx_ptr) {

#if (QTHREAD_ASSEMBLY_ARCH == QTHREAD_IA32) && defined(__PIC__)
unsigned int eax, ebx, ecx, edx;
unsigned int pic_ebx = 0;
Expand Down Expand Up @@ -339,6 +362,8 @@ static void figure_out_cacheline_size(void) {
(QTHREAD_ASSEMBLY_ARCH == QTHREAD_POWERPC64) */
}

#endif

/* returns the cache line size */
int API_FUNC qthread_cacheline(void) {
if (cacheline_bytes == 0) {
Expand Down
115 changes: 115 additions & 0 deletions src/fastcontext/asm.S
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#if 0
/* Portions of this file are Copyright (c) 2025 Tactical Computing Labs, LLC; see COPYING */
/* Portions of this file are Copyright (c) 2005-2006 Russ Cox, MIT; see COPYRIGHT */
/* Portions of this file are Copyright Sandia National Laboratories */
#endif
Expand Down Expand Up @@ -50,6 +51,10 @@
# define NEEDARMCONTEXT 1
# define SET qt_setmctxt
# define GET qt_getmctxt
# elif (QTHREAD_ASSEMBLY_ARCH == QTHREAD_RISCV)
# define NEEDRISCVCONTEXT 1
# define SET qt_setmctxt
# define GET qt_getmctxt
# elif (QTHREAD_ASSEMBLY_ARCH == QTHREAD_ARMV8_A64)
# define NEEDARMA64CONTEXT 1
# define SET qt_setmctxt
Expand Down Expand Up @@ -636,6 +641,116 @@ SET:
bx lr
#endif

#ifdef NEEDRISCVCONTEXT
/* Register Usage:
*
* x1/ra - return address
* x2/sp - stack pointer
* x3/gp - global pointer
* x4/tp - thread pointer
* x5/t0 - temporary 0
* x6/t1 - temporary 1
* x7/t2 - temporary 2
* x8/s0/fp - saved register 0 / frame pointer
* x9/s1 - saved register 0
* x10/a0 - function argument 0
* x11/a1 - function argument 1
* x12/a2 - function argument 2
* x13/a3 - function argument 3
* x14/a4 - function argument 4
* x15/a5 - function argument 5
* x16/a6 - function argument 6
* x17/a7 - function argument 7
* x18/s2 - saved register 2
* x19/s3 - saved register 3
* x20/s4 - saved register 4
* x21/s5 - saved register 5
* x22/s6 - saved register 6
* x22/s7 - saved register 7
* x22/s8 - saved register 8
* x22/s9 - saved register 9
* x22/s10 - saved register 10
* x22/s11 - saved register 11
* x22/t3 - temporary 3
* x22/t4 - temporary 4
* x22/t5 - temporary 5
* x22/t6 - temporary 6
*/
.globl GET
.global GET
.type GET, %function
GET:
st a1, [a0,#8] _(/* ARG 1 */)
st a2, [a0,#12] _(/* ARG 2 */)
st a3, [a0,#16] _(/* ARG 3 */)
st a4, [a0,#20] _(/* ARG 4 */)
st a5, [a0,#24] _(/* ARG 5 */)
st a6, [a0,#28] _(/* ARG 8 */)
st a7, [a0,#32] _(/* ARG 7 */)
st s2 [a0,#36] _(/* saved registers */
st s3 [a0,#40] _(/* saved registers */
st s4 [a0,#44] _(/* saved registers */
st s5 [a0,#48] _(/* saved registers */
st s6 [a0,#52] _(/* saved registers */
st s7 [a0,#56] _(/* saved registers */
st s8 [a0,#60] _(/* saved registers */
st s9 [a0,#64] _(/* saved registers */
st s10 [a0,#68] _(/* saved registers */
st s11 [a0,#72] _(/* saved registers */
st t0 [a0,#76] _(/* saved registers */
st t1 [a0,#80] _(/* saved registers */
st t2 [a0,#84] _(/* saved registers */
st t3 [a0,#88] _(/* saved registers */
st t4 [a0,#92] _(/* saved registers */
st t5 [a0,#96] _(/* saved registers */
st t6 [a0,#100] _(/* saved registers */
st sp, [a0,#104] _(/* SP */)
st ra, [a0,#108] _(/* LR */)
st gp, [a0,#112]
st tp, [a0,#116]
_(/*) store 1 as a0-to-restore */)
mov a1, #1
st a1, [a0] _(/* arg 1 / RET 1 */)
_(/*) return 0 */)
mov a0, #0
ret

.globl SET
.global SET
.type SET, %function
SET:
ld a1, [a0,#4] _(/* ARG 1 */)
ld a2, [a0,#8] _(/* ARG 2 */)
ld a3, [a0,#12] _(/* ARG 3 */)
ld a4, [a0,#16] _(/* ARG 4 */)
ld a5, [a0,#20] _(/* ARG 5 */)
ld a6, [a0,#24] _(/* ARG 8 */)
ld a7, [a0,#28] _(/* ARG 7 */)
ld s2 [a0,#32] _(/* saved registers */
ld s3 [a0,#36] _(/* saved registers */
ld s4 [a0,#40] _(/* saved registers */
ld s5 [a0,#44] _(/* saved registers */
ld s6 [a0,#48] _(/* saved registers */
ld s7 [a0,#52] _(/* saved registers */
ld s8 [a0,#56] _(/* saved registers */
ld s9 [a0,#60] _(/* saved registers */
ld s10 [a0,#64] _(/* saved registers */
ld s11 [a0,#68] _(/* saved registers */
ld t0 [a0,#72] _(/* saved registers */
ld t1 [a0,#76] _(/* saved registers */
ld t2 [a0,#80] _(/* saved registers */
ld t3 [a0,#84] _(/* saved registers */
ld t4 [a0,#88] _(/* saved registers */
ld t5 [a0,#92] _(/* saved registers */
ld t6 [a0,#96] _(/* saved registers */
ld sp, [a0,#100] _(/* SP */)
ld ra, [a0,#104] _(/* LR */)
ld gp, [a0,#108]
ld tp, [a0,#112]
ld a0, [a0]
ret
#endif

#if defined(__ELF__)
.section .note.GNU-stack,"",%progbits
#endif
14 changes: 14 additions & 0 deletions src/syncvar.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* System Headers */
/* Portions of this file are Copyright (c) 2025 Tactical Computing Labs, LLC; see COPYING */
#include <limits.h> /* for INT_MAX */
#include <stdint.h>

Expand Down Expand Up @@ -80,6 +81,19 @@ extern unsigned int QTHREAD_LOCKING_STRIPES;
#if (QTHREAD_ASSEMBLY_ARCH == QTHREAD_AMD64 || \
QTHREAD_ASSEMBLY_ARCH == QTHREAD_ARM || \
QTHREAD_ASSEMBLY_ARCH == QTHREAD_ARMV8_A64)
#define UNLOCK_THIS_UNMODIFIED_SYNCVAR(addr, unlocked) \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The risc-v branch is identical to the AMD64 and ARM branches so please just include QTHREAD_RISCV in that branch instead of making a separate one.

do { \
atomic_store_explicit( \
(_Atomic uint64_t *)&(addr)->u.w, (unlocked), memory_order_relaxed); \
} while (0)
#define UNLOCK_THIS_MODIFIED_SYNCVAR(addr, val, state) \
do { \
MACHINE_FENCE; \
atomic_store_explicit((_Atomic uint64_t *)&(addr)->u.w, \
BUILD_UNLOCKED_SYNCVAR(val, state), \
memory_order_relaxed); \
} while (0)
#elif (QTHREAD_RISCV)
#define UNLOCK_THIS_UNMODIFIED_SYNCVAR(addr, unlocked) \
do { \
atomic_store_explicit( \
Expand Down
Loading