From cdb0a718561faceeef6bf52e851b5841aee00926 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 21 May 2025 12:33:44 +0200 Subject: [PATCH] wasm: fix C realloc and optimize it a bit - Do not use make([]byte, ...) to allocate, instead call the allocator directly with a nil (undefined) layout. 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. --- compiler/map.go | 3 +++ src/runtime/arch_tinygowasm_malloc.go | 31 ++++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/compiler/map.go b/compiler/map.go index eb45e6b53d..6aaf8c76c6 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -250,6 +250,9 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv func hashmapIsBinaryKey(keyType types.Type) bool { switch keyType := keyType.Underlying().(type) { case *types.Basic: + // TODO: unsafe.Pointer is also a binary key, but to support that we + // need to fix an issue with interp first (see + // https://github.com/tinygo-org/tinygo/pull/4898). return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0 case *types.Pointer: return true diff --git a/src/runtime/arch_tinygowasm_malloc.go b/src/runtime/arch_tinygowasm_malloc.go index 8726328025..471361bd77 100644 --- a/src/runtime/arch_tinygowasm_malloc.go +++ b/src/runtime/arch_tinygowasm_malloc.go @@ -8,16 +8,21 @@ 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. +// TODO: make this a map[unsafe.Pointer]uintptr, since that results in slightly +// smaller binaries. But for that to work, unsafe.Pointer needs to be seen as a +// binary key (which it is not at the moment). +// See https://github.com/tinygo-org/tinygo/pull/4898 for details. +var allocs = make(map[*byte]uintptr) //export malloc func libc_malloc(size uintptr) unsafe.Pointer { if size == 0 { return nil } - buf := make([]byte, size) - ptr := unsafe.Pointer(&buf[0]) - allocs[uintptr(ptr)] = buf + ptr := alloc(size, nil) + allocs[(*byte)(ptr)] = size return ptr } @@ -26,8 +31,8 @@ func libc_free(ptr unsafe.Pointer) { if ptr == nil { return } - if _, ok := allocs[uintptr(ptr)]; ok { - delete(allocs, uintptr(ptr)) + if _, ok := allocs[(*byte)(ptr)]; ok { + delete(allocs, (*byte)(ptr)) } else { panic("free: invalid pointer") } @@ -48,18 +53,20 @@ 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. + ptr := alloc(size, nil) if oldPtr != nil { - if oldBuf, ok := allocs[uintptr(oldPtr)]; ok { - copy(buf, oldBuf) - delete(allocs, uintptr(oldPtr)) + if oldSize, ok := allocs[(*byte)(oldPtr)]; ok { + oldBuf := unsafe.Slice((*byte)(oldPtr), oldSize) + newBuf := unsafe.Slice((*byte)(ptr), size) + copy(newBuf, oldBuf) + delete(allocs, (*byte)(oldPtr)) } else { panic("realloc: invalid pointer") } } - ptr := unsafe.Pointer(&buf[0]) - allocs[uintptr(ptr)] = buf + allocs[(*byte)(ptr)] = size return ptr }