Skip to content
This repository has been archived by the owner on May 16, 2024. It is now read-only.

Commit

Permalink
Use precise GC for all small objects (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
anuraaga authored Oct 13, 2023
1 parent 8a547b7 commit 061330c
Showing 1 changed file with 50 additions and 13 deletions.
63 changes: 50 additions & 13 deletions gc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package nottinygc

import (
"math/bits"
"runtime"
"unsafe"
)
Expand All @@ -14,7 +15,9 @@ import (
#include <stddef.h>
void* GC_malloc(unsigned int size);
void* GC_malloc_explicitly_typed(unsigned int size, void* gc_descr);
void* GC_malloc_explicitly_typed(unsigned int size, unsigned int gc_descr);
void* GC_calloc_explicitly_typed(unsigned int nelements, unsigned int element_size, unsigned int gc_descr);
unsigned int GC_make_descriptor(unsigned int* bm, unsigned int len);
void GC_free(void* ptr);
void GC_gcollect();
void GC_set_on_collection_event(void* f);
Expand All @@ -32,6 +35,13 @@ const (
gcEventStart = 0
)

const (
// CPP_WORDSZ is a simple integer constant representing the word size
cppWordsz = uintptr(unsafe.Sizeof(uintptr(0)) * 8)
signb = uintptr(1) << (cppWordsz - 1)
gcDsBitmap = uintptr(1)
)

//export onCollectionEvent
func onCollectionEvent(eventType uint32) {
switch eventType {
Expand All @@ -45,17 +55,27 @@ func onCollectionEvent(eventType uint32) {
//go:linkname initHeap runtime.initHeap
func initHeap() {
C.GC_set_on_collection_event(C.onCollectionEvent)
// We avoid overhead in calling GC_make_descriptor on every allocation by implementing
// the bitmap computation in Go, but we need to call it at least once to initialize
// typed GC itself.
C.GC_make_descriptor(nil, 0)
}

// alloc tries to find some free space on the heap, possibly doing a garbage
// collection cycle if needed. If no space is free, it panics.
//
//go:linkname alloc runtime.alloc
func alloc(size uintptr, layoutPtr unsafe.Pointer) unsafe.Pointer {
// For now, provide type information when there are no pointers. In the future,
// we can try to make this more precise for all types but this should still handle
// the common case of large arrays.
var buf unsafe.Pointer

layout := uintptr(layoutPtr)
// We only handle the case where the layout is embedded because it is cheap to
// transform into a descriptor for bdwgc. Larger layouts may need to allocate,
// and we would want to cache the descriptor, but we cannot use a Go cache in this
// function which is runtime.alloc. If we wanted to pass their information to bdwgc
// efficiently, we would want LLVM to generate the descriptors in its format.
// Because TinyGo uses a byte array for bitmap and bdwgc uses a word array, there are
// likely endian issues in trying to use it directly.
if layout&1 != 0 {
// Layout is stored directly in the integer value.
// Determine format of bitfields in the integer.
Expand All @@ -73,22 +93,39 @@ func alloc(size uintptr, layoutPtr unsafe.Pointer) unsafe.Pointer {
}
layoutSz := (layout >> 1) & (1<<sizeFieldBits - 1)
layoutBm := layout >> (1 + sizeFieldBits)
if layoutSz == 1 && layoutBm == 0 {
// No pointers!
buf := C.GC_malloc_explicitly_typed(C.uint(size), nil)
if buf == nil {
panic("out of memory")
}
return buf
}
buf = allocTyped(size, layoutSz, layoutBm)
} else {
buf = C.GC_malloc(C.uint(size))
}
buf := C.GC_malloc(C.uint(size))
if buf == nil {
panic("out of memory")
}
return buf
}

func allocTyped(allocSz uintptr, layoutSz uintptr, layoutBm uintptr) unsafe.Pointer {
descr := gcDescr(layoutSz, layoutBm)

itemSz := layoutSz * unsafe.Sizeof(uintptr(0))
if descr == 0 || itemSz == allocSz {
return C.GC_malloc_explicitly_typed(C.uint(allocSz), C.uint(descr))
}
numItems := allocSz / itemSz
return C.GC_calloc_explicitly_typed(C.uint(numItems), C.uint(itemSz), C.uint(descr))
}

// Reimplementation of the simple bitmap case from bdwgc
// https://github.com/ivmai/bdwgc/blob/806537be2dec4f49056cb2fe091ac7f7d78728a8/typd_mlc.c#L204
func gcDescr(layoutSz uintptr, layoutBm uintptr) uintptr {
if layoutBm == 0 {
return 0 // no pointers
}

// reversebits processes all bits but is branchless, unlike a looping version so appears
// to perform a little better.
return uintptr(bits.Reverse32(uint32(layoutBm))) | gcDsBitmap
}

//go:linkname free runtime.free
func free(ptr unsafe.Pointer) {
C.GC_free(ptr)
Expand Down

0 comments on commit 061330c

Please sign in to comment.