From e76a3220a13eec3ca5e108d5f98938757fbd9acc Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Fri, 17 Feb 2023 11:29:14 +0900 Subject: [PATCH] Implement SetFinalizer for latest tinygo dev (#5) --- .github/workflows/ci.yaml | 6 +--- finalizer.go | 59 +++++++++++++++++++++++++++++++++++++++ finalizer_test.go | 45 +++++++++++++++++++++++++++++ magefiles/magefile.go | 13 ++++++++- 4 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 finalizer.go create mode 100644 finalizer_test.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8e8d4d4..075d939 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -40,13 +40,9 @@ jobs: - run: go run mage.go check build-tinygodev: runs-on: ubuntu-22.04 - container: ghcr.io/tinygo-org/tinygo/tinygo-dev:sha-e0a5fc255522933f651a89c071ebd531ad634e7b + container: ghcr.io/tinygo-org/tinygo/tinygo-dev:sha-f6df2761187f1975e35eb43461d735d6e325df85 steps: - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: - go-version: '^1.20.0' - cache: true - run: | go install github.com/wasilibs/tools/cmd/wasmtime@c93d2e477ab3c1eb7f5303c66a35c84a21d06dbd diff --git a/finalizer.go b/finalizer.go new file mode 100644 index 0000000..ebc008d --- /dev/null +++ b/finalizer.go @@ -0,0 +1,59 @@ +//go:build nottinygc_finalizer + +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" + +var finalizers = map[uintptr]interface{}{} + +//go:linkname SetFinalizer runtime.SetFinalizer +func SetFinalizer(obj interface{}, finalizer interface{}) { + finKey := uintptr((*_interface)(unsafe.Pointer(&finalizer)).value) + finalizers[finKey] = finalizer + + in := (*_interface)(unsafe.Pointer(&obj)) + + rf := (*registeredFinalizer)(C.malloc(C.ulong(unsafe.Sizeof(registeredFinalizer{})))) + rf.typecode = in.typecode + rf.finKey = finKey + + C.GC_register_finalizer(in.value, C.onFinalizer, unsafe.Pointer(rf), nil, nil) +} + +//export onFinalizer +func onFinalizer(obj unsafe.Pointer, data unsafe.Pointer) { + defer C.free(data) + + rf := (*registeredFinalizer)(data) + finalizer := finalizers[rf.finKey] + delete(finalizers, rf.finKey) + + var in interface{} + inFields := (*_interface)(unsafe.Pointer(&in)) + inFields.typecode = rf.typecode + inFields.value = obj + + switch f := finalizer.(type) { + case func(interface{}): + f(in) + default: + panic("currently only finalizers of the form func(interface{}) are supported") + } +} + +type _interface struct { + typecode uintptr + value unsafe.Pointer +} + +type registeredFinalizer struct { + typecode uintptr + finKey uintptr +} diff --git a/finalizer_test.go b/finalizer_test.go new file mode 100644 index 0000000..68943b3 --- /dev/null +++ b/finalizer_test.go @@ -0,0 +1,45 @@ +//go:build nottinygc_finalizer + +package nottinygc + +import ( + "runtime" + "testing" +) + +func TestFinalizer(t *testing.T) { + finalized := 0 + allocFinalized(10, func() { + finalized++ + }) + + runtime.GC() + + if finalized == 0 { + t.Errorf("finalizer not called") + } +} + +//go:noinline +func allocFinalized(num int, cb func()) { + f := &finalized{ + a: 100, + b: "foo", + } + + runtime.SetFinalizer(f, func(f interface{}) { + cb() + }) + + // Recurse to create some more stack frames or else the shadow stack + // may still not have been reset, causing GC to find the inaccessible + // pointers. + if num > 1 { + allocFinalized(num-1, cb) + } +} + +type finalized struct { + a int + b string +} diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 7070a24..c2f8a2a 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" @@ -11,7 +12,17 @@ import ( // Test runs unit tests func Test() error { - return sh.RunV("tinygo", "test", "-gc=custom", "-tags=custommalloc", "-target=wasi", "-v", "-scheduler=none", "./...") + v, err := sh.Output("tinygo", "version") + if err != nil { + return fmt.Errorf("invoking tinygo: %w", err) + } + + tags := []string{"custommalloc"} + if strings.HasSuffix(v, "tinygo version 0.28.") { + tags = append(tags, "nottinygc_finalizer") + } + + return sh.RunV("tinygo", "test", "-gc=custom", fmt.Sprintf("-tags='%s'", strings.Join(tags, " ")), "-target=wasi", "-v", "-scheduler=none", "./...") } func Format() error {