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

Commit

Permalink
Use typed alloc for large objects too (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
anuraaga authored Oct 19, 2023
1 parent d2349cb commit bdc173d
Show file tree
Hide file tree
Showing 8 changed files with 404 additions and 17 deletions.
35 changes: 35 additions & 0 deletions bitmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright wasilibs authors
// SPDX-License-Identifier: MIT

package nottinygc

import "unsafe"

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

type gcBitmap struct {
words []uintptr
}

func newBitmap(size uintptr) gcBitmap {
bmSize := gcBitmapSize(size)
wordsArr := cmalloc(bmSize * unsafe.Sizeof(uintptr(0)))
words := unsafe.Slice((*uintptr)(wordsArr), bmSize)
for i := 0; i < len(words); i++ {
words[i] = 0
}
return gcBitmap{words: words}
}

func (b gcBitmap) set(idx uintptr) {
b.words[idx/cppWordsz] |= 1 << (idx % cppWordsz)
}

func (b gcBitmap) get(idx uintptr) uintptr {
return (b.words[idx/cppWordsz] >> (idx % cppWordsz)) & 1
}

func gcBitmapSize(size uintptr) uintptr {
return (size + cppWordsz - 1) / cppWordsz
}
102 changes: 102 additions & 0 deletions bitmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright wasilibs authors
// SPDX-License-Identifier: MIT

package nottinygc

import (
"fmt"
"testing"
)

func TestBitmap32Bits(t *testing.T) {
tests := []uintptr{
0,
0b1,
0b101,
0b111,
0b0001,
0b1000001,
0xFFFFFFFF,
0x11111111,
0x01010101,
0x0F0F0F0F,
}

for _, tc := range tests {
tc := tc
t.Run(fmt.Sprintf("%v", tc), func(t *testing.T) {
bm := newBitmap(32)
if len(bm.words) != 1 {
t.Fatalf("expected 1 word, got %v", len(bm.words))
}
for i := 0; i < 32; i++ {
if tc&(1<<i) != 0 {
bm.set(uintptr(i))
}
}

for i := 0; i < 32; i++ {
got := bm.get(uintptr(i))
if tc&(1<<i) != 0 {
if got == 0 {
t.Fatalf("expected bit %v to be set", i)
}
} else {
if got != 0 {
t.Fatalf("expected bit %v to be unset", i)
}
}
}
})
}
}

// Test for multiple words, we pick larger than 64-bits to have more than one word on Go
// as well. We don't actually run CI with Go but it can be helpful for development.
func TestBitmap128Bits(t *testing.T) {
// We'll just repeat these.
tests := []uintptr{
0,
0b1,
0b101,
0b111,
0b0001,
0b1000001,
0xFFFFFFFF,
0x11111111,
0x01010101,
0x0F0F0F0F,
}

for _, tc := range tests {
tc := tc
t.Run(fmt.Sprintf("%v", tc), func(t *testing.T) {
bm := newBitmap(128)
if cppWordsz == 32 && len(bm.words) != 4 || cppWordsz == 64 && len(bm.words) != 2 {
t.Fatalf("got %v words", len(bm.words))
}
for j := 0; j < 4; j++ {
for i := 0; i < 32; i++ {
if tc&(1<<(32*j+i)) != 0 {
bm.set(uintptr(i))
}
}
}

for j := 0; j < 4; j++ {
for i := 0; i < 32; i++ {
got := bm.get(uintptr(32*j + i))
if tc&(1<<(32*j+i)) != 0 {
if got == 0 {
t.Fatalf("expected bit %v to be set", i)
}
} else {
if got != 0 {
t.Fatalf("expected bit %v to be unset", i)
}
}
}
}
})
}
}
6 changes: 2 additions & 4 deletions finalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ package nottinygc
/*
void GC_register_finalizer(void* obj, void* fn, void* cd, void** ofn, void** ocn);
void onFinalizer(void* obj, void* fn);
void* malloc(unsigned int long);
void free(void* ptr);
*/
import "C"
import "unsafe"
Expand All @@ -33,7 +31,7 @@ func SetFinalizer(obj interface{}, finalizer interface{}) {

in := (*_interface)(unsafe.Pointer(&obj))

rf := (*registeredFinalizer)(C.malloc(C.ulong(unsafe.Sizeof(registeredFinalizer{}))))
rf := (*registeredFinalizer)(cmalloc(unsafe.Sizeof(registeredFinalizer{})))
rf.typecode = in.typecode
rf.finKey = finKey

Expand All @@ -42,7 +40,7 @@ func SetFinalizer(obj interface{}, finalizer interface{}) {

//export onFinalizer
func onFinalizer(obj unsafe.Pointer, data unsafe.Pointer) {
defer C.free(data)
defer cfree(data)

rf := (*registeredFinalizer)(data)
finalizer := finalizers[rf.finKey]
Expand Down
48 changes: 36 additions & 12 deletions gc.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ void* GC_malloc(unsigned int size);
void* GC_malloc_atomic(unsigned int size);
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);
unsigned int GC_make_descriptor(void* bm, unsigned int len);
void GC_free(void* ptr);
void GC_gcollect();
void GC_set_on_collection_event(void* f);
Expand All @@ -40,12 +40,11 @@ const (
)

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)
)

var descriptorCache = newIntMap()

//export onCollectionEvent
func onCollectionEvent(eventType uint32) {
switch eventType {
Expand Down Expand Up @@ -98,33 +97,58 @@ func alloc(size uintptr, layoutPtr unsafe.Pointer) unsafe.Pointer {
}
layoutSz := (layout >> 1) & (1<<sizeFieldBits - 1)
layoutBm := layout >> (1 + sizeFieldBits)
buf = allocTyped(size, layoutSz, layoutBm)
} else {
buf = allocSmall(size, layoutSz, layoutBm)
} else if layoutPtr == nil {
// Unknown layout, assume all pointers.
buf = C.GC_malloc(C.uint(size))
} else {
buf = allocLarge(size, layoutPtr)
}
if buf == nil {
panic("out of memory")
}
return buf
}

func allocTyped(allocSz uintptr, layoutSz uintptr, layoutBm uintptr) unsafe.Pointer {
descr := gcDescr(layoutSz, layoutBm)
if descr == 0 {
func allocSmall(allocSz uintptr, layoutSz uintptr, layoutBm uintptr) unsafe.Pointer {
desc := gcDescr(layoutBm)
if desc == 0 {
return C.GC_malloc_atomic(C.uint(allocSz))
}

return allocTyped(allocSz, layoutSz, desc)
}

func allocLarge(allocSz uintptr, layoutPtr unsafe.Pointer) unsafe.Pointer {
layoutSz := *(*uintptr)(layoutPtr)
desc, ok := descriptorCache.get(uintptr(layoutPtr))
if !ok {
bm := newBitmap(layoutSz)
bitsPtr := unsafe.Add(layoutPtr, unsafe.Sizeof(uintptr(0)))
for i := uintptr(0); i < layoutSz; i++ {
if (*(*uint8)(unsafe.Add(bitsPtr, i/8))>>(i%8))&1 != 0 {
bm.set(i)
}
}
desc = uintptr(C.GC_make_descriptor(unsafe.Pointer(&bm.words[0]), C.uint(layoutSz)))
descriptorCache.put(uintptr(layoutPtr), desc)
}

return allocTyped(allocSz, layoutSz, desc)
}

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

// 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 {
func gcDescr(layoutBm uintptr) uintptr {
if layoutBm == 0 {
return 0 // no pointers
}
Expand Down
2 changes: 1 addition & 1 deletion gc_notcustom.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright wasilibs authors
// SPDX-License-Identifier: MIT

//go:build !gc.custom
//go:build tinygo && !gc.custom

package nottinygc

Expand Down
Loading

0 comments on commit bdc173d

Please sign in to comment.