From 8e36345d6bebf9e353ea0351316892fa7a59ada0 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 17 May 2025 16:21:24 -0700 Subject: [PATCH 1/2] runtime: temporarily store waspi2 memory allocations from cabi_realloc This is an initial take at solving the interaction between the TinyGo GC and the Component Model allocation scheme (cabi_realloc). --- src/runtime/runtime_wasip2.go | 17 +++++++++++++++-- src/runtime/runtime_wasmentry.go | 7 +++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/runtime/runtime_wasip2.go b/src/runtime/runtime_wasip2.go index 46ce3d853b..82e5d8f5b7 100644 --- a/src/runtime/runtime_wasip2.go +++ b/src/runtime/runtime_wasip2.go @@ -30,8 +30,21 @@ func os_runtime_args() []string { } //export cabi_realloc -func cabi_realloc(ptr, oldsize, align, newsize unsafe.Pointer) unsafe.Pointer { - return realloc(ptr, uintptr(newsize)) +func cabi_realloc(ptr unsafe.Pointer, oldSize, align, newSize uintptr) unsafe.Pointer { + if newSize == 0 { + return nil + } + newPtr := realloc(ptr, newSize) + if ptr != nil { + for i := range wasmAllocs { + if wasmAllocs[i] == ptr { + wasmAllocs[i] = newPtr + return newPtr + } + } + } + wasmAllocs = append(wasmAllocs, newPtr) + return newPtr } func ticksToNanoseconds(ticks timeUnit) int64 { diff --git a/src/runtime/runtime_wasmentry.go b/src/runtime/runtime_wasmentry.go index 59cacb3b04..160d643dbd 100644 --- a/src/runtime/runtime_wasmentry.go +++ b/src/runtime/runtime_wasmentry.go @@ -96,8 +96,15 @@ func wasmExportExit() { // //go:wasmexport function has exited. schedulerExit = true + // Clear wasm allocations. + wasmAllocs = nil + task.Pause() // TODO: we could cache the allocated stack so we don't have to keep // allocating a new stack on every //go:wasmexport call. } + +// wasmAllocs holds memory allocated by the host in guest memory. +// See func cabi_realloc for more information. +var wasmAllocs []unsafe.Pointer From c8bb418b5f150a51408ec1aae96f2876b286f557 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 21 May 2025 12:33:44 +0200 Subject: [PATCH 2/2] wasm: fix C realloc and optimize it a bit - Do not use make([]byte, ...) to allocate, instead allocate a slice of pointers. This makes sure the precise GC will scan the contents of the allocation, since C could very well put pointers in there. - Simplify the map to use the pointer as the key and the size as the value, instead of storing the slices directly in the map. --- src/runtime/arch_tinygowasm_malloc.go | 29 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/runtime/arch_tinygowasm_malloc.go b/src/runtime/arch_tinygowasm_malloc.go index 239f7c73eb..824031ef63 100644 --- a/src/runtime/arch_tinygowasm_malloc.go +++ b/src/runtime/arch_tinygowasm_malloc.go @@ -8,16 +8,19 @@ import "unsafe" // code linked from other languages can allocate memory without colliding with // our GC allocations. -var allocs = make(map[uintptr][]byte) +// Map of allocations, where the key is the allocated pointer and the value is +// the size of the allocation. +var allocs = make(map[unsafe.Pointer]uintptr) //export malloc func libc_malloc(size uintptr) unsafe.Pointer { if size == 0 { return nil } - buf := make([]byte, size) + const wordSize = unsafe.Sizeof(unsafe.Pointer(nil)) + buf := make([]unsafe.Pointer, (size+wordSize-1)/wordSize) ptr := unsafe.Pointer(&buf[0]) - allocs[uintptr(ptr)] = buf + allocs[ptr] = size return ptr } @@ -26,8 +29,8 @@ func libc_free(ptr unsafe.Pointer) { if ptr == nil { return } - if _, ok := allocs[uintptr(ptr)]; ok { - delete(allocs, uintptr(ptr)) + if _, ok := allocs[ptr]; ok { + delete(allocs, ptr) } else { panic("free: invalid pointer") } @@ -48,18 +51,22 @@ func libc_realloc(oldPtr unsafe.Pointer, size uintptr) unsafe.Pointer { // It's hard to optimize this to expand the current buffer with our GC, but // it is theoretically possible. For now, just always allocate fresh. - buf := make([]byte, size) + // TODO: we could skip this if the new allocation is smaller than the old. + const wordSize = unsafe.Sizeof(unsafe.Pointer(nil)) + buf := make([]unsafe.Pointer, (size+wordSize-1)/wordSize) + ptr := unsafe.Pointer(&buf[0]) if oldPtr != nil { - if oldBuf, ok := allocs[uintptr(oldPtr)]; ok { - copy(buf, oldBuf) - delete(allocs, uintptr(oldPtr)) + if oldSize, ok := allocs[oldPtr]; ok { + oldBuf := unsafe.Slice((*byte)(oldPtr), oldSize) + newBuf := unsafe.Slice((*byte)(ptr), size) + copy(newBuf, oldBuf) + delete(allocs, oldPtr) } else { panic("realloc: invalid pointer") } } - ptr := unsafe.Pointer(&buf[0]) - allocs[uintptr(ptr)] = buf + allocs[ptr] = size return ptr }