| // Copyright 2026 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build goexperiment.simd && (amd64 || arm64) && linux | |
| package simd_test | |
| import ( | |
| "fmt" | |
| "path/filepath" | |
| "reflect" | |
| "runtime" | |
| "runtime/debug" | |
| "simd/archsimd" | |
| "strings" | |
| "syscall" | |
| "testing" | |
| "unsafe" | |
| ) | |
| // The tests in this file check that the Load*Part functions access only the | |
| // elements of their slice argument. They place the slice at the start and at | |
| // the end of a page that lies between two inaccessible pages, so that any | |
| // access outside the slice faults. See also bytes/boundary_test.go. | |
| func TestLoadPartPageBoundary128(t *testing.T) { | |
| testLoadPartPageBoundary(t, archsimd.LoadInt8x16Part) | |
| testLoadPartPageBoundary(t, archsimd.LoadInt16x8Part) | |
| testLoadPartPageBoundary(t, archsimd.LoadInt32x4Part) | |
| testLoadPartPageBoundary(t, archsimd.LoadInt64x2Part) | |
| testLoadPartPageBoundary(t, archsimd.LoadUint8x16Part) | |
| testLoadPartPageBoundary(t, archsimd.LoadUint16x8Part) | |
| testLoadPartPageBoundary(t, archsimd.LoadUint32x4Part) | |
| testLoadPartPageBoundary(t, archsimd.LoadUint64x2Part) | |
| testLoadPartPageBoundary(t, archsimd.LoadFloat32x4Part) | |
| testLoadPartPageBoundary(t, archsimd.LoadFloat64x2Part) | |
| } | |
| // HasLenAndStore is implemented by the vector types returned by the Load*Part | |
| // functions. | |
| type HasLenAndStore[T number] interface { | |
| Len() int | |
| Store(s []T) | |
| } | |
| // testLoadPartPageBoundary runs a subtest that checks that load, a Load*Part | |
| // function, reads only the elements of its argument and loads them correctly. | |
| // It tries every slice length from 1 to the vector length, with the slice at | |
| // each end of a page whose neighbors are inaccessible. | |
| func testLoadPartPageBoundary[T number, V HasLenAndStore[T]](t *testing.T, load func(s []T) (V, int)) { | |
| name := runtime.FuncForPC(reflect.ValueOf(load).Pointer()).Name() | |
| name = name[strings.LastIndexByte(name, '.')+1:] | |
| t.Run(name, func(t *testing.T) { | |
| var zero V | |
| n := zero.Len() | |
| size := int(unsafe.Sizeof(T(0))) | |
| page := guardedPage(t) | |
| for l := 1; l <= n; l++ { | |
| for _, off := range []int{0, len(page) - l*size} { | |
| s := unsafe.Slice((*T)(unsafe.Pointer(&page[off])), l) | |
| for i := range s { | |
| s[i] = T(i + 1) | |
| } | |
| v, got, fault := loadCatchingFault(load, s) | |
| if fault != nil { | |
| start := uintptr(unsafe.Pointer(&s[0])) | |
| where := "past the end of" | |
| if fault.addr < start { | |
| where = "before the start of" | |
| } | |
| t.Fatalf("%s read %s a %d-element slice at [%#x, %#x): fault at %#x in %s", | |
| name, where, l, start, start+uintptr(l*size), fault.addr, fault.loc) | |
| } | |
| if got != l { | |
| t.Errorf("%s(s) with len(s) = %d returned %d, want %d", name, l, got, l) | |
| } | |
| gotElems := make([]T, n) | |
| v.Store(gotElems) | |
| wantElems := make([]T, n) | |
| copy(wantElems, s) | |
| checkSlicesLogInput(t, gotElems, wantElems, 0.0, func() { t.Helper(); t.Logf("len(s) = %d", l) }) | |
| } | |
| } | |
| }) | |
| } | |
| // guardedPage returns a page of memory that is immediately preceded and | |
| // followed by inaccessible pages. | |
| func guardedPage(t *testing.T) []byte { | |
| t.Helper() | |
| size := syscall.Getpagesize() | |
| mem, err := syscall.Mmap(-1, 0, 3*size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE) | |
| if err != nil { | |
| t.Fatalf("mmap failed: %v", err) | |
| } | |
| t.Cleanup(func() { | |
| if err := syscall.Munmap(mem); err != nil { | |
| t.Errorf("munmap failed: %v", err) | |
| } | |
| }) | |
| if err := syscall.Mprotect(mem[:size], syscall.PROT_NONE); err != nil { | |
| t.Fatalf("mprotect of low page failed: %v", err) | |
| } | |
| if err := syscall.Mprotect(mem[2*size:], syscall.PROT_NONE); err != nil { | |
| t.Fatalf("mprotect of high page failed: %v", err) | |
| } | |
| return mem[size : 2*size : 2*size] | |
| } | |
| // A faultInfo describes a memory fault that the runtime turned into a panic. | |
| type faultInfo struct { | |
| addr uintptr // the address whose access faulted | |
| loc string // the function and source line that made the access | |
| } | |
| // loadCatchingFault returns load(s). If load faults, it returns a description | |
| // of the fault instead of crashing. | |
| func loadCatchingFault[T number, V any](load func(s []T) (V, int), s []T) (v V, n int, fault *faultInfo) { | |
| old := debug.SetPanicOnFault(true) | |
| defer debug.SetPanicOnFault(old) | |
| defer func() { | |
| r := recover() | |
| if r == nil { | |
| return | |
| } | |
| err, ok := r.(interface{ Addr() uintptr }) | |
| if !ok { | |
| panic(r) | |
| } | |
| fault = &faultInfo{addr: err.Addr(), loc: faultLocation()} | |
| }() | |
| v, n = load(s) | |
| return v, n, nil | |
| } | |
| // faultLocation describes the memory access that caused the panic in | |
| // progress: the function and source line that made it, followed by the | |
| // callers inside package archsimd, which end with the function that the test | |
| // called. A deferred function must call faultLocation while that panic is | |
| // being handled, when the faulting frame is still on the stack. | |
| func faultLocation() string { | |
| pcs := make([]uintptr, 64) | |
| frames := runtime.CallersFrames(pcs[:runtime.Callers(1, pcs)]) | |
| // Skip to runtime.sigpanic. The frame below it made the access. | |
| for { | |
| frame, more := frames.Next() | |
| if !more { | |
| return "unknown location" | |
| } | |
| if frame.Function == "runtime.sigpanic" { | |
| break | |
| } | |
| } | |
| var locs []string | |
| for { | |
| frame, more := frames.Next() | |
| if len(locs) > 0 && !strings.HasPrefix(frame.Function, "simd/archsimd.") { | |
| break | |
| } | |
| locs = append(locs, fmt.Sprintf("%s (%s:%d)", frame.Function, filepath.Base(frame.File), frame.Line)) | |
| if !more { | |
| break | |
| } | |
| } | |
| return strings.Join(locs, ", called from ") | |
| } |