Skip to content

Commit 8a469fb

Browse files
committed
runtime: release completed Asyncify stacks
1 parent 6ceb902 commit 8a469fb

11 files changed

Lines changed: 65 additions & 10 deletions

File tree

main_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ func TestGoexitCrash(t *testing.T) {
11731173
panicStrategy string
11741174
want string
11751175
}{
1176-
{"wasip1-deadlock", "deadlock", "", "deadlocked: no event source"},
1176+
{"wasip1-deadlock", "deadlock", "", "fatal error: all goroutines are asleep - deadlock!"},
11771177
{"wasip1-goexit-panic-trap", "defer", "trap", "defer ran"},
11781178
} {
11791179
t.Run(tc.name, func(t *testing.T) {

src/internal/task/task.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ type Task struct {
3030
// since it falls into the padding of the FipsIndicator bit above.
3131
RunState uint8
3232

33+
// Exited is set after a task with a releasable stack has finished.
34+
Exited bool
35+
3336
// DeferFrame stores a pointer to the (stack allocated) defer frame of the
3437
// goroutine that is used for the recover builtin.
3538
DeferFrame unsafe.Pointer
@@ -72,5 +75,8 @@ func getGoroutineStackSize(fn uintptr) uintptr
7275
//go:linkname runtime_alloc runtime.alloc
7376
func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
7477

78+
//go:linkname runtime_freeTaskStack runtime.freeTaskStack
79+
func runtime_freeTaskStack(ptr uintptr)
80+
7581
//go:linkname scheduleTask runtime.scheduleTask
7682
func scheduleTask(*Task)

src/internal/task/task_asyncify.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ type state struct {
2525
// stackState is the state of the stack while unwound.
2626
stackState
2727

28-
launched bool
28+
stackBase uintptr
29+
launched bool
2930

3031
// finishing marks a goroutine that paused after it completed.
3132
// Resume uses this per task flag to clear the stack.
@@ -36,6 +37,8 @@ type state struct {
3637
panicTarget uintptr
3738
}
3839

40+
const hasReleasableStack = true
41+
3942
// stackState is the saved state of a stack while unwound.
4043
// The stack is arranged with asyncify at the bottom, C stack at the top, and a gap of available stack space between the two.
4144
type stackState struct {
@@ -79,6 +82,7 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
7982

8083
// Create a stack.
8184
stack := runtime_alloc(stackSize, gclayout.Conservative.AsPtr())
85+
s.stackBase = uintptr(stack)
8286

8387
// Set up the stack canary, a random number that should be checked when
8488
// switching from the task back to the scheduler. The stack canary pointer
@@ -153,6 +157,7 @@ func StopPanicUnwind(replay, target uintptr) {
153157
currentTask.state.panicReplay = replay
154158
currentTask.state.panicTarget = target
155159
}
160+
panicStackState = stackState{}
156161
}
157162

158163
func ClearPanicReplay() {
@@ -165,6 +170,7 @@ func ClearPanicReplay() {
165170
currentTask.state.panicOrigin = stackState{}
166171
currentTask.state.panicReplay = 0
167172
currentTask.state.panicTarget = 0
173+
panicStackState = stackState{}
168174
}
169175

170176
func PanicRewindData() unsafe.Pointer {
@@ -211,7 +217,12 @@ func (t *Task) Resume() {
211217
if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) {
212218
runtimeFatal("stack overflow")
213219
}
214-
if t.state.finishing {
220+
if t.Exited {
221+
runtime_freeTaskStack(t.state.stackBase)
222+
t.state = state{}
223+
t.gcData = gcData{}
224+
t.DeferFrame = nil
225+
} else if t.state.finishing {
215226
// The task is complete. Clear stale stack pointers and release its argument bundle.
216227
t.state.finishing = false
217228
t.clearStack()

src/internal/task/task_exit.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,16 @@ func Goexit() {
2222
exit(true)
2323
}
2424

25+
// Exit exits the current task after its entry function returns.
26+
func Exit() {
27+
exit(false)
28+
}
29+
2530
func exit(goexit bool) {
2631
t := Current()
32+
if hasReleasableStack {
33+
t.Exited = true
34+
}
2735
remaining := atomic.AddUint32(&liveTasks, ^uint32(0))
2836
if t == mainTask {
2937
if goexit {

src/internal/task/task_stack.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ type state struct {
2929
canaryPtr *uintptr
3030
}
3131

32+
const hasReleasableStack = false
33+
3234
//export tinygo_task_exit
3335
func taskExit() {
3436
exit(false)

src/runtime/gc_blocks.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,22 @@ func free(ptr unsafe.Pointer) {
559559
gcLock.Unlock()
560560
}
561561

562+
//go:noinline
563+
func freeTaskStack(addr uintptr) {
564+
ptr := unsafe.Pointer(addr)
565+
gcLock.Lock()
566+
firstBlock := blockFromAddr(addr)
567+
if gcAsserts && firstBlock.pointer() != ptr {
568+
runtimeFatal("gc: freeing pointer inside allocation")
569+
}
570+
lastBlock := firstBlock.findHead()
571+
for block := firstBlock; block <= lastBlock; block++ {
572+
block.free()
573+
}
574+
insertFreeRange(ptr, uintptr(lastBlock-firstBlock+1))
575+
gcLock.Unlock()
576+
}
577+
562578
// GC performs a garbage collection cycle.
563579
func GC() {
564580
gcLock.Lock()

src/runtime/gc_boehm.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ func free(ptr unsafe.Pointer) {
122122
gcLock.Unlock()
123123
}
124124

125+
//go:noinline
126+
func freeTaskStack(ptr uintptr) {
127+
free(unsafe.Pointer(ptr))
128+
}
129+
125130
func GC() {
126131
gcLock.Lock()
127132
libgc_gcollect()

src/runtime/gc_custom.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
4040
// free is called to explicitly free a previously allocated pointer.
4141
func free(ptr unsafe.Pointer)
4242

43+
//go:noinline
44+
func freeTaskStack(ptr uintptr) {
45+
free(unsafe.Pointer(ptr))
46+
}
47+
4348
// markRoots is called with the start and end addresses to scan for references.
4449
func markRoots(start, end uintptr)
4550

src/runtime/gc_leaking.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ func free(ptr unsafe.Pointer) {
7272
// Memory is never freed.
7373
}
7474

75+
//go:noinline
76+
func freeTaskStack(ptr uintptr) {
77+
}
78+
7579
func markRoots(start, end uintptr) {
7680
runtimeFatal("unreachable: markRoots")
7781
}

src/runtime/gc_none.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ func free(ptr unsafe.Pointer) {
2626
// Nothing to free when nothing gets allocated.
2727
}
2828

29+
//go:noinline
30+
func freeTaskStack(ptr uintptr) {
31+
}
32+
2933
func GC() {
3034
// Unimplemented.
3135
}

0 commit comments

Comments
 (0)