mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-08 21:13:39 +00:00
120d17c124
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.
124 lines
3.0 KiB
Go
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
|
|
}
|