aboutsummaryrefslogtreecommitdiffhomepage
path: root/tests/runtime_wasi/malloc_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'tests/runtime_wasi/malloc_test.go')
-rw-r--r--tests/runtime_wasi/malloc_test.go127
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))
+ })
+ }
+}