Source file src/simd/archsimd/internal/simd_test/simd_arm64_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 && arm64
     6  
     7  package simd_test
     8  
     9  import (
    10  	"fmt"
    11  	"simd/archsimd"
    12  	"strings"
    13  	"testing"
    14  )
    15  
    16  func TestLookupOrZero(t *testing.T) {
    17  	// Out-of-range indices produce zero lane value.
    18  	x := []uint8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
    19  	indices := []uint8{7, 6, 5, 4, 3, 2, 1, 0, 0xff, 8, 16, 9, 128, 10, 20, 11}
    20  	want := []uint8{8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 0, 10, 0, 11, 0, 12}
    21  	got := make([]uint8, len(x))
    22  	archsimd.LoadUint8x16(x).LookupOrZero(archsimd.LoadUint8x16(indices)).StorePart(got)
    23  	checkSlices(t, got, want)
    24  }
    25  
    26  func TestClMul(t *testing.T) {
    27  	if !archsimd.ARM64.PMULL() {
    28  		t.Skip("no carryless multiply")
    29  	}
    30  	var x = archsimd.LoadUint64x2([]uint64{1, 5})
    31  	var y = archsimd.LoadUint64x2([]uint64{3, 9})
    32  
    33  	foo := func(v archsimd.Uint64x2, s []uint64) {
    34  		r := make([]uint64, 2, 2)
    35  		v.StorePart(r)
    36  		checkSlices[uint64](t, r, s)
    37  	}
    38  
    39  	foo(x.CarrylessMultiplyEven(y), []uint64{3, 0})
    40  	foo(x.CarrylessMultiplyEvenOdd(y), []uint64{9, 0})
    41  	foo(x.CarrylessMultiplyOddEven(y), []uint64{15, 0})
    42  	foo(x.CarrylessMultiplyOdd(y), []uint64{45, 0})
    43  	foo(y.CarrylessMultiplyEven(y), []uint64{5, 0})
    44  }
    45  
    46  //go:noinline
    47  func addInt8sNoinline(a, b archsimd.Int8s) archsimd.Int8s { return a.Add(b) }
    48  
    49  //go:noinline
    50  func blackholeSVE() {}
    51  
    52  // TestAddSVEAcrossCall passes scalable vectors across a real (non-inlined) ABI
    53  // boundary, exercising the register/stack passing that size.go's simdify decides
    54  // for SVE types.
    55  func TestAddSVEAcrossCall(t *testing.T) {
    56  	if !archsimd.ARM64.SVE() {
    57  		t.Skip("no sve")
    58  	}
    59  	var a, b, got [32]int8
    60  	for i := range a {
    61  		a[i] = int8(i)
    62  		b[i] = int8(2*i + 1)
    63  	}
    64  	x := archsimd.LoadInt8s(a[:])
    65  	addInt8sNoinline(x, archsimd.LoadInt8s(b[:])).Store(got[:])
    66  	for i := 0; i < x.Len(); i++ {
    67  		if want := a[i] + b[i]; got[i] != want {
    68  			t.Errorf("lane %d: got %d, want %d", i, got[i], want)
    69  		}
    70  	}
    71  }
    72  
    73  //go:noinline
    74  func greaterInt8sNoinline(a, b archsimd.Int8s) archsimd.Mask8s { return a.Greater(b) }
    75  
    76  // TestGreaterSVEMaskRoundTrip returns a mask across a non-inlined call, exercising
    77  // the predicate memory round-trip (PSTR to return it, PLDR to reload it) that the
    78  // mask ABI relies on. It then stores the mask, reloads it with LoadMask8s, and
    79  // checks both agree with a > b lane by lane.
    80  func TestGreaterSVEMaskRoundTrip(t *testing.T) {
    81  	if !archsimd.ARM64.SVE() {
    82  		t.Skip("no sve")
    83  	}
    84  	var a, b [32]int8
    85  	for i := range a {
    86  		a[i] = int8(i - 8)
    87  		b[i] = int8(2*i - 20)
    88  	}
    89  	var z archsimd.Int8s
    90  	m := greaterInt8sNoinline(archsimd.LoadInt8s(a[:]), archsimd.LoadInt8s(b[:]))
    91  	bits := make([]uint16, sveMaskUint16s)
    92  	m.Store(bits)
    93  
    94  	reloaded := make([]uint16, sveMaskUint16s)
    95  	archsimd.LoadMask8s(bits).Store(reloaded)
    96  
    97  	for i := 0; i < z.Len(); i++ {
    98  		want := a[i] > b[i]
    99  		got := bits[i/16]>>uint(i%16)&1 == 1
   100  		if got != want {
   101  			t.Errorf("lane %d: got %v, want %v (a=%d b=%d)", i, got, want, a[i], b[i])
   102  		}
   103  		if reloaded[i/16] != bits[i/16] {
   104  			t.Errorf("LoadMask8s round-trip mismatch at uint16 %d: %#x vs %#x", i/16, reloaded[i/16], bits[i/16])
   105  		}
   106  	}
   107  }
   108  
   109  // TestAddSVESpill keeps a scalable vector live across a call, forcing the
   110  // register allocator to spill and reload it (ZSTR/ZLDR).
   111  func TestAddSVESpill(t *testing.T) {
   112  	if !archsimd.ARM64.SVE() {
   113  		t.Skip("no sve")
   114  	}
   115  	var a, b, got [32]int8
   116  	for i := range a {
   117  		a[i] = int8(i)
   118  		b[i] = int8(100 - i)
   119  	}
   120  	sum := archsimd.LoadInt8s(a[:]).Add(archsimd.LoadInt8s(b[:]))
   121  	blackholeSVE() // clobbers caller-saved regs; sum must survive via a spill
   122  	sum.Store(got[:])
   123  	for i := 0; i < sum.Len(); i++ {
   124  		if want := a[i] + b[i]; got[i] != want {
   125  			t.Errorf("lane %d: got %d, want %d", i, got[i], want)
   126  		}
   127  	}
   128  }
   129  
   130  // TestAddSaturatedSVE checks that the generated saturating add saturates.
   131  func TestAddSaturatedSVE(t *testing.T) {
   132  	if !archsimd.ARM64.SVE() {
   133  		t.Skip("no sve")
   134  	}
   135  	var si, gi [32]int8
   136  	for i := range si {
   137  		si[i] = 100 // 100+100 saturates to +127
   138  	}
   139  	vi := archsimd.LoadInt8s(si[:])
   140  	vi.AddSaturated(vi).Store(gi[:])
   141  	for i := 0; i < vi.Len(); i++ {
   142  		if gi[i] != 127 {
   143  			t.Errorf("int8 lane %d: got %d, want 127", i, gi[i])
   144  		}
   145  	}
   146  	var su, gu [32]uint8
   147  	for i := range su {
   148  		su[i] = 200 // 200+200 saturates to 255
   149  	}
   150  	vu := archsimd.LoadUint8s(su[:])
   151  	vu.AddSaturated(vu).Store(gu[:])
   152  	for i := 0; i < vu.Len(); i++ {
   153  		if gu[i] != 255 {
   154  			t.Errorf("uint8 lane %d: got %d, want 255", i, gu[i])
   155  		}
   156  	}
   157  }
   158  
   159  func TestStringSVE(t *testing.T) {
   160  	if !archsimd.ARM64.SVE() {
   161  		t.Skip("no sve")
   162  	}
   163  	want := func(v any) string {
   164  		return "{" + strings.ReplaceAll(strings.Trim(fmt.Sprint(v), "[]"), " ", ",") + "}"
   165  	}
   166  
   167  	xs := make([]int8, archsimd.Int8s{}.Len())
   168  	ys := make([]int64, archsimd.Int64s{}.Len())
   169  	for i := range xs {
   170  		xs[i] = int8(i % 2)
   171  	}
   172  	for i := range ys {
   173  		ys[i] = int64(i % 2)
   174  	}
   175  	x := archsimd.LoadInt8s(xs)
   176  	y := archsimd.LoadInt64s(ys)
   177  	mx := x.Greater(archsimd.LoadInt8s(make([]int8, len(xs))))
   178  	my := y.Greater(archsimd.LoadInt64s(make([]int64, len(ys))))
   179  
   180  	if x.String() != want(xs) {
   181  		t.Errorf("x=%s wanted %s", x, want(xs))
   182  	}
   183  	if y.String() != want(ys) {
   184  		t.Errorf("y=%s wanted %s", y, want(ys))
   185  	}
   186  	if mx.String() != want(xs) {
   187  		t.Errorf("mx=%s wanted %s", mx, want(xs))
   188  	}
   189  	if my.String() != want(ys) {
   190  		t.Errorf("my=%s wanted %s", my, want(ys))
   191  	}
   192  	t.Logf("x=%s", x)
   193  	t.Logf("y=%s", y)
   194  	t.Logf("mx=%s", mx)
   195  	t.Logf("my=%s", my)
   196  }
   197  

View as plain text