| // 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. |
| |
| package arm64 |
| |
| import ( |
| "flag" |
| "fmt" |
| "os" |
| "reflect" |
| "regexp" |
| "sort" |
| "testing" |
| ) |
| |
| var arm64Path = flag.String("arm64Path", "", "Path to ARM64 XML definitions") |
| |
| func requireEqual(t *testing.T, expected, actual interface{}) bool { |
| t.Helper() |
| if !reflect.DeepEqual(expected, actual) { |
| t.Errorf("❌ expected %v, got %v", expected, actual) |
| return false |
| } |
| |
| if expected != nil { |
| switch reflect.TypeOf(expected).Kind() { |
| case reflect.Slice, reflect.Array: |
| t.Logf("✅ %v", expected) |
| } |
| } |
| return true |
| } |
| |
| func matchEqual(t *testing.T, expected, actual interface{}) bool { |
| t.Helper() |
| eq := reflect.DeepEqual(expected, actual) |
| if eq && expected != nil { |
| switch reflect.TypeOf(expected).Kind() { |
| case reflect.Slice, reflect.Array: |
| t.Logf("✅ %v", expected) |
| } |
| } |
| return eq |
| } |
| |
| func arngs(t *testing.T, instr *Instruction, expectedArrangements []string, expectedShape ArngShape) { |
| t.Helper() |
| actualArrangements, actualShape := instr.Arrangements() |
| actualStrings := make([]string, len(actualArrangements)) |
| for i, arr := range actualArrangements { |
| actualStrings[i] = fmt.Sprintf("%s%d:%s", arr.baseType, arr.elemBits, arr.arrangement) |
| } |
| sort.Strings(actualStrings) |
| sort.Strings(expectedArrangements) |
| requireEqual(t, expectedArrangements, actualStrings) |
| requireEqual(t, expectedShape, actualShape) |
| } |
| |
| func ops(t *testing.T, instr *Instruction, expectedOps []string, equal func(*testing.T, interface{}, interface{}) bool) bool { |
| t.Helper() |
| templates := instr.templates() |
| if !equal(t, 1, len(templates)) { |
| return false |
| } |
| template := templates[0] |
| operands := template.operands |
| var actualOps []string |
| for _, operand := range operands { |
| opStr := fmt.Sprintf("%s:%d", operand.Type.String(), operand.AsmPos) |
| actualOps = append(actualOps, opStr) |
| } |
| return equal(t, expectedOps, actualOps) |
| } |
| |
| func matchOps(expectedOps []string) func(*testing.T, *Instruction) bool { |
| return func(t *testing.T, instr *Instruction) bool { |
| return ops(t, instr, expectedOps, matchEqual) |
| } |
| } |
| |
| func requireOps(expectedOps []string) func(*testing.T, *Instruction) bool { |
| return func(t *testing.T, instr *Instruction) bool { |
| return ops(t, instr, expectedOps, requireEqual) |
| } |
| } |
| |
| func requireArngs(expectedArngs []string, expectedShape ArngShape) func(*testing.T, *Instruction) { |
| return func(t *testing.T, instr *Instruction) { |
| arngs(t, instr, expectedArngs, expectedShape) |
| } |
| } |
| |
| func emitsDefs(expectedCount int) func(*testing.T, *Instruction) { |
| return func(t *testing.T, instr *Instruction) { |
| values := instr.EmitAll() |
| requireEqual(t, expectedCount, len(values)) |
| } |
| } |
| |
| var ( |
| // Operand patterns |
| binary = []string{"VReg:0", "VReg:1", "VReg:2"} |
| unary = []string{"VReg:0", "VReg:1"} |
| twoArgsResultInArg0 = []string{"VReg:0", "VReg:0", "VReg:1"} |
| unaryWithImm = []string{"VReg:0", "Imm:2", "VReg:1"} |
| unaryWithImmResultInArg0 = []string{"VReg:0", "Imm:2", "VReg:0", "VReg:1"} |
| binaryWithImm = []string{"VReg:0", "Imm:3", "VReg:1", "VReg:2"} |
| elemToVreg = []string{"VReg:0", "Imm:1", "VReg:1"} |
| insertFromLane = []string{"VReg:0", "Imm:0", "Imm:1", "VReg:0", "VReg:1"} |
| insertFromGReg = []string{"VReg:0", "Imm:0", "VReg:0", "GReg:1"} |
| threeArgsResultInArg0 = []string{"VReg:0", "VReg:0", "VReg:1", "VReg:2"} |
| fourOperands = []string{"VReg:0", "VReg:1", "VReg:2", "VReg:3"} |
| elemToGReg = []string{"GReg:0", "Imm:1", "VReg:1"} |
| |
| // Arrangement patterns |
| floatS32 = []string{"float32:4S"} |
| floating = []string{"float32:2S", "float32:4S", "float64:2D"} |
| bitwise16B = []string{"int8:16B", "uint8:16B"} |
| integer2D = []string{"int64:2D", "uint64:2D"} |
| integerUpTo8Bits = []string{"int8:16B", "int8:8B", "uint8:16B", "uint8:8B"} |
| integerUpTo16Bits = append([]string{"int16:4H", "int16:8H", "uint16:4H", "uint16:8H"}, integerUpTo8Bits...) |
| integerUpTo32Bits = append([]string{"int32:2S", "int32:4S", "uint32:2S", "uint32:4S"}, integerUpTo16Bits...) |
| integer32And8Bits = append([]string{"int32:2S", "int32:4S", "uint32:2S", "uint32:4S"}, integerUpTo8Bits...) |
| addvArngs = append([]string{"int32:4S", "uint32:4S"}, integerUpTo16Bits...) |
| integer = append([]string{"int64:2D", "uint64:2D"}, integerUpTo32Bits...) |
| integerWith1D = append([]string{"int64:1D", "uint64:1D"}, integer...) |
| allArngs = append(append([]string{}, floating...), integer...) |
| bitwise = []string{ |
| "int8:16B", "uint8:16B", "int8:8B", "uint8:8B", |
| "int16:16B", "uint16:16B", "int16:8B", "uint16:8B", |
| "int32:16B", "uint32:16B", "int32:8B", "uint32:8B", |
| "int64:16B", "uint64:16B", |
| } |
| fullwidth = []string{ |
| "float32:4S", "float64:2D", |
| "int8:16B", "int16:8H", "int32:4S", "int64:2D", |
| "uint8:16B", "uint16:8H", "uint32:4S", "uint64:2D", |
| } |
| ) |
| |
| type Arm64InstructionTestSpec struct { |
| Pattern string // to match instruction mnemonics |
| OpMatch func(*testing.T, *Instruction) bool // returns true if operands match |
| ArngTest func(*testing.T, *Instruction) |
| EmitAllTest func(*testing.T, *Instruction) |
| } |
| |
| var arm64InstructionTests = []Arm64InstructionTestSpec{ |
| {"^((UQ|SQ)?ADD|(UQ|SQ)?SUB)$", requireOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^ADDP$", matchOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^ADDP$", matchOps(unary), requireArngs([]string{"int64:2D", "uint64:2D"}, DefaultArngs), emitsDefs(2)}, |
| {"^FADDP$", matchOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^FADDP$", matchOps(unary), requireArngs([]string{"float32:2S", "float64:2D"}, DefaultArngs), emitsDefs(2)}, |
| {"^SABA$", matchOps(threeArgsResultInArg0), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)}, |
| {"^SABAL$", matchOps(threeArgsResultInArg0), requireArngs(integer, LongArngs), emitsDefs(14)}, |
| {"^F(ADD|SUB|DIV)$", requireOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^(AND|ORR|EOR|BIC|ORN)$", matchOps(binary), requireArngs(bitwise, DefaultArngs), emitsDefs(14)}, |
| {"^NOT$", requireOps(unary), requireArngs(bitwise, DefaultArngs), emitsDefs(14)}, |
| {"^CM(GT|GE|EQ)$", matchOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^CM(GT|GE|EQ)$", matchOps(unaryWithImm), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^CM(HI|HS|TST)$", requireOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^CM(LT|LE)$", requireOps(unaryWithImm), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^FCM(GT|GE|EQ)$", matchOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^FCM(GT|GE|EQ)$", matchOps(unaryWithImm), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^FCM(LT|LE)$", requireOps(unaryWithImm), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^(BIT|BIF|BSL)$", matchOps(threeArgsResultInArg0), requireArngs(bitwise, DefaultArngs), emitsDefs(14)}, |
| {"^BCAX$", matchOps(fourOperands), requireArngs(bitwise16B, DefaultArngs), emitsDefs(2)}, |
| {"^EOR3$", matchOps(fourOperands), requireArngs(bitwise16B, DefaultArngs), emitsDefs(2)}, |
| {"^RAX1$", matchOps(binary), requireArngs(integer2D, DefaultArngs), emitsDefs(2)}, |
| {"^XAR$", matchOps(binaryWithImm), requireArngs(integer2D, DefaultArngs), emitsDefs(2)}, |
| {"^DUP$", matchOps(elemToVreg), requireArngs(allArngs, DefaultArngs), emitsDefs(17)}, |
| {"^INS$", matchOps(insertFromLane), requireArngs(fullwidth, DefaultArngs), emitsDefs(10)}, |
| {"^INS$", matchOps(insertFromGReg), requireArngs(fullwidth, DefaultArngs), emitsDefs(10)}, |
| {"^UMOV$", matchOps(elemToGReg), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^EXT$", requireOps(binaryWithImm), requireArngs(integerUpTo8Bits, DefaultArngs), emitsDefs(4)}, |
| {"^TBL$", requireOps(binary), requireArngs(integerUpTo8Bits, DefaultArngs), emitsDefs(4)}, |
| {"^TBX$", requireOps(threeArgsResultInArg0), requireArngs(integerUpTo8Bits, DefaultArngs), emitsDefs(4)}, |
| {"^REV16$", requireOps(unary), requireArngs(integerUpTo8Bits, DefaultArngs), emitsDefs(4)}, |
| {"^REV32$", requireOps(unary), requireArngs(integerUpTo16Bits, DefaultArngs), emitsDefs(8)}, |
| {"^REV64$", requireOps(unary), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)}, |
| {"^(ZIP[12]|UZP[12]|TRN[12])$", requireOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^(S|U)(MIN|MAX)P?$", requireOps(binary), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)}, |
| {"^((S|U)(MIN|MAX)|ADD)V$", requireOps(unary), requireArngs(addvArngs, DefaultArngs), emitsDefs(10)}, |
| {"^F(MIN|MAX)(NM)?$", requireOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^F(MIN|MAX)(NM)?P$", matchOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^F(MIN|MAX)(NM)?V$", requireOps(unary), requireArngs(floatS32, DefaultArngs), emitsDefs(1)}, |
| {"^(SQ)?(ABS|NEG)$", requireOps(unary), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^F(ABS|NEG)$", requireOps(unary), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^(S|U)SHL$", requireOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^(S|U)QSHL$", matchOps(binary), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^(S|U)QSHL$", matchOps(unaryWithImm), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^SHL$", requireOps(unaryWithImm), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^(S|U)SHR$", requireOps(unaryWithImm), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^(S|U)SRA$", requireOps(unaryWithImmResultInArg0), requireArngs(integer, DefaultArngs), emitsDefs(14)}, |
| {"^(S|U)SHLL$", requireOps(unaryWithImm), requireArngs(integer, LongArngs), emitsDefs(14)}, |
| {"^SADALP$", matchOps(twoArgsResultInArg0), requireArngs(integerWith1D, LongArngs), emitsDefs(16)}, |
| {"^((S|U)ADDLP)$", requireOps(unary), requireArngs(integerWith1D, LongArngs), emitsDefs(16)}, |
| {"^(R?(ADD|SUB)HN)$", requireOps(binary), requireArngs(integer, NarrowArngs), emitsDefs(14)}, |
| {"^SHRN$", requireOps(unaryWithImm), requireArngs(integer, NarrowArngs), emitsDefs(14)}, |
| {"^(CLZ|CLS)$", requireOps(unary), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)}, |
| {"^(CNT|RBIT)$", requireOps(unary), requireArngs(integerUpTo8Bits, DefaultArngs), emitsDefs(4)}, |
| {"^(S|U)R?HADD$", matchOps(binary), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)}, |
| {"^F(RINT(N|P|M|Z)?|SQRT)$", requireOps(unary), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^FMUL$", matchOps(binary), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^F(MLA|MLS)$", matchOps(threeArgsResultInArg0), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^MUL$", matchOps(binary), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)}, |
| {"^((S|U)MULL)$", matchOps(binary), requireArngs(integer, LongArngs), emitsDefs(14)}, |
| {"^(MLA|MLS)$", matchOps(threeArgsResultInArg0), requireArngs(integerUpTo32Bits, DefaultArngs), emitsDefs(12)}, |
| {"^((S|U)Q)?XTN$", requireOps(unary), requireArngs(integer, NarrowArngs), emitsDefs(14)}, |
| {"^(S|U)XTL$", requireOps(unary), requireArngs(integer, LongArngs), emitsDefs(14)}, |
| {"^FCVT[NMPZ](S|U)$", matchOps(unary), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^(S|U)CVTF$", matchOps(unary), requireArngs(floating, DefaultArngs), emitsDefs(3)}, |
| {"^(S|U)ADDW$", requireOps(binary), requireArngs(integer, WideArngs), emitsDefs(14)}, |
| {"^(S|U)SUBW$", requireOps(binary), requireArngs(integer, WideArngs), emitsDefs(14)}, |
| {"^FCVTL$", requireOps(unary), requireArngs(floating, LongArngs), emitsDefs(3)}, |
| {"^USDOT$", matchOps(threeArgsResultInArg0), requireArngs(integer32And8Bits, UnsupportedArngs), emitsDefs(0)}, |
| } |
| |
| func TestArm64Instructions(t *testing.T) { |
| if *arm64Path == "" { |
| t.Skip("ARM64 path not specified, use -arm64Path flag") |
| } |
| |
| instructions, err := ParseInstructions(*arm64Path) |
| if err != nil { |
| t.Fatalf("ParseInstructions failed: %v", err) |
| } |
| t.Logf("parsed %d ARM64 instructions", len(instructions)) |
| |
| for _, spec := range arm64InstructionTests { |
| regex, err := regexp.Compile(spec.Pattern) |
| requireEqual(t, error(nil), err) |
| |
| t.Run(spec.Pattern, func(t *testing.T) { |
| var instrCount int |
| var matches []*Instruction |
| |
| for _, instr := range instructions { |
| if regex.MatchString(instr.Mnemonic()) { |
| instrCount++ |
| if spec.OpMatch(t, instr) { |
| matches = append(matches, instr) |
| } |
| } |
| } |
| requireEqual(t, true, len(matches) > 0) |
| t.Logf("🔍 pattern %s: %d instructions, %d matched", spec.Pattern, instrCount, len(matches)) |
| |
| for _, instr := range matches { |
| t.Run(instr.Mnemonic(), func(t *testing.T) { |
| requireEqual(t, "advsimd", instr.InstrClass()) |
| |
| t.Run("Arrangements", func(t *testing.T) { |
| spec.ArngTest(t, instr) |
| }) |
| |
| t.Run("EmitAll", func(t *testing.T) { |
| spec.EmitAllTest(t, instr) |
| }) |
| }) |
| } |
| }) |
| } |
| } |
| |
| func TestMain(m *testing.M) { |
| flag.Parse() |
| os.Exit(m.Run()) |
| } |