diff options
Diffstat (limited to 'tests/runtime_wasi/malloc_test.go')
-rw-r--r-- | tests/runtime_wasi/malloc_test.go | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/tests/runtime_wasi/malloc_test.go b/tests/runtime_wasi/malloc_test.go new file mode 100644 index 000000000..06b197f13 --- /dev/null +++ b/tests/runtime_wasi/malloc_test.go @@ -0,0 +1,127 @@ +//go:build tinygo.wasm +// +build tinygo.wasm + +package runtime_wasi + +import ( + "reflect" + "runtime" + "strconv" + "testing" + "unsafe" +) + +//export malloc +func libc_malloc(size uintptr) unsafe.Pointer + +//export free +func libc_free(ptr unsafe.Pointer) + +//export calloc +func libc_calloc(nmemb, size uintptr) unsafe.Pointer + +//export realloc +func libc_realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer + +func getFilledBuffer_malloc() uintptr { + ptr := libc_malloc(5) + fillPanda(ptr) + return uintptr(ptr) +} + +func getFilledBuffer_calloc() uintptr { + ptr := libc_calloc(2, 5) + fillPanda(ptr) + *(*byte)(unsafe.Add(ptr, 5)) = 'b' + *(*byte)(unsafe.Add(ptr, 6)) = 'e' + *(*byte)(unsafe.Add(ptr, 7)) = 'a' + *(*byte)(unsafe.Add(ptr, 8)) = 'r' + *(*byte)(unsafe.Add(ptr, 9)) = 's' + return uintptr(ptr) +} + +func getFilledBuffer_realloc() uintptr { + origPtr := getFilledBuffer_malloc() + ptr := libc_realloc(unsafe.Pointer(origPtr), 9) + *(*byte)(unsafe.Add(ptr, 5)) = 'b' + *(*byte)(unsafe.Add(ptr, 6)) = 'e' + *(*byte)(unsafe.Add(ptr, 7)) = 'a' + *(*byte)(unsafe.Add(ptr, 8)) = 'r' + return uintptr(ptr) +} + +func getFilledBuffer_reallocNil() uintptr { + ptr := libc_realloc(nil, 5) + fillPanda(ptr) + return uintptr(ptr) +} + +func fillPanda(ptr unsafe.Pointer) { + *(*byte)(unsafe.Add(ptr, 0)) = 'p' + *(*byte)(unsafe.Add(ptr, 1)) = 'a' + *(*byte)(unsafe.Add(ptr, 2)) = 'n' + *(*byte)(unsafe.Add(ptr, 3)) = 'd' + *(*byte)(unsafe.Add(ptr, 4)) = 'a' +} + +func checkFilledBuffer(t *testing.T, ptr uintptr, content string) { + t.Helper() + buf := *(*string)(unsafe.Pointer(&reflect.StringHeader{ + Data: ptr, + Len: uintptr(len(content)), + })) + if buf != content { + t.Errorf("expected %q, got %q", content, buf) + } +} + +func TestMallocFree(t *testing.T) { + tests := []struct { + name string + getBuffer func() uintptr + content string + }{ + { + name: "malloc", + getBuffer: getFilledBuffer_malloc, + content: "panda", + }, + { + name: "calloc", + getBuffer: getFilledBuffer_calloc, + content: "pandabears", + }, + { + name: "realloc", + getBuffer: getFilledBuffer_realloc, + content: "pandabear", + }, + { + name: "realloc nil", + getBuffer: getFilledBuffer_reallocNil, + content: "panda", + }, + } + + for _, tc := range tests { + tt := tc + t.Run(tt.name, func(t *testing.T) { + bufPtr := tt.getBuffer() + // Don't use defer to free the buffer as it seems to cause the GC to track it. + + // Churn GC, the pointer should still be valid until free is called. + for i := 0; i < 1000; i++ { + a := "hello" + strconv.Itoa(i) + // Some conditional logic to ensure optimization doesn't remove the loop completely. + if len(a) < 0 { + break + } + runtime.GC() + } + + checkFilledBuffer(t, bufPtr, tt.content) + + libc_free(unsafe.Pointer(bufPtr)) + }) + } +} |