1
2
3
4
5
6
7 package simd_test
8
9 import (
10 "fmt"
11 "path/filepath"
12 "reflect"
13 "runtime"
14 "runtime/debug"
15 "simd/archsimd"
16 "strings"
17 "syscall"
18 "testing"
19 "unsafe"
20 )
21
22
23
24
25
26
27 func TestLoadPartPageBoundary128(t *testing.T) {
28 testLoadPartPageBoundary(t, archsimd.LoadInt8x16Part)
29 testLoadPartPageBoundary(t, archsimd.LoadInt16x8Part)
30 testLoadPartPageBoundary(t, archsimd.LoadInt32x4Part)
31 testLoadPartPageBoundary(t, archsimd.LoadInt64x2Part)
32 testLoadPartPageBoundary(t, archsimd.LoadUint8x16Part)
33 testLoadPartPageBoundary(t, archsimd.LoadUint16x8Part)
34 testLoadPartPageBoundary(t, archsimd.LoadUint32x4Part)
35 testLoadPartPageBoundary(t, archsimd.LoadUint64x2Part)
36 testLoadPartPageBoundary(t, archsimd.LoadFloat32x4Part)
37 testLoadPartPageBoundary(t, archsimd.LoadFloat64x2Part)
38 }
39
40
41
42 type HasLenAndStore[T number] interface {
43 Len() int
44 Store(s []T)
45 }
46
47
48
49
50
51 func testLoadPartPageBoundary[T number, V HasLenAndStore[T]](t *testing.T, load func(s []T) (V, int)) {
52 name := runtime.FuncForPC(reflect.ValueOf(load).Pointer()).Name()
53 name = name[strings.LastIndexByte(name, '.')+1:]
54 t.Run(name, func(t *testing.T) {
55 var zero V
56 n := zero.Len()
57 size := int(unsafe.Sizeof(T(0)))
58 page := guardedPage(t)
59 for l := 1; l <= n; l++ {
60 for _, off := range []int{0, len(page) - l*size} {
61 s := unsafe.Slice((*T)(unsafe.Pointer(&page[off])), l)
62 for i := range s {
63 s[i] = T(i + 1)
64 }
65 v, got, fault := loadCatchingFault(load, s)
66 if fault != nil && inQEMU() && runtime.GOARCH == "amd64" && (size == 4 || size == 8) {
67
68
69
70 t.Skipf("%s faulted with %d elements; skipping because qemu-x86_64 does not suppress faults in masked loads", name, l)
71 }
72 if fault != nil {
73 start := uintptr(unsafe.Pointer(&s[0]))
74 where := "past the end of"
75 if fault.addr < start {
76 where = "before the start of"
77 }
78 t.Fatalf("%s read %s a %d-element slice at [%#x, %#x): fault at %#x in %s",
79 name, where, l, start, start+uintptr(l*size), fault.addr, fault.loc)
80 }
81 if got != l {
82 t.Errorf("%s(s) with len(s) = %d returned %d, want %d", name, l, got, l)
83 }
84 gotElems := make([]T, n)
85 v.Store(gotElems)
86 wantElems := make([]T, n)
87 copy(wantElems, s)
88 checkSlicesLogInput(t, gotElems, wantElems, 0.0, func() { t.Helper(); t.Logf("len(s) = %d", l) })
89 }
90 }
91 })
92 }
93
94
95
96 func guardedPage(t *testing.T) []byte {
97 t.Helper()
98 size := syscall.Getpagesize()
99 mem, err := syscall.Mmap(-1, 0, 3*size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE)
100 if err != nil {
101 t.Fatalf("mmap failed: %v", err)
102 }
103 t.Cleanup(func() {
104 if err := syscall.Munmap(mem); err != nil {
105 t.Errorf("munmap failed: %v", err)
106 }
107 })
108 if err := syscall.Mprotect(mem[:size], syscall.PROT_NONE); err != nil {
109 t.Fatalf("mprotect of low page failed: %v", err)
110 }
111 if err := syscall.Mprotect(mem[2*size:], syscall.PROT_NONE); err != nil {
112 t.Fatalf("mprotect of high page failed: %v", err)
113 }
114 return mem[size : 2*size : 2*size]
115 }
116
117
118 type faultInfo struct {
119 addr uintptr
120 loc string
121 }
122
123
124
125 func loadCatchingFault[T number, V any](load func(s []T) (V, int), s []T) (v V, n int, fault *faultInfo) {
126 old := debug.SetPanicOnFault(true)
127 defer debug.SetPanicOnFault(old)
128 defer func() {
129 r := recover()
130 if r == nil {
131 return
132 }
133 err, ok := r.(interface{ Addr() uintptr })
134 if !ok {
135 panic(r)
136 }
137 fault = &faultInfo{addr: err.Addr(), loc: faultLocation()}
138 }()
139 v, n = load(s)
140 return v, n, nil
141 }
142
143
144
145
146
147
148 func faultLocation() string {
149 pcs := make([]uintptr, 64)
150 frames := runtime.CallersFrames(pcs[:runtime.Callers(1, pcs)])
151
152 for {
153 frame, more := frames.Next()
154 if !more {
155 return "unknown location"
156 }
157 if frame.Function == "runtime.sigpanic" {
158 break
159 }
160 }
161 var locs []string
162 for {
163 frame, more := frames.Next()
164 if len(locs) > 0 && !strings.HasPrefix(frame.Function, "simd/archsimd.") {
165 break
166 }
167 locs = append(locs, fmt.Sprintf("%s (%s:%d)", frame.Function, filepath.Base(frame.File), frame.Line))
168 if !more {
169 break
170 }
171 }
172 return strings.Join(locs, ", called from ")
173 }
174
View as plain text