//go:build tinygo.wasm && !(custommalloc || wasm_unknown || gc.boehm)

package runtime

import "unsafe"

// The below functions override the default allocator of wasi-libc. This ensures
// code linked from other languages can allocate memory without colliding with
// our GC allocations.

// 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
	}
	ptr := alloc(size, nil)
	allocs[(*byte)(ptr)] = size
	return ptr
}

//export free
func libc_free(ptr unsafe.Pointer) {
	if ptr == nil {
		return
	}
	if _, ok := allocs[(*byte)(ptr)]; ok {
		delete(allocs, (*byte)(ptr))
	} else {
		runtimeFatal("free: invalid pointer")
	}
}

//export calloc
func libc_calloc(nmemb, size uintptr) unsafe.Pointer {
	// No difference between calloc and malloc.
	return libc_malloc(nmemb * size)
}

//export realloc
func libc_realloc(oldPtr unsafe.Pointer, size uintptr) unsafe.Pointer {
	if size == 0 {
		libc_free(oldPtr)
		return nil
	}

	// 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.
	// TODO: we could skip this if the new allocation is smaller than the old.
	ptr := alloc(size, nil)

	if oldPtr != nil {
		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 {
			runtimeFatal("realloc: invalid pointer")
		}
	}

	allocs[(*byte)(ptr)] = size
	return ptr
}
