Source file src/simd/archsimd/internal/simd_test/slicepart_boundary_128_test.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.simd && (amd64 || arm64) && linux
     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  // The tests in this file check that the Load*Part functions access only the
    23  // elements of their slice argument. They place the slice at the start and at
    24  // the end of a page that lies between two inaccessible pages, so that any
    25  // access outside the slice faults. See also bytes/boundary_test.go.
    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  // HasLenAndStore is implemented by the vector types returned by the Load*Part
    41  // functions.
    42  type HasLenAndStore[T number] interface {
    43  	Len() int
    44  	Store(s []T)
    45  }
    46  
    47  // testLoadPartPageBoundary runs a subtest that checks that load, a Load*Part
    48  // function, reads only the elements of its argument and loads them correctly.
    49  // It tries every slice length from 1 to the vector length, with the slice at
    50  // each end of a page whose neighbors are inaccessible.
    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  					// The 32- and 64-bit element Load*Part functions use
    68  					// VMASKMOVPS/PD, and qemu-x86_64 does not suppress
    69  					// faults on their masked-off elements.
    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  // guardedPage returns a page of memory that is immediately preceded and
    95  // followed by inaccessible pages.
    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  // A faultInfo describes a memory fault that the runtime turned into a panic.
   118  type faultInfo struct {
   119  	addr uintptr // the address whose access faulted
   120  	loc  string  // the function and source line that made the access
   121  }
   122  
   123  // loadCatchingFault returns load(s). If load faults, it returns a description
   124  // of the fault instead of crashing.
   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  // faultLocation describes the memory access that caused the panic in
   144  // progress: the function and source line that made it, followed by the
   145  // callers inside package archsimd, which end with the function that the test
   146  // called. A deferred function must call faultLocation while that panic is
   147  // being handled, when the faulting frame is still on the stack.
   148  func faultLocation() string {
   149  	pcs := make([]uintptr, 64)
   150  	frames := runtime.CallersFrames(pcs[:runtime.Callers(1, pcs)])
   151  	// Skip to runtime.sigpanic. The frame below it made the access.
   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