Files
tinygo/src/runtime/gc_leaking.go
T
Ayke van Laethem 120d17c124 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.
2025-04-11 15:18:41 +02:00

124 lines
3.0 KiB
Go

//go:build gc.leaking
package runtime
// This GC implementation is the simplest useful memory allocator possible: it
// only allocates memory and never frees it. For some constrained systems, it
// may be the only memory allocator possible.
import (
"internal/task"
"unsafe"
)
const needsStaticHeap = true
// Ever-incrementing pointer: no memory is freed.
var heapptr uintptr
// Total amount allocated for runtime.MemStats
var gcTotalAlloc uint64
// Total number of calls to alloc()
var gcMallocs uint64
// Heap lock for parallel goroutines. No-op when single threaded.
var gcLock task.PMutex
// Total number of objected freed; for leaking collector this stays 0
const gcFrees = 0
// Inlining alloc() speeds things up slightly but bloats the executable by 50%,
// see https://github.com/tinygo-org/tinygo/issues/2674. So don't.
//
//go:noinline
func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
// TODO: this can be optimized by not casting between pointers and ints so
// much. And by using platform-native data types (e.g. *uint8 for 8-bit
// systems).
gcLock.Lock()
size = align(size)
addr := heapptr
gcTotalAlloc += uint64(size)
gcMallocs++
heapptr += size
for heapptr >= heapEnd {
// Try to increase the heap and check again.
if growHeap() {
continue
}
// Failed to make the heap bigger, so we must really be out of memory.
runtimePanic("out of memory")
}
gcLock.Unlock()
pointer := unsafe.Pointer(addr)
zero_new_alloc(pointer, size)
return pointer
}
func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer {
newAlloc := alloc(size, nil)
if ptr == nil {
return newAlloc
}
// according to POSIX everything beyond the previous pointer's
// size will have indeterminate values so we can just copy garbage
memcpy(newAlloc, ptr, size)
return newAlloc
}
func free(ptr unsafe.Pointer) {
// Memory is never freed.
}
func markRoots(start, end uintptr) {
runtimePanic("unreachable: markRoots")
}
// ReadMemStats populates m with memory statistics.
//
// The returned memory statistics are up to date as of the
// call to ReadMemStats. This would not do GC implicitly for you.
func ReadMemStats(m *MemStats) {
gcLock.Lock()
m.HeapIdle = 0
m.HeapInuse = gcTotalAlloc
m.HeapReleased = 0 // always 0, we don't currently release memory back to the OS.
m.HeapSys = m.HeapInuse + m.HeapIdle
m.GCSys = 0
m.TotalAlloc = gcTotalAlloc
m.Mallocs = gcMallocs
m.Frees = gcFrees
m.Sys = uint64(heapEnd - heapStart)
// no free -- current in use heap is the total allocated
m.HeapAlloc = gcTotalAlloc
m.Alloc = m.HeapAlloc
gcLock.Unlock()
}
func GC() {
// No-op.
}
func SetFinalizer(obj interface{}, finalizer interface{}) {
// No-op.
}
func initHeap() {
// Initialize this bump-pointer allocator to the start of the heap.
// Needed here because heapStart may not be a compile-time constant.
heapptr = heapStart
}
// setHeapEnd sets a new (larger) heapEnd pointer.
func setHeapEnd(newHeapEnd uintptr) {
// This "heap" is so simple that simply assigning a new value is good
// enough.
heapEnd = newHeapEnd
}