github.com/wasilibs/nottinygc@v0.7.2-0.20240312114022-d59c9478ef51/gc.go (about) 1 // Copyright wasilibs authors 2 // SPDX-License-Identifier: MIT 3 4 //go:build gc.custom 5 6 package nottinygc 7 8 import ( 9 "math/bits" 10 "runtime" 11 "unsafe" 12 ) 13 14 /* 15 #include <stddef.h> 16 17 void* GC_malloc(unsigned int size); 18 void* GC_malloc_atomic(unsigned int size); 19 void* GC_malloc_ignore_off_page(unsigned int size); 20 void* GC_malloc_explicitly_typed(unsigned int size, unsigned int gc_descr); 21 void* GC_malloc_explicitly_typed_ignore_off_page(unsigned int size, unsigned int gc_descr); 22 void* GC_calloc_explicitly_typed(unsigned int nelements, unsigned int element_size, unsigned int gc_descr); 23 unsigned int GC_make_descriptor(void* bm, unsigned int len); 24 void GC_free(void* ptr); 25 void GC_gcollect(); 26 void GC_set_on_collection_event(void* f); 27 28 size_t GC_get_gc_no(); 29 void GC_get_heap_usage_safe(size_t* heap_size, size_t* free_bytes, size_t* unmapped_bytes, size_t* bytesSinceGC, size_t* totalBytes); 30 size_t GC_get_obtained_from_os_bytes(); 31 void mi_process_info(size_t *elapsed_msecs, size_t *user_msecs, size_t *system_msecs, size_t *current_rss, size_t *peak_rss, size_t *current_commit, size_t *peak_commit, size_t *page_faults); 32 33 void GC_ignore_warn_proc(char* msg, unsigned int arg); 34 void GC_set_warn_proc(void* p); 35 36 void onCollectionEvent(); 37 */ 38 import "C" 39 40 const ( 41 gcEventStart = 0 42 bigObjSize = 100 * 1024 43 ) 44 45 const ( 46 gcDsBitmap = uintptr(1) 47 ) 48 49 var descriptorCache intMap 50 51 //export onCollectionEvent 52 func onCollectionEvent(eventType uint32) { 53 switch eventType { 54 case gcEventStart: 55 markStack() 56 } 57 } 58 59 // Initialize the memory allocator. 60 // 61 //go:linkname initHeap runtime.initHeap 62 func initHeap() { 63 descriptorCache = newIntMap() 64 65 C.GC_set_on_collection_event(C.onCollectionEvent) 66 // We avoid overhead in calling GC_make_descriptor on every allocation by implementing 67 // the bitmap computation in Go, but we need to call it at least once to initialize 68 // typed GC itself. 69 C.GC_make_descriptor(nil, 0) 70 C.GC_set_warn_proc(C.GC_ignore_warn_proc) 71 } 72 73 // alloc tries to find some free space on the heap, possibly doing a garbage 74 // collection cycle if needed. If no space is free, it panics. 75 // 76 //go:linkname alloc runtime.alloc 77 func alloc(size uintptr, layoutPtr unsafe.Pointer) unsafe.Pointer { 78 var buf unsafe.Pointer 79 80 layout := uintptr(layoutPtr) 81 if layout&1 != 0 { 82 // Layout is stored directly in the integer value. 83 // Determine format of bitfields in the integer. 84 const layoutBits = uint64(unsafe.Sizeof(layout) * 8) 85 var sizeFieldBits uint64 86 switch layoutBits { // note: this switch should be resolved at compile time 87 case 16: 88 sizeFieldBits = 4 89 case 32: 90 sizeFieldBits = 5 91 case 64: 92 sizeFieldBits = 6 93 default: 94 panic("unknown pointer size") 95 } 96 layoutSz := (layout >> 1) & (1<<sizeFieldBits - 1) 97 layoutBm := layout >> (1 + sizeFieldBits) 98 buf = allocSmall(size, layoutSz, layoutBm) 99 } else if layoutPtr == nil { 100 // Unknown layout, assume all pointers. 101 if size >= bigObjSize { 102 buf = C.GC_malloc_ignore_off_page(C.uint(size)) 103 } else { 104 buf = C.GC_malloc(C.uint(size)) 105 } 106 } else { 107 buf = allocLarge(size, layoutPtr) 108 } 109 if buf == nil { 110 panic("out of memory") 111 } 112 return buf 113 } 114 115 func allocSmall(allocSz uintptr, layoutSz uintptr, layoutBm uintptr) unsafe.Pointer { 116 desc := gcDescr(layoutBm) 117 return allocTyped(allocSz, layoutSz, desc) 118 } 119 120 func allocLarge(allocSz uintptr, layoutPtr unsafe.Pointer) unsafe.Pointer { 121 layoutSz := *(*uintptr)(layoutPtr) 122 desc, ok := descriptorCache.get(uintptr(layoutPtr)) 123 if !ok { 124 bm := newBitmap(layoutSz) 125 defer bm.free() 126 127 bitsPtr := unsafe.Add(layoutPtr, unsafe.Sizeof(uintptr(0))) 128 for i := uintptr(0); i < layoutSz; i++ { 129 if (*(*uint8)(unsafe.Add(bitsPtr, i/8))>>(i%8))&1 != 0 { 130 bm.set(i) 131 } 132 } 133 desc = uintptr(C.GC_make_descriptor(unsafe.Pointer(&bm.words[0]), C.uint(layoutSz))) 134 descriptorCache.put(uintptr(layoutPtr), desc) 135 } 136 137 return allocTyped(allocSz, layoutSz, desc) 138 } 139 140 func allocTyped(allocSz uintptr, layoutSz uintptr, desc uintptr) unsafe.Pointer { 141 itemSz := layoutSz * unsafe.Sizeof(uintptr(0)) 142 if desc == 0 || itemSz == allocSz { 143 // A bit unsure what the difference is, but it is recommended by bdwgc and seems to make a big 144 // difference in some apps. 145 // https://github.com/ivmai/bdwgc/blob/master/README.md#the-c-interface-to-the-allocator 146 if allocSz >= bigObjSize { 147 return C.GC_malloc_explicitly_typed_ignore_off_page(C.uint(allocSz), C.uint(desc)) 148 } 149 return C.GC_malloc_explicitly_typed(C.uint(allocSz), C.uint(desc)) 150 } 151 numItems := allocSz / itemSz 152 return C.GC_calloc_explicitly_typed(C.uint(numItems), C.uint(itemSz), C.uint(desc)) 153 } 154 155 // Reimplementation of the simple bitmap case from bdwgc 156 // https://github.com/ivmai/bdwgc/blob/806537be2dec4f49056cb2fe091ac7f7d78728a8/typd_mlc.c#L204 157 func gcDescr(layoutBm uintptr) uintptr { 158 if layoutBm == 0 { 159 return 0 // no pointers 160 } 161 162 // reversebits processes all bits but is branchless, unlike a looping version so appears 163 // to perform a little better. 164 return uintptr(bits.Reverse32(uint32(layoutBm))) | gcDsBitmap 165 } 166 167 //go:linkname free runtime.free 168 func free(ptr unsafe.Pointer) { 169 C.GC_free(ptr) 170 } 171 172 //go:linkname markRoots runtime.markRoots 173 func markRoots(start, end uintptr) { 174 // Roots are already registered in bdwgc so we have nothing to do here. 175 } 176 177 //go:linkname markStack runtime.markStack 178 func markStack() 179 180 // GC performs a garbage collection cycle. 181 // 182 //go:linkname GC runtime.GC 183 func GC() { 184 C.GC_gcollect() 185 } 186 187 //go:linkname ReadMemStats runtime.ReadMemStats 188 func ReadMemStats(ms *runtime.MemStats) { 189 var heapSize, freeBytes, unmappedBytes, bytesSinceGC, totalBytes C.size_t 190 C.GC_get_heap_usage_safe(&heapSize, &freeBytes, &unmappedBytes, &bytesSinceGC, &totalBytes) 191 192 var peakRSS C.size_t 193 C.mi_process_info(nil, nil, nil, nil, &peakRSS, nil, nil, nil) 194 195 gcOSBytes := C.GC_get_obtained_from_os_bytes() 196 197 ms.Sys = uint64(peakRSS + gcOSBytes) 198 ms.HeapSys = uint64(heapSize) 199 ms.HeapIdle = uint64(freeBytes) 200 ms.HeapReleased = uint64(unmappedBytes) 201 ms.TotalAlloc = uint64(totalBytes) 202 }