Skip to content

Commit 6ad2cfb

Browse files
committed
runtime: release completed Asyncify stacks
1 parent 1f1801b commit 6ad2cfb

11 files changed

Lines changed: 64 additions & 10 deletions

File tree

main_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1125,7 +1125,7 @@ func TestGoexitCrash(t *testing.T) {
11251125
panicStrategy string
11261126
want string
11271127
}{
1128-
{"wasip1-deadlock", "deadlock", "", "deadlocked: no event source"},
1128+
{"wasip1-deadlock", "deadlock", "", "fatal error: all goroutines are asleep - deadlock!"},
11291129
{"wasip1-goexit-panic-trap", "defer", "trap", "defer ran"},
11301130
} {
11311131
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: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type state struct {
2424
// stackState is the state of the stack while unwound.
2525
stackState
2626

27+
stackBase uintptr
2728
launched bool
2829

2930
// finishing marks a goroutine that paused after it completed.
@@ -35,6 +36,8 @@ type state struct {
3536
panicTarget uintptr
3637
}
3738

39+
const hasReleasableStack = true
40+
3841
// stackState is the saved state of a stack while unwound.
3942
// The stack is arranged with asyncify at the bottom, C stack at the top, and a gap of available stack space between the two.
4043
type stackState struct {
@@ -76,8 +79,8 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
7679
s.entry = fn
7780
s.args = args
7881

79-
// Create a stack.
8082
stack := runtime_alloc(stackSize, nil)
83+
s.stackBase = uintptr(stack)
8184

8285
// Set up the stack canary, a random number that should be checked when
8386
// switching from the task back to the scheduler. The stack canary pointer
@@ -152,6 +155,7 @@ func StopPanicUnwind(replay, target uintptr) {
152155
currentTask.state.panicReplay = replay
153156
currentTask.state.panicTarget = target
154157
}
158+
panicStackState = stackState{}
155159
}
156160

157161
func ClearPanicReplay() {
@@ -164,6 +168,7 @@ func ClearPanicReplay() {
164168
currentTask.state.panicOrigin = stackState{}
165169
currentTask.state.panicReplay = 0
166170
currentTask.state.panicTarget = 0
171+
panicStackState = stackState{}
167172
}
168173

169174
func PanicRewindData() unsafe.Pointer {
@@ -210,7 +215,12 @@ func (t *Task) Resume() {
210215
if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) {
211216
runtimeFatal("stack overflow")
212217
}
213-
if t.state.finishing {
218+
if t.Exited {
219+
runtime_freeTaskStack(t.state.stackBase)
220+
t.state = state{}
221+
t.gcData = gcData{}
222+
t.DeferFrame = nil
223+
} else if t.state.finishing {
214224
// The task is complete. Clear stale stack pointers and release its argument bundle.
215225
t.state.finishing = false
216226
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
@@ -28,6 +28,8 @@ type state struct {
2828
canaryPtr *uintptr
2929
}
3030

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

src/runtime/gc_blocks.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,22 @@ func free(ptr unsafe.Pointer) {
537537
// TODO: free blocks on request, when the compiler knows they're unused.
538538
}
539539

540+
//go:noinline
541+
func freeTaskStack(addr uintptr) {
542+
ptr := unsafe.Pointer(addr)
543+
gcLock.Lock()
544+
firstBlock := blockFromAddr(addr)
545+
if gcAsserts && firstBlock.pointer() != ptr {
546+
runtimeFatal("gc: freeing pointer inside allocation")
547+
}
548+
lastBlock := firstBlock.findHead()
549+
for block := firstBlock; block <= lastBlock; block++ {
550+
block.free()
551+
}
552+
insertFreeRange(ptr, uintptr(lastBlock-firstBlock+1))
553+
gcLock.Unlock()
554+
}
555+
540556
// GC performs a garbage collection cycle.
541557
func GC() {
542558
gcLock.Lock()

src/runtime/gc_boehm.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ func free(ptr unsafe.Pointer) {
102102
libgc_free(ptr)
103103
}
104104

105+
//go:noinline
106+
func freeTaskStack(ptr uintptr) {
107+
free(unsafe.Pointer(ptr))
108+
}
109+
105110
func GC() {
106111
gcLock.Lock()
107112
libgc_gcollect()

src/runtime/gc_custom.go

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

50+
//go:noinline
51+
func freeTaskStack(ptr uintptr) {
52+
free(unsafe.Pointer(ptr))
53+
}
54+
5055
// markRoots is called with the start and end addresses to scan for references.
5156
// It is currently only called with the top and bottom of the stack.
5257
func markRoots(start, end uintptr)

src/runtime/gc_leaking.go

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

87+
//go:noinline
88+
func freeTaskStack(ptr uintptr) {
89+
}
90+
8791
func markRoots(start, end uintptr) {
8892
runtimeFatal("unreachable: markRoots")
8993
}

src/runtime/gc_none.go

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

31+
//go:noinline
32+
func freeTaskStack(ptr uintptr) {
33+
}
34+
3135
func GC() {
3236
// Unimplemented.
3337
}

0 commit comments

Comments
 (0)