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 {
    67  					start := uintptr(unsafe.Pointer(&s[0]))
    68  					where := "past the end of"
    69  					if fault.addr < start {
    70  						where = "before the start of"
    71  					}
    72  					t.Fatalf("%s read %s a %d-element slice at [%#x, %#x): fault at %#x in %s",
    73  						name, where, l, start, start+uintptr(l*size), fault.addr, fault.loc)
    74  				}
    75  				if got != l {
    76  					t.Errorf("%s(s) with len(s) = %d returned %d, want %d", name, l, got, l)
    77  				}
    78  				gotElems := make([]T, n)
    79  				v.Store(gotElems)
    80  				wantElems := make([]T, n)
    81  				copy(wantElems, s)
    82  				checkSlicesLogInput(t, gotElems, wantElems, 0.0, func() { t.Helper(); t.Logf("len(s) = %d", l) })
    83  			}
    84  		}
    85  	})
    86  }
    87  
    88  // guardedPage returns a page of memory that is immediately preceded and
    89  // followed by inaccessible pages.
    90  func guardedPage(t *testing.T) []byte {
    91  	t.Helper()
    92  	size := syscall.Getpagesize()
    93  	mem, err := syscall.Mmap(-1, 0, 3*size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE)
    94  	if err != nil {
    95  		t.Fatalf("mmap failed: %v", err)
    96  	}
    97  	t.Cleanup(func() {
    98  		if err := syscall.Munmap(mem); err != nil {
    99  			t.Errorf("munmap failed: %v", err)
   100  		}
   101  	})
   102  	if err := syscall.Mprotect(mem[:size], syscall.PROT_NONE); err != nil {
   103  		t.Fatalf("mprotect of low page failed: %v", err)
   104  	}
   105  	if err := syscall.Mprotect(mem[2*size:], syscall.PROT_NONE); err != nil {
   106  		t.Fatalf("mprotect of high page failed: %v", err)
   107  	}
   108  	return mem[size : 2*size : 2*size]
   109  }
   110  
   111  // A faultInfo describes a memory fault that the runtime turned into a panic.
   112  type faultInfo struct {
   113  	addr uintptr // the address whose access faulted
   114  	loc  string  // the function and source line that made the access
   115  }
   116  
   117  // loadCatchingFault returns load(s). If load faults, it returns a description
   118  // of the fault instead of crashing.
   119  func loadCatchingFault[T number, V any](load func(s []T) (V, int), s []T) (v V, n int, fault *faultInfo) {
   120  	old := debug.SetPanicOnFault(true)
   121  	defer debug.SetPanicOnFault(old)
   122  	defer func() {
   123  		r := recover()
   124  		if r == nil {
   125  			return
   126  		}
   127  		err, ok := r.(interface{ Addr() uintptr })
   128  		if !ok {
   129  			panic(r)
   130  		}
   131  		fault = &faultInfo{addr: err.Addr(), loc: faultLocation()}
   132  	}()
   133  	v, n = load(s)
   134  	return v, n, nil
   135  }
   136  
   137  // faultLocation describes the memory access that caused the panic in
   138  // progress: the function and source line that made it, followed by the
   139  // callers inside package archsimd, which end with the function that the test
   140  // called. A deferred function must call faultLocation while that panic is
   141  // being handled, when the faulting frame is still on the stack.
   142  func faultLocation() string {
   143  	pcs := make([]uintptr, 64)
   144  	frames := runtime.CallersFrames(pcs[:runtime.Callers(1, pcs)])
   145  	// Skip to runtime.sigpanic. The frame below it made the access.
   146  	for {
   147  		frame, more := frames.Next()
   148  		if !more {
   149  			return "unknown location"
   150  		}
   151  		if frame.Function == "runtime.sigpanic" {
   152  			break
   153  		}
   154  	}
   155  	var locs []string
   156  	for {
   157  		frame, more := frames.Next()
   158  		if len(locs) > 0 && !strings.HasPrefix(frame.Function, "simd/archsimd.") {
   159  			break
   160  		}
   161  		locs = append(locs, fmt.Sprintf("%s (%s:%d)", frame.Function, filepath.Base(frame.File), frame.Line))
   162  		if !more {
   163  			break
   164  		}
   165  	}
   166  	return strings.Join(locs, ", called from ")
   167  }
   168  

View as plain text