aboutsummaryrefslogtreecommitdiffhomepage
path: root/tests/runtime_wasi/malloc_test.go
blob: 06b197f135806cc6bc04ca4cc093cb681a3c0c27 (plain)
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))
		})
	}
}