Files
tinygo/src/runtime/gc_leaking.go
T
Jake Bailey 16fc1ea2bb runtime: make fatal failures unrecoverable
Match the Go runtime by terminating for deadlocks, stack overflows,
runtime and GC invariants, invalid lock operations, and platform
initialization failures instead of routing them through panic/recover.

Keep language-level runtime errors and unsupported user operations
recoverable. Add crash coverage that verifies fatal errors bypass deferred
recover calls.
2026-07-25 07:14:00 +02:00

130 lines
3.1 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"
"sync/atomic"
"unsafe"
)
const needsStaticHeap = true
var gcScanState atomic.Uint32
func scanCurrentStack() {}
// 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.
runtimeFatal("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) {
runtimeFatal("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.HeapObjects = gcMallocs
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
}