Skip to content

Commit a4e95f9

Browse files
committed
asyncify wip
1 parent b40703e commit a4e95f9

File tree

8 files changed

+222
-7
lines changed

8 files changed

+222
-7
lines changed

compileopts/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
150150
// target.
151151
func (c *Config) FuncImplementation() string {
152152
switch c.Scheduler() {
153-
case "tasks":
153+
case "tasks", "asyncify":
154154
// A func value is implemented as a pair of pointers:
155155
// {context, function pointer}
156156
// where the context may be a pointer to a heap-allocated struct

compileopts/options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
var (
1010
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
11-
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
11+
validSchedulerOptions = []string{"none", "tasks", "coroutines", "asyncify"}
1212
validSerialOptions = []string{"none", "uart", "usb"}
1313
validPrintSizeOptions = []string{"none", "short", "full"}
1414
validPanicStrategyOptions = []string{"print", "trap"}

compiler/goroutine.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (b *builder) createGo(instr *ssa.Go) {
9292
switch b.Scheduler {
9393
case "none", "coroutines":
9494
// There are no additional parameters needed for the goroutine start operation.
95-
case "tasks":
95+
case "tasks", "asyncify":
9696
// Add the function pointer as a parameter to start the goroutine.
9797
params = append(params, funcPtr)
9898
default:
@@ -107,7 +107,7 @@ func (b *builder) createGo(instr *ssa.Go) {
107107
paramBundle := b.emitPointerPack(params)
108108
var callee, stackSize llvm.Value
109109
switch b.Scheduler {
110-
case "none", "tasks":
110+
case "none", "tasks", "asyncify":
111111
callee = b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos())
112112
if b.AutomaticStackSize {
113113
// The stack size is not known until after linking. Call a dummy
@@ -119,7 +119,7 @@ func (b *builder) createGo(instr *ssa.Go) {
119119
} else {
120120
// The stack size is fixed at compile time. By emitting it here as a
121121
// constant, it can be optimized.
122-
if b.Scheduler == "tasks" && b.DefaultStackSize == 0 {
122+
if (b.Scheduler == "tasks" || b.Scheduler == "asyncify") && b.DefaultStackSize == 0 {
123123
b.addError(instr.Pos(), "default stack size for goroutines is not set")
124124
}
125125
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)

src/internal/task/task_asyncify.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// +build scheduler.asyncify
2+
3+
package task
4+
5+
import (
6+
"unsafe"
7+
)
8+
9+
// state is a structure which holds a reference to the state of the task.
10+
// When the task is suspended, the stack pointers are saved here.
11+
type state struct {
12+
// entry is the entry function of the task.
13+
// This is needed every time the function is invoked so that asyncify knows what to rewind.
14+
entry uintptr
15+
16+
// args are a pointer to a struct holding the arguments of the function.
17+
args unsafe.Pointer
18+
19+
// stackState is the state of the stack while unwound.
20+
stackState
21+
}
22+
23+
// stackState is the saved state of a stack while unwound.
24+
// The stack is arranged with asyncify at the bottom, C stack at the top, and a gap of available stack space between the two.
25+
type stackState struct {
26+
// asyncify is the stack pointer of the asyncify stack.
27+
// This starts from the bottom and grows upwards.
28+
asyncifysp uintptr
29+
30+
// asyncify is stack pointer of the C stack.
31+
// This starts from the top and grows downwards.
32+
csp uintptr
33+
}
34+
35+
// start creates and starts a new goroutine with the given function and arguments.
36+
// The new goroutine is immediately started.
37+
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
38+
t := &Task{}
39+
t.state.initialize(fn, args, stackSize)
40+
prev := currentTask
41+
currentTask = t
42+
t.state.launch()
43+
currentTask = prev
44+
}
45+
46+
//export tinygo_launch
47+
func (*state) launch()
48+
49+
// initialize the state and prepare to call the specified function with the specified argument bundle.
50+
func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
51+
// Save the entry call.
52+
s.entry = fn
53+
s.args = args
54+
55+
// Create a stack.
56+
stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0)))
57+
58+
// Calculate stack base addresses.
59+
s.asyncifysp = uintptr(unsafe.Pointer(&stack[0]))
60+
s.csp = uintptr(unsafe.Pointer(&stack[len(stack)-1])) + unsafe.Sizeof(uintptr(0)) - 1
61+
}
62+
63+
//go:linkname runqueuePushBack runtime.runqueuePushBack
64+
func runqueuePushBack(*Task)
65+
66+
// currentTask is the current running task, or nil if currently in the scheduler.
67+
var currentTask *Task
68+
69+
// Current returns the current active task.
70+
func Current() *Task {
71+
return currentTask
72+
}
73+
74+
// Pause suspends the current task and returns to the scheduler.
75+
// This function may only be called when running on a goroutine stack, not when running on the system stack.
76+
func Pause() {
77+
println("pausing", currentTask)
78+
currentTask.state.unwind()
79+
}
80+
81+
//export tinygo_unwind
82+
func (*stackState) unwind()
83+
84+
// Resume the task until it pauses or completes.
85+
// This may only be called from the scheduler.
86+
func (t *Task) Resume() {
87+
println("resume", t)
88+
currentTask = t
89+
t.state.rewind()
90+
currentTask = nil
91+
println("rewound", t)
92+
}
93+
94+
//export tinygo_rewind
95+
func (*state) rewind()
96+
97+
// OnSystemStack returns whether the caller is running on the system stack.
98+
func OnSystemStack() bool {
99+
// If there is not an active goroutine, then this must be running on the system stack.
100+
return Current() == nil
101+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
.globaltype __stack_pointer, i32
2+
3+
.global tinygo_unwind
4+
.type tinygo_unwind,@function
5+
tinygo_unwind: // func (state *stackState) unwind()
6+
.functype tinygo_unwind (i32) -> ()
7+
.local i32
8+
// Check if we are rewinding.
9+
i32.const 0
10+
i32.load8_u tinygo_rewinding
11+
if // if tinygo_rewinding {
12+
// Stop rewinding.
13+
call stop_rewind
14+
i32.const 0
15+
i32.const 0
16+
i32.store8 rewinding // tinygo_rewinding = false;
17+
else
18+
// Save the C stack pointer (destination structure pointer is in local 0).
19+
local.get 0
20+
global.get __stack_pointer
21+
i32.store 4 // state.csp = getCurrentStackPointer()
22+
// Ask asyncify to unwind.
23+
// When resuming, asyncify will return this function with tinygo_rewinding set to true.
24+
local.get 0
25+
call start_unwind // asyncify.start_unwind(state)
26+
end_if
27+
return
28+
end_function
29+
30+
.global tinygo_launch
31+
.type tinygo_launch,@function
32+
tinygo_launch: // func (state *state) launch()
33+
.functype tinygo_launch (i32) -> ()
34+
.local i32
35+
// Switch to the goroutine's C stack.
36+
global.get __stack_pointer // prev := getCurrentStackPointer()
37+
local.get 0
38+
i32.load 12
39+
global.set __stack_pointer // setStackPointer(state.csp)
40+
// Get the argument pack and entry pointer.
41+
local.get 0
42+
i32.load 4 // args := state.args
43+
local.get 0
44+
i32.load 0 // fn := state.entry
45+
// Launch the entry function.
46+
call_indirect (i32) -> () // fn(args)
47+
// Stop unwinding.
48+
call stop_unwind
49+
// Restore the C stack.
50+
global.set __stack_pointer // setStackPointer(prev)
51+
return
52+
end_function
53+
54+
.global tinygo_rewind
55+
.type tinygo_rewind,@function
56+
tinygo_rewind: // func (state *state) rewind()
57+
.functype tinygo_rewind (i32) -> ()
58+
.local i32
59+
// Switch to the goroutine's C stack.
60+
global.get __stack_pointer // prev := getCurrentStackPointer()
61+
local.get 0
62+
i32.load 12
63+
global.set __stack_pointer // setStackPointer(state.csp)
64+
// Get the argument pack and entry pointer.
65+
local.get 0
66+
i32.load 4 // args := state.args
67+
local.get 0
68+
i32.load 0 // fn := state.entry
69+
// Prepare to rewind.
70+
i32.const 0
71+
i32.const 1
72+
i32.store8 tinygo_rewinding // tinygo_rewinding = true;
73+
local.get 0
74+
i32.const 8
75+
i32.add
76+
call start_rewind // asyncify.start_rewind(&state.stackState)
77+
// Launch the entry function.
78+
// This will actually rewind the call stack.
79+
call_indirect (i32) -> () // fn(args)
80+
// Stop unwinding.
81+
call stop_unwind
82+
// Restore the C stack.
83+
global.set __stack_pointer // setStackPointer(prev)
84+
return
85+
end_function
86+
87+
.functype start_unwind (i32) -> ()
88+
.import_module start_unwind, asyncify
89+
.functype stop_unwind () -> ()
90+
.import_module stop_unwind, asyncify
91+
.functype start_rewind (i32) -> ()
92+
.import_module start_rewind, asyncify
93+
.functype stop_rewind () -> ()
94+
.import_module stop_rewind, asyncify
95+
96+
.hidden tinygo_rewinding # @tinygo_rewinding
97+
.type tinygo_rewinding,@object
98+
.section .bss.tinygo_rewinding,"",@
99+
.globl tinygo_rewinding
100+
tinygo_rewinding:
101+
.int8 0 # 0x0
102+
.size tinygo_rewinding, 1

targets/wasi.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"goarch": "arm",
66
"linker": "wasm-ld",
77
"libc": "wasi-libc",
8+
"scheduler": "asyncify",
9+
"gc": "leaking",
10+
"default-stack-size": 1000000,
811
"cflags": [
912
"--target=wasm32--wasi",
1013
"--sysroot={root}/lib/wasi-libc/sysroot",
@@ -16,6 +19,9 @@
1619
"--export-dynamic",
1720
"--no-demangle"
1821
],
22+
"extra-files": [
23+
"src/internal/task/task_asyncify_wasm.S"
24+
],
1925
"emulator": ["wasmtime"],
2026
"wasm-abi": "generic"
2127
}

targets/wasm.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"goarch": "wasm",
66
"linker": "wasm-ld",
77
"libc": "wasi-libc",
8+
"scheduler": "asyncify",
9+
"gc": "leaking",
10+
"default-stack-size": 2048,
811
"cflags": [
912
"--target=wasm32--wasi",
1013
"--sysroot={root}/lib/wasi-libc/sysroot",
@@ -15,6 +18,9 @@
1518
"--stack-first",
1619
"--no-demangle"
1720
],
21+
"extra-files": [
22+
"src/internal/task/task_asyncify_wasm.S"
23+
],
1824
"emulator": ["node", "targets/wasm_exec.js"],
1925
"wasm-abi": "js"
2026
}

transform/optimizer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
125125
if err != nil {
126126
return []error{err}
127127
}
128-
case "tasks":
128+
case "tasks", "asyncify":
129129
// No transformations necessary.
130130
case "none":
131131
// Check for any goroutine starts.
@@ -219,7 +219,7 @@ func getFunctionsUsedInTransforms(config *compileopts.Config) []string {
219219
case "none":
220220
case "coroutines":
221221
fnused = append(append([]string{}, fnused...), coroFunctionsUsedInTransforms...)
222-
case "tasks":
222+
case "tasks", "asyncify":
223223
fnused = append(append([]string{}, fnused...), taskFunctionsUsedInTransforms...)
224224
default:
225225
panic(fmt.Errorf("invalid scheduler %q", config.Scheduler()))

0 commit comments

Comments
 (0)