1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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))
})
}
}
|