mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-13 15:33:40 +00:00
runtime: map every goroutine to a new OS thread
This is not a scheduler in the runtime, instead every goroutine is mapped to a single OS thread - meaning 1:1 scheduling. While this may not perform well (or at all) for large numbers of threads, it greatly simplifies many things in the runtime. For example, blocking syscalls can be called directly instead of having to use epoll or similar. Also, we don't need to do anything special to call C code - the default stack is all we need.
This commit is contained in:
committed by
Ron Evans
parent
193f91b870
commit
120d17c124
+39
-6
@@ -1,5 +1,21 @@
|
||||
//go:build gc.boehm
|
||||
|
||||
// This is the Boehm-Demers-Weiser conservative garbage collector, integrated
|
||||
// into TinyGo.
|
||||
//
|
||||
// Note that we use a special way of dealing with threads:
|
||||
// * All calls to the bdwgc library are serialized using locks.
|
||||
// * When the bdwgc library wants to push GC roots, all other threads that are
|
||||
// running are stopped.
|
||||
// * After returning from a bdwgc library call, the caller checks whether
|
||||
// other threads were stopped (meaning a GC cycle happened) and resumes the
|
||||
// world.
|
||||
// This is not exactly the most efficient way to do this. We can likely speed
|
||||
// things up by using bdwgc-native wrappers for starting/stopping threads (and
|
||||
// also to resume the world while sweeping). Also, thread local allocation might
|
||||
// help. But we don't do any of these right now, it is left as a possible future
|
||||
// improvement.
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
@@ -16,6 +32,10 @@ var zeroSizedAlloc uint8
|
||||
|
||||
var gcLock task.PMutex
|
||||
|
||||
// Normally false, set to true during a GC scan when all other threads get
|
||||
// paused.
|
||||
var needsResumeWorld bool
|
||||
|
||||
func initHeap() {
|
||||
libgc_init()
|
||||
|
||||
@@ -25,13 +45,16 @@ func initHeap() {
|
||||
var gcCallbackPtr = reflectlite.ValueOf(gcCallback).UnsafePointer()
|
||||
|
||||
func gcCallback() {
|
||||
// Mark the system stack and (if we're on a goroutine stack) also the
|
||||
// current goroutine stack.
|
||||
markStack()
|
||||
// Mark globals and all stacks, and stop the world if we're using threading.
|
||||
gcMarkReachable()
|
||||
|
||||
findGlobals(func(start, end uintptr) {
|
||||
libgc_push_all(start, end)
|
||||
})
|
||||
if needsResumeWorld {
|
||||
// Should never happen, check for it anyway.
|
||||
runtimePanic("gc: world already stopped")
|
||||
}
|
||||
|
||||
// Note that we need to resume the world after finishing the GC call.
|
||||
needsResumeWorld = true
|
||||
}
|
||||
|
||||
func markRoots(start, end uintptr) {
|
||||
@@ -57,6 +80,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
||||
}
|
||||
|
||||
gcLock.Lock()
|
||||
needsResumeWorld = false
|
||||
var ptr unsafe.Pointer
|
||||
if layout == gclayout.NoPtrs {
|
||||
// This object is entirely pointer free, for example make([]int, ...).
|
||||
@@ -73,6 +97,9 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
||||
// Memory returned from libgc_malloc has already been zeroed, so nothing
|
||||
// to do here.
|
||||
}
|
||||
if needsResumeWorld {
|
||||
gcResumeWorld()
|
||||
}
|
||||
gcLock.Unlock()
|
||||
if ptr == nil {
|
||||
runtimePanic("gc: out of memory")
|
||||
@@ -86,7 +113,13 @@ func free(ptr unsafe.Pointer) {
|
||||
}
|
||||
|
||||
func GC() {
|
||||
gcLock.Lock()
|
||||
needsResumeWorld = false
|
||||
libgc_gcollect()
|
||||
if needsResumeWorld {
|
||||
gcResumeWorld()
|
||||
}
|
||||
gcLock.Unlock()
|
||||
}
|
||||
|
||||
// This should be stack-allocated, but we don't currently have a good way of
|
||||
|
||||
Reference in New Issue
Block a user