Skip to content

Commit 2635ef6

Browse files
Add RISC-V architecture support (#3125)
This commit adds full support for RISC-V 64-bit architecture to brpc. Changes include: - Add RISC-V atomic operations implementation - Add RISC-V architecture detection in build system - Add RISC-V context switching (bthread support) - Add RISC-V clock cycle counter support (rdcycle) - Update CMake and Makefile for RISC-V compilation All core functionalities have been tested and verified in QEMU RISC-V environment, including: - Atomic operations (32-bit and 64-bit) - Memory barriers - Context switching - Clock cycle counting Co-authored-by: gong-flying <gongxiaofei24@iscas.ac.cn>
1 parent 8b6a9c0 commit 2635ef6

File tree

11 files changed

+316
-3
lines changed

11 files changed

+316
-3
lines changed

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
170170
elseif((CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64"))
171171
# segmentation fault in libcontext
172172
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-gcse")
173+
elseif((CMAKE_SYSTEM_PROCESSOR MATCHES "riscv64"))
174+
# RISC-V specific optimizations
175+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv64gc")
173176
endif()
174177
if(NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0))
175178
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-aligned-new")

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ ifeq ($(shell test $(GCC_VERSION) -ge 40400; echo $$?),0)
4444
CXXFLAGS+=-msse4 -msse4.2
4545
endif
4646
endif
47+
# RISC-V specific optimizations
48+
ifeq ($(shell uname -m),riscv64)
49+
CXXFLAGS+=-march=rv64gc
50+
endif
4751
#not solved yet
4852
ifeq ($(CC),gcc)
4953
ifeq ($(shell test $(GCC_VERSION) -ge 70000; echo $$?),0)

src/bthread/context.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,3 +900,91 @@ __asm (
900900
);
901901

902902
#endif
903+
904+
#if defined(BTHREAD_CONTEXT_PLATFORM_linux_riscv64) && defined(BTHREAD_CONTEXT_COMPILER_gcc)
905+
__asm (
906+
".text\n"
907+
".align 3\n"
908+
".global bthread_jump_fcontext\n"
909+
".type bthread_jump_fcontext, %function\n"
910+
"bthread_jump_fcontext:\n"
911+
" addi sp, sp, -160\n"
912+
" # save callee-saved registers\n"
913+
" sd s0, 64(sp)\n"
914+
" sd s1, 72(sp)\n"
915+
" sd s2, 80(sp)\n"
916+
" sd s3, 88(sp)\n"
917+
" sd s4, 96(sp)\n"
918+
" sd s5, 104(sp)\n"
919+
" sd s6, 112(sp)\n"
920+
" sd s7, 120(sp)\n"
921+
" sd s8, 128(sp)\n"
922+
" sd s9, 136(sp)\n"
923+
" sd s10, 144(sp)\n"
924+
" sd s11, 152(sp)\n"
925+
" sd ra, 0(sp)\n"
926+
" sd fp, 8(sp)\n"
927+
" # save floating point registers\n"
928+
" fsd fs0, 16(sp)\n"
929+
" fsd fs1, 24(sp)\n"
930+
" fsd fs2, 32(sp)\n"
931+
" fsd fs3, 40(sp)\n"
932+
" fsd fs4, 48(sp)\n"
933+
" fsd fs5, 56(sp)\n"
934+
" # store current stack pointer\n"
935+
" sd sp, 0(a0)\n"
936+
" # load new stack pointer\n"
937+
" mv sp, a1\n"
938+
" # restore floating point registers\n"
939+
" fld fs0, 16(sp)\n"
940+
" fld fs1, 24(sp)\n"
941+
" fld fs2, 32(sp)\n"
942+
" fld fs3, 40(sp)\n"
943+
" fld fs4, 48(sp)\n"
944+
" fld fs5, 56(sp)\n"
945+
" # restore callee-saved registers\n"
946+
" ld s0, 64(sp)\n"
947+
" ld s1, 72(sp)\n"
948+
" ld s2, 80(sp)\n"
949+
" ld s3, 88(sp)\n"
950+
" ld s4, 96(sp)\n"
951+
" ld s5, 104(sp)\n"
952+
" ld s6, 112(sp)\n"
953+
" ld s7, 120(sp)\n"
954+
" ld s8, 128(sp)\n"
955+
" ld s9, 136(sp)\n"
956+
" ld s10, 144(sp)\n"
957+
" ld s11, 152(sp)\n"
958+
" ld ra, 0(sp)\n"
959+
" ld fp, 8(sp)\n"
960+
" # restore stack pointer\n"
961+
" addi sp, sp, 160\n"
962+
" # return value in a0\n"
963+
" mv a0, a2\n"
964+
" # jump to new context\n"
965+
" ret\n"
966+
);
967+
968+
__asm (
969+
".text\n"
970+
".align 3\n"
971+
".global bthread_make_fcontext\n"
972+
".type bthread_make_fcontext, %function\n"
973+
"bthread_make_fcontext:\n"
974+
" # align stack to 16-byte boundary\n"
975+
" andi a0, a0, -16\n"
976+
" addi a0, a0, -160\n"
977+
" # store function pointer at the top of stack\n"
978+
" sd a2, 0(a0)\n"
979+
" # store finish function address\n"
980+
" la t0, finish\n"
981+
" sd t0, 8(a0)\n"
982+
" # return pointer to context data\n"
983+
" ret\n"
984+
"finish:\n"
985+
" # exit with code 0\n"
986+
" li a0, 0\n"
987+
" # call exit\n"
988+
" call _exit\n"
989+
);
990+
#endif

src/bthread/context.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
#elif __loongarch64
4343
#define BTHREAD_CONTEXT_PLATFORM_linux_loongarch64
4444
#define BTHREAD_CONTEXT_CALL_CONVENTION
45+
#elif __riscv
46+
#define BTHREAD_CONTEXT_PLATFORM_linux_riscv64
47+
#define BTHREAD_CONTEXT_CALL_CONVENTION
4548
#endif
4649

4750
#elif defined(__MINGW32__) || defined (__MINGW64__)

src/bthread/processor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
# ifndef cpu_relax
2929
#if defined(ARCH_CPU_ARM_FAMILY)
3030
# define cpu_relax() asm volatile("yield\n": : :"memory")
31+
#elif defined(ARCH_CPU_RISCV_FAMILY)
32+
# define cpu_relax() asm volatile("fence.i\n": : :"memory")
3133
#elif defined(ARCH_CPU_LOONGARCH64_FAMILY)
3234
# define cpu_relax() asm volatile("nop\n": : :"memory");
3335
#else

src/bthread/task_group.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ AtomicInteger128::Value AtomicInteger128::load() const {
101101
#endif // __x86_64__
102102
return {value[0], value[1]};
103103
#else // __x86_64__ || __ARM_NEON
104-
BAIDU_SCOPED_LOCK(_mutex);
104+
// RISC-V and other architectures use mutex fallback
105+
BAIDU_SCOPED_LOCK(const_cast<FastPthreadMutex&>(_mutex));
105106
return _value;
106107
#endif // __x86_64__ || __ARM_NEON
107108
}
@@ -114,7 +115,8 @@ void AtomicInteger128::store(Value value) {
114115
int64x2_t v = vld1q_s64(reinterpret_cast<int64_t*>(&value));
115116
vst1q_s64(reinterpret_cast<int64_t*>(&_value), v);
116117
#else
117-
BAIDU_SCOPED_LOCK(_mutex);
118+
// RISC-V and other architectures use mutex fallback
119+
BAIDU_SCOPED_LOCK(const_cast<FastPthreadMutex&>(_mutex));
118120
_value = value;
119121
#endif // __x86_64__ || __ARM_NEON
120122
}

src/bthread/task_group.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class AtomicInteger128 {
7373

7474
private:
7575
Value _value{};
76-
// Used to protect `_cpu_time_stat' when __x86_64__ and __ARM_NEON is not defined.
76+
// Used to protect `_cpu_time_stat' when __x86_64__, __ARM_NEON, and __riscv is not defined.
7777
FastPthreadMutex _mutex;
7878
};
7979

src/butil/atomicops.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ Atomic64 Release_Load(volatile const Atomic64* ptr);
157157
#include "butil/atomicops_internals_mips_gcc.h"
158158
#elif defined(COMPILER_GCC) && defined(ARCH_CPU_LOONGARCH64_FAMILY)
159159
#include "butil/atomicops_internals_loongarch64_gcc.h"
160+
#elif defined(COMPILER_GCC) && defined(ARCH_CPU_RISCV_FAMILY)
161+
#include "butil/atomicops_internals_riscv_gcc.h"
160162
#else
161163
#error "Atomic operations are not supported on your platform"
162164
#endif
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// Copyright 2024 The Apache Software Foundation. All rights reserved.
2+
// Use of this source code is governed by the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
// This file is an internal atomic implementation, use butil/atomicops.h instead.
6+
// RISC-V architecture specific atomic operations implementation using GCC intrinsics.
7+
8+
#ifndef BUTIL_ATOMICOPS_INTERNALS_RISCV_GCC_H_
9+
#define BUTIL_ATOMICOPS_INTERNALS_RISCV_GCC_H_
10+
11+
namespace butil {
12+
namespace subtle {
13+
14+
inline void MemoryBarrier() {
15+
__asm__ __volatile__ ("fence" ::: "memory"); // NOLINT
16+
}
17+
18+
// RISC-V atomic operations using GCC built-in functions
19+
// These are implemented using the standard GCC atomic built-ins which
20+
// are supported on RISC-V since GCC 7.1+
21+
22+
inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr,
23+
Atomic32 old_value,
24+
Atomic32 new_value) {
25+
Atomic32 prev_value;
26+
do {
27+
if (__sync_bool_compare_and_swap(ptr, old_value, new_value))
28+
return old_value;
29+
prev_value = *ptr;
30+
} while (prev_value == old_value);
31+
return prev_value;
32+
}
33+
34+
inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr,
35+
Atomic32 new_value) {
36+
Atomic32 old_value;
37+
do {
38+
old_value = *ptr;
39+
} while (!__sync_bool_compare_and_swap(ptr, old_value, new_value));
40+
return old_value;
41+
}
42+
43+
inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr,
44+
Atomic32 increment) {
45+
return Barrier_AtomicIncrement(ptr, increment);
46+
}
47+
48+
inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr,
49+
Atomic32 increment) {
50+
for (;;) {
51+
// Atomic exchange the old value with an incremented one.
52+
Atomic32 old_value = *ptr;
53+
Atomic32 new_value = old_value + increment;
54+
if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) {
55+
// The exchange took place as expected.
56+
return new_value;
57+
}
58+
// Otherwise, *ptr changed mid-loop and we need to retry.
59+
}
60+
}
61+
62+
inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr,
63+
Atomic32 old_value,
64+
Atomic32 new_value) {
65+
// Since NoBarrier_CompareAndSwap uses __sync_bool_compare_and_swap, which
66+
// is a full memory barrier, none is needed here or below in Release.
67+
return NoBarrier_CompareAndSwap(ptr, old_value, new_value);
68+
}
69+
70+
inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr,
71+
Atomic32 old_value,
72+
Atomic32 new_value) {
73+
return NoBarrier_CompareAndSwap(ptr, old_value, new_value);
74+
}
75+
76+
inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) {
77+
*ptr = value;
78+
}
79+
80+
inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) {
81+
*ptr = value;
82+
MemoryBarrier();
83+
}
84+
85+
inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) {
86+
MemoryBarrier();
87+
*ptr = value;
88+
}
89+
90+
inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) {
91+
return *ptr;
92+
}
93+
94+
inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) {
95+
Atomic32 value = *ptr;
96+
MemoryBarrier();
97+
return value;
98+
}
99+
100+
inline Atomic32 Release_Load(volatile const Atomic32* ptr) {
101+
MemoryBarrier();
102+
return *ptr;
103+
}
104+
105+
// 64-bit versions of the operations.
106+
// See the 32-bit versions for comments.
107+
108+
inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr,
109+
Atomic64 old_value,
110+
Atomic64 new_value) {
111+
Atomic64 prev_value;
112+
do {
113+
if (__sync_bool_compare_and_swap(ptr, old_value, new_value))
114+
return old_value;
115+
prev_value = *ptr;
116+
} while (prev_value == old_value);
117+
return prev_value;
118+
}
119+
120+
inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr,
121+
Atomic64 new_value) {
122+
Atomic64 old_value;
123+
do {
124+
old_value = *ptr;
125+
} while (!__sync_bool_compare_and_swap(ptr, old_value, new_value));
126+
return old_value;
127+
}
128+
129+
inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr,
130+
Atomic64 increment) {
131+
return Barrier_AtomicIncrement(ptr, increment);
132+
}
133+
134+
inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr,
135+
Atomic64 increment) {
136+
for (;;) {
137+
// Atomic exchange the old value with an incremented one.
138+
Atomic64 old_value = *ptr;
139+
Atomic64 new_value = old_value + increment;
140+
if (__sync_bool_compare_and_swap(ptr, old_value, new_value)) {
141+
// The exchange took place as expected.
142+
return new_value;
143+
}
144+
// Otherwise, *ptr changed mid-loop and we need to retry.
145+
}
146+
}
147+
148+
inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr,
149+
Atomic64 old_value,
150+
Atomic64 new_value) {
151+
return NoBarrier_CompareAndSwap(ptr, old_value, new_value);
152+
}
153+
154+
inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr,
155+
Atomic64 old_value,
156+
Atomic64 new_value) {
157+
return NoBarrier_CompareAndSwap(ptr, old_value, new_value);
158+
}
159+
160+
inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) {
161+
*ptr = value;
162+
}
163+
164+
inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) {
165+
*ptr = value;
166+
MemoryBarrier();
167+
}
168+
169+
inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) {
170+
MemoryBarrier();
171+
*ptr = value;
172+
}
173+
174+
inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) {
175+
return *ptr;
176+
}
177+
178+
inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) {
179+
Atomic64 value = *ptr;
180+
MemoryBarrier();
181+
return value;
182+
}
183+
184+
inline Atomic64 Release_Load(volatile const Atomic64* ptr) {
185+
MemoryBarrier();
186+
return *ptr;
187+
}
188+
189+
} // namespace butil::subtle
190+
} // namespace butil
191+
192+
#endif // BUTIL_ATOMICOPS_INTERNALS_RISCV_GCC_H_

src/butil/build_config.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,16 @@
138138
#define ARCH_CPU_LOONGARCH64 1
139139
#define ARCH_CPU_64_BITS 1
140140
#define ARCH_CPU_LITTLE_ENDIAN 1
141+
#elif defined(__riscv)
142+
#define ARCH_CPU_RISCV_FAMILY 1
143+
#if defined(__riscv_xlen) && (__riscv_xlen == 64)
144+
#define ARCH_CPU_RISCV64 1
145+
#define ARCH_CPU_64_BITS 1
146+
#else
147+
#define ARCH_CPU_RISCV32 1
148+
#define ARCH_CPU_32_BITS 1
149+
#endif
150+
#define ARCH_CPU_LITTLE_ENDIAN 1
141151
#else
142152
#error Please add support for your architecture in butil/build_config.h
143153
#endif

0 commit comments

Comments
 (0)