github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/tests/runtime_wasi/malloc_test.go (about) 1 //go:build tinygo.wasm 2 3 package runtime_wasi 4 5 import ( 6 "reflect" 7 "runtime" 8 "strconv" 9 "testing" 10 "unsafe" 11 ) 12 13 //export malloc 14 func libc_malloc(size uintptr) unsafe.Pointer 15 16 //export free 17 func libc_free(ptr unsafe.Pointer) 18 19 //export calloc 20 func libc_calloc(nmemb, size uintptr) unsafe.Pointer 21 22 //export realloc 23 func libc_realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer 24 25 func getFilledBuffer_malloc() uintptr { 26 ptr := libc_malloc(5) 27 fillPanda(ptr) 28 return uintptr(ptr) 29 } 30 31 func getFilledBuffer_calloc() uintptr { 32 ptr := libc_calloc(2, 5) 33 fillPanda(ptr) 34 *(*byte)(unsafe.Add(ptr, 5)) = 'b' 35 *(*byte)(unsafe.Add(ptr, 6)) = 'e' 36 *(*byte)(unsafe.Add(ptr, 7)) = 'a' 37 *(*byte)(unsafe.Add(ptr, 8)) = 'r' 38 *(*byte)(unsafe.Add(ptr, 9)) = 's' 39 return uintptr(ptr) 40 } 41 42 func getFilledBuffer_realloc() uintptr { 43 origPtr := getFilledBuffer_malloc() 44 ptr := libc_realloc(unsafe.Pointer(origPtr), 9) 45 *(*byte)(unsafe.Add(ptr, 5)) = 'b' 46 *(*byte)(unsafe.Add(ptr, 6)) = 'e' 47 *(*byte)(unsafe.Add(ptr, 7)) = 'a' 48 *(*byte)(unsafe.Add(ptr, 8)) = 'r' 49 return uintptr(ptr) 50 } 51 52 func getFilledBuffer_reallocNil() uintptr { 53 ptr := libc_realloc(nil, 5) 54 fillPanda(ptr) 55 return uintptr(ptr) 56 } 57 58 func fillPanda(ptr unsafe.Pointer) { 59 *(*byte)(unsafe.Add(ptr, 0)) = 'p' 60 *(*byte)(unsafe.Add(ptr, 1)) = 'a' 61 *(*byte)(unsafe.Add(ptr, 2)) = 'n' 62 *(*byte)(unsafe.Add(ptr, 3)) = 'd' 63 *(*byte)(unsafe.Add(ptr, 4)) = 'a' 64 } 65 66 func checkFilledBuffer(t *testing.T, ptr uintptr, content string) { 67 t.Helper() 68 buf := *(*string)(unsafe.Pointer(&reflect.StringHeader{ 69 Data: ptr, 70 Len: uintptr(len(content)), 71 })) 72 if buf != content { 73 t.Errorf("expected %q, got %q", content, buf) 74 } 75 } 76 77 func TestMallocFree(t *testing.T) { 78 tests := []struct { 79 name string 80 getBuffer func() uintptr 81 content string 82 }{ 83 { 84 name: "malloc", 85 getBuffer: getFilledBuffer_malloc, 86 content: "panda", 87 }, 88 { 89 name: "calloc", 90 getBuffer: getFilledBuffer_calloc, 91 content: "pandabears", 92 }, 93 { 94 name: "realloc", 95 getBuffer: getFilledBuffer_realloc, 96 content: "pandabear", 97 }, 98 { 99 name: "realloc nil", 100 getBuffer: getFilledBuffer_reallocNil, 101 content: "panda", 102 }, 103 } 104 105 for _, tc := range tests { 106 tt := tc 107 t.Run(tt.name, func(t *testing.T) { 108 bufPtr := tt.getBuffer() 109 // Don't use defer to free the buffer as it seems to cause the GC to track it. 110 111 // Churn GC, the pointer should still be valid until free is called. 112 for i := 0; i < 1000; i++ { 113 a := "hello" + strconv.Itoa(i) 114 // Some conditional logic to ensure optimization doesn't remove the loop completely. 115 if len(a) < 0 { 116 break 117 } 118 runtime.GC() 119 } 120 121 checkFilledBuffer(t, bufPtr, tt.content) 122 123 libc_free(unsafe.Pointer(bufPtr)) 124 }) 125 } 126 } 127 128 func TestMallocEmpty(t *testing.T) { 129 ptr := libc_malloc(0) 130 if ptr != nil { 131 t.Errorf("expected nil pointer, got %p", ptr) 132 } 133 } 134 135 func TestCallocEmpty(t *testing.T) { 136 ptr := libc_calloc(0, 1) 137 if ptr != nil { 138 t.Errorf("expected nil pointer, got %p", ptr) 139 } 140 ptr = libc_calloc(1, 0) 141 if ptr != nil { 142 t.Errorf("expected nil pointer, got %p", ptr) 143 } 144 } 145 146 func TestReallocEmpty(t *testing.T) { 147 ptr := libc_malloc(1) 148 if ptr == nil { 149 t.Error("expected pointer but was nil") 150 } 151 ptr = libc_realloc(ptr, 0) 152 if ptr != nil { 153 t.Errorf("expected nil pointer, got %p", ptr) 154 } 155 }