Skip to content

Commit 7b77437

Browse files
committed
riscv: switch to tasks-based scheduler
This is only supported for RV32 at the moment. RV64 can be added at a later time.
1 parent 4ddb107 commit 7b77437

File tree

5 files changed

+139
-1
lines changed

5 files changed

+139
-1
lines changed

src/internal/task/task_stack_arm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// +build scheduler.tasks,arm,!cortexm,!avr,!xtensa
1+
// +build scheduler.tasks,arm,!cortexm,!avr,!xtensa,!tinygo.riscv
22

33
package task
44

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
.section .text.tinygo_startTask
2+
.global tinygo_startTask
3+
.type tinygo_startTask, %function
4+
tinygo_startTask:
5+
// Small assembly stub for starting a goroutine. This is already run on the
6+
// new stack, with the callee-saved registers already loaded.
7+
// Most importantly, s0 contains the pc of the to-be-started function and s1
8+
// contains the only argument it is given. Multiple arguments are packed
9+
// into one by storing them in a new allocation.
10+
11+
// Set the first argument of the goroutine start wrapper, which contains all
12+
// the arguments.
13+
mv a0, s1
14+
15+
// Branch to the "goroutine start" function. Use jalr to write the return
16+
// address to ra so we'll return here after the goroutine exits.
17+
jalr s0
18+
19+
// After return, exit this goroutine. This is a tail call.
20+
tail tinygo_pause
21+
22+
.section .text.tinygo_swapTask
23+
.global tinygo_swapTask
24+
.type tinygo_swapTask, %function
25+
tinygo_swapTask:
26+
// This function gets the following parameters:
27+
// a0 = newStack uintptr
28+
// a1 = oldStack *uintptr
29+
30+
// Push all callee-saved registers.
31+
addi sp, sp, -52
32+
sw ra, 48(sp)
33+
sw s11, 44(sp)
34+
sw s10, 40(sp)
35+
sw s9, 36(sp)
36+
sw s8, 32(sp)
37+
sw s7, 28(sp)
38+
sw s6, 24(sp)
39+
sw s5, 20(sp)
40+
sw s4, 16(sp)
41+
sw s3, 12(sp)
42+
sw s2, 8(sp)
43+
sw s1, 4(sp)
44+
sw s0, (sp)
45+
46+
// Save the current stack pointer in oldStack.
47+
sw sp, 0(a1)
48+
49+
// Switch to the new stack pointer.
50+
mv sp, a0
51+
52+
// Pop all saved registers from this new stack.
53+
lw ra, 48(sp)
54+
lw s11, 44(sp)
55+
lw s10, 40(sp)
56+
lw s9, 36(sp)
57+
lw s8, 32(sp)
58+
lw s7, 28(sp)
59+
lw s6, 24(sp)
60+
lw s5, 20(sp)
61+
lw s4, 16(sp)
62+
lw s3, 12(sp)
63+
lw s2, 8(sp)
64+
lw s1, 4(sp)
65+
lw s0, (sp)
66+
addi sp, sp, 52
67+
68+
// Return into the task.
69+
ret
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// +build scheduler.tasks,tinygo.riscv
2+
3+
package task
4+
5+
import "unsafe"
6+
7+
var systemStack uintptr
8+
9+
// calleeSavedRegs is the list of registers that must be saved and restored when
10+
// switching between tasks. Also see scheduler_riscv.S that relies on the
11+
// exact layout of this struct.
12+
type calleeSavedRegs struct {
13+
s0 uintptr // x8 (fp)
14+
s1 uintptr // x9
15+
s2 uintptr // x18
16+
s3 uintptr // x19
17+
s4 uintptr // x20
18+
s5 uintptr // x21
19+
s6 uintptr // x22
20+
s7 uintptr // x23
21+
s8 uintptr // x24
22+
s9 uintptr // x25
23+
s10 uintptr // x26
24+
s11 uintptr // x27
25+
26+
pc uintptr
27+
}
28+
29+
// archInit runs architecture-specific setup for the goroutine startup.
30+
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
31+
// Store the initial sp for the startTask function (implemented in assembly).
32+
s.sp = uintptr(unsafe.Pointer(r))
33+
34+
// Initialize the registers.
35+
// These will be popped off of the stack on the first resume of the goroutine.
36+
37+
// Start the function at tinygo_startTask (defined in src/internal/task/task_stack_riscv.S).
38+
// This assembly code calls a function (passed in s0) with a single argument
39+
// (passed in s1). After the function returns, it calls Pause().
40+
r.pc = uintptr(unsafe.Pointer(&startTask))
41+
42+
// Pass the function to call in s0.
43+
// This function is a compiler-generated wrapper which loads arguments out
44+
// of a struct pointer. See createGoroutineStartWrapper (defined in
45+
// compiler/goroutine.go) for more information.
46+
r.s0 = fn
47+
48+
// Pass the pointer to the arguments struct in s1.
49+
r.s1 = uintptr(args)
50+
}
51+
52+
func (s *state) resume() {
53+
swapTask(s.sp, &systemStack)
54+
}
55+
56+
func (s *state) pause() {
57+
newStack := systemStack
58+
systemStack = 0
59+
swapTask(newStack, &s.sp)
60+
}
61+
62+
// SystemStack returns the system stack pointer when called from a task stack.
63+
// When called from the system stack, it returns 0.
64+
func SystemStack() uintptr {
65+
return systemStack
66+
}

targets/riscv.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
],
1818
"extra-files": [
1919
"src/device/riscv/start.S",
20+
"src/internal/task/task_stack_tinygoriscv.S",
2021
"src/runtime/gc_riscv.S",
2122
"src/device/riscv/handleinterrupt.S"
2223
],

targets/riscv32.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"inherits": ["riscv"],
33
"llvm-target": "riscv32--none",
44
"build-tags": ["tinygo.riscv32"],
5+
"scheduler": "tasks",
6+
"default-stack-size": 2048,
57
"cflags": [
68
"--target=riscv32--none",
79
"-march=rv32imac",

0 commit comments

Comments
 (0)