Source file src/simd/archsimd/_gen/simdgen/arm64/instruction_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  package arm64
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"os"
    11  	"reflect"
    12  	"regexp"
    13  	"sort"
    14  	"testing"
    15  )
    16  
    17  var arm64Path = flag.String("arm64Path", "", "Path to ARM64 XML definitions")
    18  
    19  func requireEqual(t *testing.T, expected, actual interface{}) bool {
    20  	t.Helper()
    21  	if !reflect.DeepEqual(expected, actual) {
    22  		t.Errorf("❌ expected %v, got %v", expected, actual)
    23  		return false
    24  	}
    25  
    26  	if expected != nil {
    27  		switch reflect.TypeOf(expected).Kind() {
    28  		case reflect.Slice, reflect.Array:
    29  			t.Logf("✅ %v", expected)
    30  		}
    31  	}
    32  	return true
    33  }
    34  
    35  func matchEqual(t *testing.T, expected, actual interface{}) bool {
    36  	t.Helper()
    37  	eq := reflect.DeepEqual(expected, actual)
    38  	if eq && expected != nil {
    39  		switch reflect.TypeOf(expected).Kind() {
    40  		case reflect.Slice, reflect.Array:
    41  			t.Logf("✅ %v", expected)
    42  		}
    43  	}
    44  	return eq
    45  }
    46  
    47  func arngs(t *testing.T, instr *Instruction, expectedArrangements []string, expectedShape ArngShape) {
    48  	t.Helper()
    49  	actualArrangements, actualShape := instr.Arrangements()
    50  	actualStrings := make([]string, len(actualArrangements))
    51  	for i, arr := range actualArrangements {
    52  		actualStrings[i] = fmt.Sprintf("%s%d:%s", arr.baseType, arr.elemBits, arr.arrangement)
    53  	}
    54  	sort.Strings(actualStrings)
    55  	sort.Strings(expectedArrangements)
    56  	requireEqual(t, expectedArrangements, actualStrings)
    57  	requireEqual(t, expectedShape, actualShape)
    58  }
    59  
    60  func ops(t *testing.T, instr *Instruction, expectedOps []string, equal func(*testing.T, interface{}, interface{}) bool) bool {
    61  	t.Helper()
    62  	templates := instr.templates()
    63  	if !equal(t, 1, len(templates)) {
    64  		return false
    65  	}
    66  	template := templates[0]
    67  	operands := template.operands
    68  	var actualOps []string
    69  	for _, operand := range operands {
    70  		opStr := fmt.Sprintf("%s:%d", operand.Type.String(), operand.AsmPos)
    71  		actualOps = append(actualOps, opStr)
    72  	}
    73  	return equal(t, expectedOps, actualOps)
    74  }
    75  
    76  func matchOps(expectedOps []string) func(*testing.T, *Instruction) bool {
    77  	return func(t *testing.T, instr *Instruction) bool {
    78  		return ops(t, instr, expectedOps, matchEqual)
    79  	}
    80  }
    81  
    82  func requireOps(expectedOps []string) func(*testing.T, *Instruction) bool {
    83  	return func(t *testing.T, instr *Instruction) bool {
    84  		return ops(t, instr, expectedOps, requireEqual)
    85  	}
    86  }
    87  
    88  func requireArngs(expectedArngs []string, expectedShape ArngShape) func(*testing.T, *Instruction) {
    89  	return func(t *testing.T, instr *Instruction) {
    90  		arngs(t, instr, expectedArngs, expectedShape)
    91  	}
    92  }
    93  
    94  func emitsDefs(expectedCount int) func(*testing.T, *Instruction) {
    95  	return func(t *testing.T, instr *Instruction) {
    96  		values := instr.EmitAll()
    97  		requireEqual(t, expectedCount, len(values))
    98  	}
    99  }
   100  
   101  var (
   102  	// Operand patterns
   103  	binary                   = []string{"VReg:0", "VReg:1", "VReg:2"}
   104  	unary                    = []string{"VReg:0", "VReg:1"}
   105  	twoArgsResultInArg0      = []string{"VReg:0", "VReg:0", "VReg:1"}
   106  	unaryWithImm             = []string{"VReg:0", "Imm:2", "VReg:1"}
   107  	unaryWithImmResultInArg0 = []string{"VReg:0", "Imm:2", "VReg:0", "VReg:1"}
   108  	binaryWithImm            = []string{"VReg:0", "Imm:3", "VReg:1", "VReg:2"}
   109  	elemToVreg               = []string{"VReg:0", "Imm:1", "VReg:1"}
   110  	insertFromLane           = []string{"VReg:0", "Imm:0", "Imm:1", "VReg:0", "VReg:1"}
   111  	insertFromGReg           = []string{"VReg:0", "Imm:0", "VReg:0", "GReg:1"}
   112  	threeArgsResultInArg0    = []string{"VReg:0", "VReg:0", "VReg:1", "VReg:2"}
   113  	fourOperands             = []string{"VReg:0", "VReg:1", "VReg:2", "VReg:3"}
   114  	elemToGReg               = []string{"GReg:0", "Imm:1", "VReg:1"}
   115  
   116  	// Arrangement patterns
   117  	floatS32          = []string{"float32:4S"}
   118  	floating          = []string{"float32:2S", "float32:4S", "float64:2D"}
   119  	bitwise16B        = []string{"int8:16B", "uint8:16B"}
   120  	integer2D         = []string{"int64:2D", "uint64:2D"}
   121  	integerUpTo8Bits  = []string{"int8:16B", "int8:8B", "uint8:16B", "uint8:8B"}
   122  	integerUpTo16Bits = append([]string{"int16:4H", "int16:8H", "uint16:4H", "uint16:8H"}, integerUpTo8Bits...)
   123  	integerUpTo32Bits = append([]string{"int32:2S", "int32:4S", "uint32:2S", "uint32:4S"}, integerUpTo16Bits...)
   124  	integerWideOnly   = []string{"int16:8H", "int32:4S", "int64:2D", "uint16:8H", "uint32:4S", "uint64:2D"}
   125  	polynomialArrngs  = []string{"int8:8B", "int8:16B", "int64:1D", "int64:2D", "uint8:8B", "uint8:16B", "uint64:1D", "uint64:2D"}
   126  	integer32And8Bits = append([]string{"int32:2S", "int32:4S", "uint32:2S", "uint32:4S"}, integerUpTo8Bits...)
   127  	addvArngs         = append([]string{"int32:4S", "uint32:4S"}, integerUpTo16Bits...)
   128  	integer           = append([]string{"int64:2D", "uint64:2D"}, integerUpTo32Bits...)
   129  	integerWith1D     = append([]string{"int64:1D", "uint64:1D"}, integer...)
   130  	allArngs          = append(append([]string{}, floating...), integer...)
   131  	bitwise           = []string{
   132  		"int8:16B", "uint8:16B", "int8:8B", "uint8:8B",
   133  		"int16:16B", "uint16:16B", "int16:8B", "uint16:8B",
   134  		"int32:16B", "uint32:16B", "int32:8B", "uint32:8B",
   135  		"int64:16B", "uint64:16B",
   136  	}
   137  	fullwidth = []string{
   138  		"float32:4S", "float64:2D",
   139  		"int8:16B", "int16:8H", "int32:4S", "int64:2D",
   140  		"uint8:16B", "uint16:8H", "uint32:4S", "uint64:2D",
   141  	}
   142  )
   143  
   144  type Arm64InstructionTestSpec struct {
   145  	Pattern     string                              // to match instruction mnemonics
   146  	OpMatch     func(*testing.T, *Instruction) bool // returns true if operands match
   147  	ArngTest    func(*testing.T, *Instruction)
   148  	EmitAllTest func(*testing.T, *Instruction)
   149  }
   150  
   151  var arm64InstructionTests = []Arm64InstructionTestSpec{
   152  	{"^((UQ|SQ)?ADD|(UQ|SQ)?SUB)$", requireOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   153  	{"^ADDP$", matchOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   154  	{"^ADDP$", matchOps(unary), requireArngs([]string{"int64:2D", "uint64:2D"}, DefaultArngs), emitsDefs(2)},
   155  	{"^FADDP$", matchOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   156  	{"^FADDP$", matchOps(unary), requireArngs([]string{"float32:2S", "float64:2D"}, DefaultArngs), emitsDefs(2)},
   157  	{"^SABA$", matchOps(threeArgsResultInArg0), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)},
   158  	{"^SABAL$", matchOps(threeArgsResultInArg0), requireArngs(integerUpTo32Bits, LongArngs), emitsDefs(12)},
   159  	{"^F(ADD|SUB|DIV)$", requireOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   160  	{"^(AND|ORR|EOR|BIC|ORN)$", matchOps(binary), requireArngs(bitwise, DefaultArngs), emitsDefs(14)},
   161  	{"^NOT$", requireOps(unary), requireArngs(bitwise, DefaultArngs), emitsDefs(14)},
   162  	{"^CM(GT|GE|EQ)$", matchOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   163  	{"^CM(GT|GE|EQ)$", matchOps(unaryWithImm), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   164  	{"^CM(HI|HS|TST)$", requireOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   165  	{"^CM(LT|LE)$", requireOps(unaryWithImm), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   166  	{"^FCM(GT|GE|EQ)$", matchOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   167  	{"^FCM(GT|GE|EQ)$", matchOps(unaryWithImm), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   168  	{"^FCM(LT|LE)$", requireOps(unaryWithImm), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   169  	{"^(BIT|BIF|BSL)$", matchOps(threeArgsResultInArg0), requireArngs(bitwise, DefaultArngs), emitsDefs(14)},
   170  	{"^BCAX$", matchOps(fourOperands), requireArngs(bitwise16B, DefaultArngs), emitsDefs(2)},
   171  	{"^EOR3$", matchOps(fourOperands), requireArngs(bitwise16B, DefaultArngs), emitsDefs(2)},
   172  	{"^RAX1$", matchOps(binary), requireArngs(integer2D, DefaultArngs), emitsDefs(2)},
   173  	{"^XAR$", matchOps(binaryWithImm), requireArngs(integer2D, DefaultArngs), emitsDefs(2)},
   174  	{"^DUP$", matchOps(elemToVreg), requireArngs(allArngs, DefaultArngs), emitsDefs(17)},
   175  	{"^INS$", matchOps(insertFromLane), requireArngs(fullwidth, DefaultArngs), emitsDefs(10)},
   176  	{"^INS$", matchOps(insertFromGReg), requireArngs(fullwidth, DefaultArngs), emitsDefs(10)},
   177  	{"^UMOV$", matchOps(elemToGReg), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   178  	{"^EXT$", requireOps(binaryWithImm), requireArngs(integerUpTo8Bits, DefaultArngs), emitsDefs(4)},
   179  	{"^TBL$", requireOps(binary), requireArngs(integerUpTo8Bits, DefaultArngs), emitsDefs(4)},
   180  	{"^TBX$", requireOps(threeArgsResultInArg0), requireArngs(integerUpTo8Bits, DefaultArngs), emitsDefs(4)},
   181  	{"^REV16$", requireOps(unary), requireArngs(integerUpTo8Bits, DefaultArngs), emitsDefs(4)},
   182  	{"^REV32$", requireOps(unary), requireArngs(integerUpTo16Bits, DefaultArngs), emitsDefs(8)},
   183  	{"^REV64$", requireOps(unary), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)},
   184  	{"^(ZIP[12]|UZP[12]|TRN[12])$", requireOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   185  	{"^(S|U)(MIN|MAX)P?$", requireOps(binary), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)},
   186  	{"^((S|U)(MIN|MAX)|ADD)V$", requireOps(unary), requireArngs(addvArngs, DefaultArngs), emitsDefs(10)},
   187  	{"^F(MIN|MAX)(NM)?$", requireOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   188  	{"^F(MIN|MAX)(NM)?P$", matchOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   189  	{"^F(MIN|MAX)(NM)?V$", requireOps(unary), requireArngs(floatS32, DefaultArngs), emitsDefs(1)},
   190  	{"^(SQ)?(ABS|NEG)$", requireOps(unary), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   191  	{"^F(ABS|NEG)$", requireOps(unary), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   192  	{"^(S|U)SHL$", requireOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   193  	{"^(S|U)QSHL$", matchOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   194  	{"^(S|U)QSHL$", matchOps(unaryWithImm), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   195  	{"^SHL$", requireOps(unaryWithImm), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   196  	{"^(S|U)SHR$", requireOps(unaryWithImm), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   197  	{"^(S|U)SRA$", requireOps(unaryWithImmResultInArg0), requireArngs(integer, DefaultArngs), emitsDefs(14)},
   198  	{"^(S|U)SHLL$", requireOps(unaryWithImm), requireArngs(integerUpTo32Bits, LongArngs), emitsDefs(12)},
   199  	{"^SADALP$", matchOps(twoArgsResultInArg0), requireArngs(integerUpTo32Bits, LongArngs), emitsDefs(12)},
   200  	{"^((S|U)ADDLP)$", requireOps(unary), requireArngs(integerUpTo32Bits, LongArngs), emitsDefs(12)},
   201  	{"^(R?(ADD|SUB)HN)$", requireOps(binary), requireArngs(integerWideOnly, NarrowArngs), emitsDefs(6)},
   202  	{"^SHRN$", requireOps(unaryWithImm), requireArngs(integerWideOnly, NarrowArngs), emitsDefs(6)},
   203  	{"^(CLZ|CLS)$", requireOps(unary), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)},
   204  	{"^(CNT|RBIT)$", requireOps(unary), requireArngs(integerUpTo8Bits, DefaultArngs), emitsDefs(4)},
   205  	{"^(S|U)R?HADD$", matchOps(binary), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)},
   206  	{"^F(RINT(N|P|M|Z)?|SQRT)$", requireOps(unary), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   207  	{"^FMUL$", matchOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   208  	{"^F(MLA|MLS)$", matchOps(threeArgsResultInArg0), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   209  	{"^MUL$", matchOps(binary), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)},
   210  	{"^((S|U)MULL)$", matchOps(binary), requireArngs(integerUpTo32Bits, LongArngs), emitsDefs(12)},
   211  	{"^(MLA|MLS)$", matchOps(threeArgsResultInArg0), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)},
   212  	{"^((S|U)Q)?XTN$", requireOps(unary), requireArngs(integerWideOnly, NarrowArngs), emitsDefs(6)},
   213  	{"^(S|U)XTL$", requireOps(unary), requireArngs(integerUpTo32Bits, LongArngs), emitsDefs(12)},
   214  	{"^FCVT[NMPZ](S|U)$", matchOps(unary), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   215  	{"^(S|U)CVTF$", matchOps(unary), requireArngs(floating, DefaultArngs), emitsDefs(3)},
   216  	{"^(S|U)ADDW$", requireOps(binary), requireArngs(integerWideOnly, WideArngs), emitsDefs(6)},
   217  	{"^(S|U)SUBW$", requireOps(binary), requireArngs(integerWideOnly, WideArngs), emitsDefs(6)},
   218  	{"^FCVTL$", requireOps(unary), requireArngs([]string{"float32:2S", "float32:4S"}, LongArngs), emitsDefs(2)},
   219  	{"^USDOT$", matchOps(threeArgsResultInArg0), requireArngs(integer32And8Bits, UnsupportedArngs), emitsDefs(0)},
   220  	{"^PMULL$", matchOps(binary), requireArngs(polynomialArrngs, LongArngs), emitsDefs(8)},
   221  }
   222  
   223  func TestArm64Instructions(t *testing.T) {
   224  	if *arm64Path == "" {
   225  		t.Skip("ARM64 path not specified, use -arm64Path flag")
   226  	}
   227  
   228  	instructions, err := ParseInstructions(*arm64Path)
   229  	if err != nil {
   230  		t.Fatalf("ParseInstructions failed: %v", err)
   231  	}
   232  	t.Logf("parsed %d ARM64 instructions", len(instructions))
   233  
   234  	for _, spec := range arm64InstructionTests {
   235  		regex, err := regexp.Compile(spec.Pattern)
   236  		requireEqual(t, error(nil), err)
   237  
   238  		t.Run(spec.Pattern, func(t *testing.T) {
   239  			var instrCount int
   240  			var matches []*Instruction
   241  
   242  			for _, instr := range instructions {
   243  				if regex.MatchString(instr.Mnemonic()) {
   244  					instrCount++
   245  					if spec.OpMatch(t, instr) {
   246  						matches = append(matches, instr)
   247  					}
   248  				}
   249  			}
   250  			requireEqual(t, true, len(matches) > 0)
   251  			t.Logf("🔍 pattern %s: %d instructions, %d matched", spec.Pattern, instrCount, len(matches))
   252  
   253  			for _, instr := range matches {
   254  				t.Run(instr.Mnemonic(), func(t *testing.T) {
   255  					requireEqual(t, "advsimd", instr.InstrClass())
   256  
   257  					t.Run("Arrangements", func(t *testing.T) {
   258  						spec.ArngTest(t, instr)
   259  					})
   260  
   261  					t.Run("EmitAll", func(t *testing.T) {
   262  						spec.EmitAllTest(t, instr)
   263  					})
   264  				})
   265  			}
   266  		})
   267  	}
   268  }
   269  
   270  func TestMain(m *testing.M) {
   271  	flag.Parse()
   272  	os.Exit(m.Run())
   273  }
   274  

View as plain text