From 8231302bca9f1089c4c865e135447ce63cd86dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Wed, 7 Oct 2026 00:11:40 +0200 Subject: [PATCH] feat(arch): the SVE predicate family in the extended layer Assisted-by: GLM 5.3 Flash --- arch/arm64_ext.go | 788 +++++++++++++++++++++++++++++++++++++- arch/arm64_ext_test.go | 413 +++++++++++++++++++- asm/arm64_ext.go | 126 +++++- asm/arm64_ext_asm_test.go | 144 ++++++- asm/extension_test.go | 22 +- 5 files changed, 1451 insertions(+), 42 deletions(-) diff --git a/arch/arm64_ext.go b/arch/arm64_ext.go index f01a812..1efe3ca 100644 --- a/arch/arm64_ext.go +++ b/arch/arm64_ext.go @@ -9,11 +9,13 @@ // per architecture; the amd64 side of the layer lives in amd64_ext.go and // attaches through the same door. // -// The first entry is the arm64 SVE and SVE2 integer add/subtract/multiply -// family (twenty-three forms over four word shapes). The encodings are -// transcribed from the manual and cross-checked against the GNU assembler's -// and LLVM's published encodings; the golden vectors in arm64_ext_test.go pin -// the bytes. +// The first entries are the arm64 SVE and SVE2 integer add/subtract/multiply +// family (twenty-three forms over four word shapes) and the SVE and SVE2.1 +// predicate family (logical operations, breaks, permutations, tests, the +// first-fault register group and the while compares). The encodings are +// transcribed from the manual and cross-checked against the Go toolchain's +// generated encoding table and corpus; the golden vectors in +// arm64_ext_test.go pin the bytes. package arch @@ -35,6 +37,9 @@ const ( ExtR32 // 32-bit general register EAX-R15D ExtR64 // 64-bit general register RAX-R15 ExtMem // base-relative memory operand, 4660(R8) style + // The arm64 layer's general and counter registers. + ExtGReg // general register R0-R30 + ExtPNReg // predicate-as-counter register PN8-PN15 ) // String returns a short label for the kind. @@ -60,6 +65,10 @@ func (k ExtOperandKind) String() string { return "64-bit general register" case ExtMem: return "memory operand" + case ExtGReg: + return "general register" + case ExtPNReg: + return "predicate-as-counter register" default: return "operand" } @@ -156,8 +165,8 @@ func (q ExtQualifier) String() string { // from the parsed statement; Encode consumes them. type ExtOperand struct { Kind ExtOperandKind - // Reg is the register number (Z: 0..31, P: 0..15); under ExtMem it is - // the base general register, 0..15. + // Reg is the register number (Z: 0..31, P: 0..15, R: 0..30, PN: 8..15); + // under ExtMem it is the base general register, 0..15. Reg int Arr ExtArrangement // element-size suffix; ExtArrNone when bare Qual ExtQualifier // predicate qualifier; ExtQualNone elsewhere @@ -206,6 +215,24 @@ func ExtPredicate(reg int, qual ExtQualifier) ExtOperand { return ExtOperand{Kind: ExtPReg, Reg: reg, Qual: qual} } +// ExtPredicateSized builds a predicate operand carrying an element-size +// suffix, P4.B style. +func ExtPredicateSized(reg int, arr ExtArrangement) ExtOperand { + return ExtOperand{Kind: ExtPReg, Reg: reg, Arr: arr} +} + +// ExtCounterPredicate builds a predicate-as-counter operand, PN14.S style. +// The counter spellings run PN8-PN15, the convention the toolchain's +// generated table and corpus use for the three-bit counter field. +func ExtCounterPredicate(reg int, arr ExtArrangement) ExtOperand { + return ExtOperand{Kind: ExtPNReg, Reg: reg, Arr: arr} +} + +// ExtGeneral builds a general register operand, R2 style. +func ExtGeneral(reg int) ExtOperand { + return ExtOperand{Kind: ExtGReg, Reg: reg} +} + // ExtImmediate builds an unshifted immediate operand. func ExtImmediate(v int64) ExtOperand { return ExtOperand{Kind: ExtImm, Imm: v} @@ -261,6 +288,13 @@ func extSet(word uint32, f ExtField, v uint32) uint32 { // named after their role in the three-vector class; the predicated class // reuses extFieldRn for its Zm operand and extFieldPg for the governing // predicate, which that class narrows to three bits (P0-P7). +// +// The predicate classes place their operands in the same neighbourhood: +// Pd in a four-bit field at bit 0, the governing predicate either narrow +// (three bits, P0-P7) or wide (four bits, P0-P15) at bit 10, the first +// source predicate in the five-bit slot at bit 5, and Pm in a three-bit +// field at bit 16. The general registers of the while-compare class use +// the five-bit Rm slot at bit 16. var ( extFieldRd = ExtField{0, 5} // destination (Zd or Zdn) extFieldRn = ExtField{5, 5} // first source (Zn, or Zm in the predicated class) @@ -269,6 +303,12 @@ var ( extFieldImm8 = ExtField{5, 8} // the immediate, bits 12..5 extFieldSh = ExtField{13, 1} // the shift flag: 1 means LSL #8 extSizeBHSD = ExtField{22, 2} // element-size field of every class here, bits 23..22 + extFieldPd = ExtField{0, 4} // predicate destination P0-P15 + extFieldPn = ExtField{5, 5} // predicate source slot P0-P15 (bit 4 unused here) + extFieldPgN = ExtField{10, 3} // narrow governing predicate P0-P7 + extFieldPgW = ExtField{10, 4} // wide governing predicate P0-P15 + extFieldPm = ExtField{16, 3} // predicate source P0-P7 + extFieldPnc = ExtField{0, 3} // predicate-as-counter destination PN8-PN15, field holds reg-8 ) // ExtForm enumerates the operand shapes the extension layer defines, in Plan @@ -344,14 +384,88 @@ const ( // ExtFormAmdVecMem is the memory-store form: VMOVSH 4660(R9), X29 // shape, the manual's m16, xmm1 lines. Operands: src, mem. ExtFormAmdVecMem + // ExtFormPredicateLogical is the predicate logical-operation form of the + // SVE2.1 predicate classes: PAND P4.B, P2.B, P1.Z, P14.B computes + // P14 = P4 AND P2 over the active lanes of P1. Operands: Pm.B, Pn.B, + // Pg/Z, Pd.B. The zeroing qualifier on the governing predicate is part + // of the spelling; every arrangement is .B and the class carries no size + // field. + ExtFormPredicateLogical + // ExtFormPredicateSelect is PSEL's form, PSEL P4.B, P2.B, P1, P14.B: + // the governing predicate arrives bare, without a qualifier. Otherwise + // identical to ExtFormPredicateLogical. + ExtFormPredicateSelect + // ExtFormPredicateLogicalDest is the break form whose first operand is + // the destination read back: PBRKN P4.B, P2.B, P1.Z, P4.B. Operands: + // Pdm.B, Pn.B, Pg/Z, Pdm.B, with operand 1 required to equal operand 4; + // the encoding carries the register once. + ExtFormPredicateLogicalDest + // ExtFormPredicateBreak is the three-operand break form, PBRKA P5.B, + // P9.Z, P2.B. Operands: Pn.B, Pg/Z or Pg/M (both spellings encode the + // one word the manual gives), Pd.B. The governing predicate is the wide + // four-bit field, P0-P15. + ExtFormPredicateBreak + // ExtFormPredicateBreakZero is ExtFormPredicateBreak with the zeroing + // qualifier alone, PBRKAS P5.B, P9.Z, P4.B. + ExtFormPredicateBreakZero + // ExtFormPredicatePermute is the predicate permutation form, PTRN1 P5.D, + // P4.D, P2.D. Operands: Pm.T, Pn.T, Pd.T under one shared arrangement + // (.B, .H, .S or .D), which feeds the size field. + ExtFormPredicatePermute + // ExtFormPredicateFirst is PFIRST's form, PPFIRST P5.B, P9, P5.B, with + // the destination read back like ExtFormPredicateLogicalDest. Operands: + // Pdn.B, Pg (bare), Pdn.B. + ExtFormPredicateFirst + // ExtFormPredicateNext is PNEXT's form, PPNEXT P5.D, P4, P5.D, the + // destination-read-back shape with an arrangement and a size field. + // Operands: Pdn.T, Pv (bare), Pdn.T. + ExtFormPredicateNext + // ExtFormPredicateOne writes or reads a single predicate, PPFALSE P13.B + // or the unpredicated RDFFR P13.B. Operands: Pd.B. + ExtFormPredicateOne + // ExtFormPredicateWrite is WRFFR's single-source form, PWRFFR P13.B: the + // one predicate sits in the source slot, not the destination. + // Operands: Pn.B. + ExtFormPredicateWrite + // ExtFormPredicateFFRRead is the predicated first-fault read, PRDFFR + // P14.Z, P0.B. Operands: Pg/Z, Pd.B; the governing predicate is the + // wide four-bit field. + ExtFormPredicateFFRRead + // ExtFormPredicateMove is the predicate reverse, PREV P14.S, P13.S. + // Operands: Pn.T, Pd.T under one shared arrangement. + ExtFormPredicateMove + // ExtFormPredicateUnpack is the predicate unpack, PPUNPKHI P14.B, P0.H: + // the arrangements are fixed by the class (.B source, .H destination) + // and carry no size field. Operands: Pn.B, Pd.H. + ExtFormPredicateUnpack + // ExtFormPredicateTest is PTEST, PPTEST P14.B, P0, which sets the flags + // and takes no destination. Operands: Pn.B, Pg (bare). + ExtFormPredicateTest + // ExtFormWhile is the while-compare form, PWHILELT R2, R10, P10.H: + // P10 counts the lanes where the signed comparison holds. Operands: + // Rm (R0-R30), Rn (R0-R30), Pd.T, the arrangement feeding the size + // field. + ExtFormWhile + // ExtFormPredicateCounter is PTRUE's counter-register form, PPTRUE + // PN14.S: the destination is a predicate-as-counter register, spelled + // PN8-PN15 and encoded as reg-8 in the three-bit field. Operands: + // PNd.T. + ExtFormPredicateCounter + // ExtFormNone takes no operands at all, SETFFR. + ExtFormNone ) // Arity returns the operand count the form takes. func (f ExtForm) Arity() int { switch f { - case ExtFormVectors, ExtFormPredicated: + case ExtFormVectors, ExtFormPredicated, ExtFormPredicatePermute, + ExtFormPredicateFirst, ExtFormPredicateNext, ExtFormWhile, + ExtFormPredicateBreak, ExtFormPredicateBreakZero: return 3 - case ExtFormImmediate, ExtFormSignedImmediate: + case ExtFormPredicateLogical, ExtFormPredicateSelect, ExtFormPredicateLogicalDest: + return 4 + case ExtFormImmediate, ExtFormSignedImmediate, ExtFormPredicateFFRRead, + ExtFormPredicateMove, ExtFormPredicateUnpack, ExtFormPredicateTest: return 2 case ExtFormAmdVec3, ExtFormAmdMask2, ExtFormAmdVecGprVec: return 3 @@ -361,6 +475,10 @@ func (f ExtForm) Arity() int { return 4 case ExtFormAmdMemVec, ExtFormAmdVecMem: return 2 + case ExtFormPredicateOne, ExtFormPredicateWrite, ExtFormPredicateCounter: + return 1 + case ExtFormNone: + return 0 default: return 0 } @@ -380,6 +498,22 @@ func (f ExtForm) Kinds() []ExtOperandKind { // The amd64 forms return no kinds: the exact vector class depends on the // entry's encoding template (its L'L field), which the form alone cannot // name, and the encode paths diagnose the class themselves. + case ExtFormPredicateLogical, ExtFormPredicateSelect, ExtFormPredicateLogicalDest: + return []ExtOperandKind{ExtPReg, ExtPReg, ExtPReg, ExtPReg} + case ExtFormPredicateBreak, ExtFormPredicateBreakZero, ExtFormPredicateFFRRead: + return []ExtOperandKind{ExtPReg, ExtPReg, ExtPReg} + case ExtFormPredicatePermute: + return []ExtOperandKind{ExtPReg, ExtPReg, ExtPReg} + case ExtFormPredicateFirst, ExtFormPredicateNext: + return []ExtOperandKind{ExtPReg, ExtPReg, ExtPReg} + case ExtFormPredicateOne, ExtFormPredicateWrite, ExtFormPredicateCounter: + return []ExtOperandKind{ExtPReg} + case ExtFormPredicateMove, ExtFormPredicateUnpack, ExtFormPredicateTest: + return []ExtOperandKind{ExtPReg, ExtPReg} + case ExtFormWhile: + return []ExtOperandKind{ExtGReg, ExtGReg, ExtPReg} + case ExtFormNone: + return nil default: return nil } @@ -418,6 +552,40 @@ func (f ExtForm) String() string { return "memory into a vector" case ExtFormAmdVecMem: return "a vector into memory" + case ExtFormPredicateLogical: + return "predicate logical" + case ExtFormPredicateSelect: + return "predicate select" + case ExtFormPredicateLogicalDest: + return "predicate logical, shared destination" + case ExtFormPredicateBreak: + return "break" + case ExtFormPredicateBreakZero: + return "break, zeroing" + case ExtFormPredicatePermute: + return "predicate permutation" + case ExtFormPredicateFirst: + return "predicate first" + case ExtFormPredicateNext: + return "predicate next" + case ExtFormPredicateOne: + return "single predicate" + case ExtFormPredicateWrite: + return "single source predicate" + case ExtFormPredicateFFRRead: + return "FFR read" + case ExtFormPredicateMove: + return "predicate move" + case ExtFormPredicateUnpack: + return "predicate unpack" + case ExtFormPredicateTest: + return "predicate test" + case ExtFormWhile: + return "while compare" + case ExtFormPredicateCounter: + return "counter predicate" + case ExtFormNone: + return "no operands" default: return "unknown form" } @@ -430,8 +598,9 @@ type ExtFeature string // The features the arm64 layer covers. const ( - ExtFeatureSVE ExtFeature = "sve" - ExtFeatureSVE2 ExtFeature = "sve2" + ExtFeatureSVE ExtFeature = "sve" + ExtFeatureSVE2 ExtFeature = "sve2" + ExtFeatureSVE2p1 ExtFeature = "sve2p1" ) // ExtImm8Kind names the imm8-control layout an amd64 extended entry carries @@ -545,6 +714,38 @@ func (in ExtInstr) Encode(ops []ExtOperand) ([]byte, error) { ExtFormAmdVecGprVec, ExtFormAmdGprVec, ExtFormAmdVecGpr, ExtFormAmdVec3Imm, ExtFormAmdMask2Imm, ExtFormAmdMemVec, ExtFormAmdVecMem: return in.encodeAmd64(ops) + case ExtFormPredicateLogical, ExtFormPredicateSelect: + return in.encodePredicateLogical(ops) + case ExtFormPredicateLogicalDest: + return in.encodePredicateLogicalDest(ops) + case ExtFormPredicateBreak, ExtFormPredicateBreakZero: + return in.encodePredicateBreak(ops) + case ExtFormPredicatePermute: + return in.encodePredicatePermute(ops) + case ExtFormPredicateFirst, ExtFormPredicateNext: + return in.encodePredicateReadBack(ops) + case ExtFormPredicateOne: + return in.encodePredicateOne(ops) + case ExtFormPredicateWrite: + return in.encodePredicateWrite(ops) + case ExtFormPredicateFFRRead: + return in.encodePredicateFFRRead(ops) + case ExtFormPredicateMove: + return in.encodePredicateMove(ops) + case ExtFormPredicateUnpack: + return in.encodePredicateUnpack(ops) + case ExtFormPredicateTest: + return in.encodePredicateTest(ops) + case ExtFormWhile: + return in.encodeWhile(ops) + case ExtFormPredicateCounter: + return in.encodePredicateCounter(ops) + case ExtFormNone: + if len(ops) != 0 { + return nil, fmt.Errorf("%s: the %s form takes no operands, got %d", + in.Name, in.Form, len(ops)) + } + return extWordLE(in.Word), nil default: return nil, fmt.Errorf("%s: unknown form %d", in.Name, in.Form) } @@ -712,6 +913,382 @@ func (arr ExtArrangement) shiftNote() string { return " (a multiple of 256 up to 65280 shifts)" } +// predicateOperand validates one predicate operand: its kind, its register +// range and, when want is non-empty, the one arrangement the class takes. +func (in ExtInstr) predicateOperand(op ExtOperand, position int, lo, hi int, want ExtArrangement) error { + if op.Kind != ExtPReg { + return fmt.Errorf("%s: operand %d wants a predicate register, got %s", + in.Name, position, op.Kind) + } + if op.Reg < lo || op.Reg > hi { + return fmt.Errorf("%s: operand %d is P%d, outside P%d-P%d in this class", + in.Name, position, op.Reg, lo, hi) + } + if want != ExtArrNone && op.Arr != want { + return fmt.Errorf("%s: operand %d carries arrangement %s, want %s", + in.Name, position, op.Arr, want) + } + return nil +} + +// encodePredicateLogical fills the four-operand predicate logical form: +// Pm.B, Pn.B, Pg{qualifier}, Pd.B. Pm takes P0-P7, Pn and Pd P0-P15; the +// governing predicate takes P0-P7 and its qualifier is zeroing for the +// logical operations and bare for PSEL, which the form distinguishes. +func (in ExtInstr) encodePredicateLogical(ops []ExtOperand) ([]byte, error) { + pm, pn, pg, pd := ops[0], ops[1], ops[2], ops[3] + if err := in.predicateOperand(pm, 1, 0, 7, ExtArrB); err != nil { + return nil, err + } + if err := in.predicateOperand(pn, 2, 0, 15, ExtArrB); err != nil { + return nil, err + } + // The governing predicate carries its qualifier in place of an + // arrangement (P1.Z), so the form judges the qualifier below. + if err := in.predicateOperand(pg, 3, 0, 7, ExtArrNone); err != nil { + return nil, err + } + if pg.Arr != ExtArrNone { + return nil, fmt.Errorf("%s: the governing predicate carries no arrangement suffix, got %s", + in.Name, pg.Arr) + } + if err := in.predicateOperand(pd, 4, 0, 15, ExtArrB); err != nil { + return nil, err + } + wantQual := ExtQualZeroing + if in.Form == ExtFormPredicateSelect { + wantQual = ExtQualNone + } + if pg.Qual != wantQual { + if wantQual == ExtQualNone { + return nil, fmt.Errorf("%s: operand 3 takes no qualifier, got %q", + in.Name, pg.Qual) + } + return nil, fmt.Errorf("%s: operand 3 wants the zeroing qualifier, got %q", + in.Name, pg.Qual) + } + word := in.Word + word = extSet(word, extFieldPm, uint32(pm.Reg)) + word = extSet(word, extFieldPn, uint32(pn.Reg)) + word = extSet(word, extFieldPgN, uint32(pg.Reg)) + word = extSet(word, extFieldPd, uint32(pd.Reg)) + return extWordLE(word), nil +} + +// encodePredicateLogicalDest fills the break form whose first operand is the +// destination read back: Pdm.B, Pn.B, Pg/Z, Pdm.B. The encoding carries the +// register once, so operands 1 and 4 must name the same register. +func (in ExtInstr) encodePredicateLogicalDest(ops []ExtOperand) ([]byte, error) { + pdm, pn, pg := ops[0], ops[1], ops[2] + if err := in.predicateOperand(pdm, 1, 0, 15, ExtArrB); err != nil { + return nil, err + } + if err := in.predicateOperand(pn, 2, 0, 15, ExtArrB); err != nil { + return nil, err + } + if err := in.predicateOperand(pg, 3, 0, 7, ExtArrNone); err != nil { + return nil, err + } + if pg.Arr != ExtArrNone { + return nil, fmt.Errorf("%s: the governing predicate carries no arrangement suffix, got %s", + in.Name, pg.Arr) + } + if err := in.predicateOperand(ops[3], 4, 0, 15, ExtArrB); err != nil { + return nil, err + } + if pg.Qual != ExtQualZeroing { + return nil, fmt.Errorf("%s: operand 3 wants the zeroing qualifier, got %q", + in.Name, pg.Qual) + } + if ops[3].Reg != pdm.Reg { + return nil, fmt.Errorf("%s: operands 1 and 4 are the same register Pdm, got P%d and P%d", + in.Name, pdm.Reg, ops[3].Reg) + } + word := in.Word + word = extSet(word, extFieldPn, uint32(pn.Reg)) + word = extSet(word, extFieldPgN, uint32(pg.Reg)) + word = extSet(word, extFieldPd, uint32(pdm.Reg)) + return extWordLE(word), nil +} + +// encodePredicateBreak fills the three-operand break form: Pn.B, Pg{qual}, +// Pd.B. The governing predicate is the wide four-bit field (P0-P15); the +// plain break accepts the merging or the zeroing qualifier, the S variant +// the zeroing alone. +func (in ExtInstr) encodePredicateBreak(ops []ExtOperand) ([]byte, error) { + pn, pg, pd := ops[0], ops[1], ops[2] + if err := in.predicateOperand(pn, 1, 0, 15, ExtArrB); err != nil { + return nil, err + } + if err := in.predicateOperand(pg, 2, 0, 15, ExtArrNone); err != nil { + return nil, err + } + if err := in.predicateOperand(pd, 3, 0, 15, ExtArrB); err != nil { + return nil, err + } + switch { + case pg.Qual == ExtQualZeroing: + case pg.Qual == ExtQualMerging && in.Form == ExtFormPredicateBreak: + default: + return nil, fmt.Errorf("%s: operand 2 wants the zeroing qualifier%s, got %q", + in.Name, mergingNote(in.Form), pg.Qual) + } + word := in.Word + word = extSet(word, extFieldPn, uint32(pn.Reg)) + word = extSet(word, extFieldPgW, uint32(pg.Reg)) + word = extSet(word, extFieldPd, uint32(pd.Reg)) + return extWordLE(word), nil +} + +// mergingNote describes the qualifier rule of a break form, for the error. +func mergingNote(f ExtForm) string { + if f == ExtFormPredicateBreak { + return " or the merging qualifier" + } + return "" +} + +// encodePredicatePermute fills the predicate permutation form: Pm.T, Pn.T, +// Pd.T under one shared arrangement, which feeds the size field. +func (in ExtInstr) encodePredicatePermute(ops []ExtOperand) ([]byte, error) { + pm, pn, pd := ops[0], ops[1], ops[2] + if err := in.predicateOperand(pm, 1, 0, 7, ExtArrNone); err != nil { + return nil, err + } + if err := in.predicateOperand(pn, 2, 0, 15, ExtArrNone); err != nil { + return nil, err + } + if err := in.predicateOperand(pd, 3, 0, 15, ExtArrNone); err != nil { + return nil, err + } + size, err := in.sharedSize(pm.Arr, pn.Arr, pd.Arr) + if err != nil { + return nil, err + } + word := in.Word + word = extSet(word, extFieldPm, uint32(pm.Reg)) + word = extSet(word, extFieldPn, uint32(pn.Reg)) + word = extSet(word, extFieldPd, uint32(pd.Reg)) + word = extSet(word, in.Size, size) + return extWordLE(word), nil +} + +// encodePredicateReadBack fills the destination-read-back forms of PFIRST +// and PNEXT: Pdn{.B or .T}, Pg or Pv (bare), Pdn. Operands 1 and 3 name the +// same register; PNEXT carries the size field, PFIRST is .B alone. +func (in ExtInstr) encodePredicateReadBack(ops []ExtOperand) ([]byte, error) { + pdn, pg := ops[0], ops[1] + if err := in.predicateOperand(pdn, 1, 0, 15, ExtArrNone); err != nil { + return nil, err + } + if err := in.predicateOperand(pg, 2, 0, 15, ExtArrNone); err != nil { + return nil, err + } + if err := in.predicateOperand(ops[2], 3, 0, 15, ExtArrNone); err != nil { + return nil, err + } + if pg.Arr != ExtArrNone { + return nil, fmt.Errorf("%s: operand 2 carries no arrangement suffix, got %s", + in.Name, pg.Arr) + } + if pg.Qual != ExtQualNone { + return nil, fmt.Errorf("%s: operand 2 takes no qualifier, got %q", in.Name, pg.Qual) + } + if ops[2].Reg != pdn.Reg || ops[2].Arr != pdn.Arr { + return nil, fmt.Errorf("%s: operands 1 and 3 are the same register Pdn, got P%d and P%d", + in.Name, pdn.Reg, ops[2].Reg) + } + var size uint32 + if in.Form == ExtFormPredicateNext { + var ok bool + size, ok = pdn.Arr.sizeBits() + if !ok { + return nil, fmt.Errorf("%s: arrangement %s has no size encoding in this class", + in.Name, pdn.Arr) + } + } else if pdn.Arr != ExtArrB { + return nil, fmt.Errorf("%s: operand 1 carries arrangement %s, want .B", + in.Name, pdn.Arr) + } + word := in.Word + word = extSet(word, extFieldPn, uint32(pg.Reg)) + word = extSet(word, extFieldPd, uint32(pdn.Reg)) + if size != 0 { + word = extSet(word, in.Size, size) + } + return extWordLE(word), nil +} + +// encodePredicateOne fills the single-predicate form: Pd.B, destination in +// the four-bit field. +func (in ExtInstr) encodePredicateOne(ops []ExtOperand) ([]byte, error) { + if err := in.predicateOperand(ops[0], 1, 0, 15, ExtArrB); err != nil { + return nil, err + } + word := extSet(in.Word, extFieldPd, uint32(ops[0].Reg)) + return extWordLE(word), nil +} + +// encodePredicateWrite fills WRFFR's single-source form: Pn.B in the +// five-bit source slot. +func (in ExtInstr) encodePredicateWrite(ops []ExtOperand) ([]byte, error) { + if err := in.predicateOperand(ops[0], 1, 0, 15, ExtArrB); err != nil { + return nil, err + } + word := extSet(in.Word, extFieldPn, uint32(ops[0].Reg)) + return extWordLE(word), nil +} + +// encodePredicateFFRRead fills the predicated first-fault read: Pg/Z, Pd.B. +// The first operand is the mask the read passes through, so it sits in the +// five-bit source slot, not in a governing-predicate field. +func (in ExtInstr) encodePredicateFFRRead(ops []ExtOperand) ([]byte, error) { + pg, pd := ops[0], ops[1] + if err := in.predicateOperand(pg, 1, 0, 15, ExtArrNone); err != nil { + return nil, err + } + if err := in.predicateOperand(pd, 2, 0, 15, ExtArrB); err != nil { + return nil, err + } + if pg.Qual != ExtQualZeroing { + return nil, fmt.Errorf("%s: operand 1 wants the zeroing qualifier, got %q", + in.Name, pg.Qual) + } + word := in.Word + word = extSet(word, extFieldPn, uint32(pg.Reg)) + word = extSet(word, extFieldPd, uint32(pd.Reg)) + return extWordLE(word), nil +} + +// encodePredicateMove fills the predicate reverse: Pn.T, Pd.T under one +// shared arrangement. +func (in ExtInstr) encodePredicateMove(ops []ExtOperand) ([]byte, error) { + pn, pd := ops[0], ops[1] + if err := in.predicateOperand(pn, 1, 0, 15, ExtArrNone); err != nil { + return nil, err + } + if err := in.predicateOperand(pd, 2, 0, 15, ExtArrNone); err != nil { + return nil, err + } + size, err := in.sharedSize(pn.Arr, pd.Arr) + if err != nil { + return nil, err + } + word := in.Word + word = extSet(word, extFieldPn, uint32(pn.Reg)) + word = extSet(word, extFieldPd, uint32(pd.Reg)) + word = extSet(word, in.Size, size) + return extWordLE(word), nil +} + +// encodePredicateUnpack fills the predicate unpack: Pn.B, Pd.H, both +// arrangements fixed by the class. +func (in ExtInstr) encodePredicateUnpack(ops []ExtOperand) ([]byte, error) { + pn, pd := ops[0], ops[1] + if err := in.predicateOperand(pn, 1, 0, 15, ExtArrB); err != nil { + return nil, err + } + if err := in.predicateOperand(pd, 2, 0, 15, ExtArrH); err != nil { + return nil, err + } + word := in.Word + word = extSet(word, extFieldPn, uint32(pn.Reg)) + word = extSet(word, extFieldPd, uint32(pd.Reg)) + return extWordLE(word), nil +} + +// encodePredicateTest fills PTEST: Pn.B, Pg (bare), no destination. +func (in ExtInstr) encodePredicateTest(ops []ExtOperand) ([]byte, error) { + pn, pg := ops[0], ops[1] + if err := in.predicateOperand(pn, 1, 0, 15, ExtArrB); err != nil { + return nil, err + } + if err := in.predicateOperand(pg, 2, 0, 15, ExtArrNone); err != nil { + return nil, err + } + if pg.Qual != ExtQualNone { + return nil, fmt.Errorf("%s: operand 2 takes no qualifier, got %q", in.Name, pg.Qual) + } + word := in.Word + word = extSet(word, extFieldPn, uint32(pn.Reg)) + word = extSet(word, extFieldPgW, uint32(pg.Reg)) + return extWordLE(word), nil +} + +// encodeWhile fills the while-compare form: Rm, Rn, Pd.T, the arrangement +// feeding the size field. The general registers run R0-R30; the class takes +// no stack pointer and no zero register spelling. +func (in ExtInstr) encodeWhile(ops []ExtOperand) ([]byte, error) { + rm, rn, pd := ops[0], ops[1], ops[2] + for i, op := range []ExtOperand{rm, rn} { + if op.Kind != ExtGReg { + return nil, fmt.Errorf("%s: operand %d wants a general register, got %s", + in.Name, i+1, op.Kind) + } + if op.Reg < 0 || op.Reg > 30 { + return nil, fmt.Errorf("%s: operand %d is R%d, outside R0-R30", + in.Name, i+1, op.Reg) + } + } + if err := in.predicateOperand(pd, 3, 0, 15, ExtArrNone); err != nil { + return nil, err + } + size, ok := pd.Arr.sizeBits() + if !ok { + return nil, fmt.Errorf("%s: arrangement %s has no size encoding in this class", + in.Name, pd.Arr) + } + word := in.Word + word = extSet(word, extFieldRm, uint32(rm.Reg)) + word = extSet(word, extFieldPn, uint32(rn.Reg)) + word = extSet(word, extFieldPd, uint32(pd.Reg)) + word = extSet(word, in.Size, size) + return extWordLE(word), nil +} + +// encodePredicateCounter fills PTRUE's counter form: PNd.T, the +// predicate-as-counter destination spelled PN8-PN15 and encoded as reg-8. +func (in ExtInstr) encodePredicateCounter(ops []ExtOperand) ([]byte, error) { + pnd := ops[0] + if pnd.Kind != ExtPNReg { + return nil, fmt.Errorf("%s: operand 1 wants a predicate-as-counter register, got %s", + in.Name, pnd.Kind) + } + if pnd.Reg < 8 || pnd.Reg > 15 { + return nil, fmt.Errorf("%s: operand 1 is PN%d, outside PN8-PN15", in.Name, pnd.Reg) + } + size, ok := pnd.Arr.sizeBits() + if !ok { + return nil, fmt.Errorf("%s: arrangement %s has no size encoding in this class", + in.Name, pnd.Arr) + } + word := in.Word + word = extSet(word, extFieldPnc, uint32(pnd.Reg-8)) + word = extSet(word, in.Size, size) + return extWordLE(word), nil +} + +// sharedSize returns the one element-size encoding the given arrangements +// agree on, or an error when any operand is bare, they disagree, or the +// arrangement has no size field. +func (in ExtInstr) sharedSize(arrs ...ExtArrangement) (uint32, error) { + arr := arrs[0] + for i, a := range arrs { + if a == ExtArrNone { + return 0, fmt.Errorf("%s: operand %d carries no arrangement suffix", in.Name, i+1) + } + if a != arr { + return 0, fmt.Errorf("%s: operand %d carries arrangement %s, want %s", + in.Name, i+1, a, arr) + } + } + size, ok := arr.sizeBits() + if !ok { + return 0, fmt.Errorf("%s: arrangement %s has no size encoding in this class", in.Name, arr) + } + return size, nil +} + // setDestAndSize fills the destructive destination register and the size // field from the arrangement the vector carries. func (in ExtInstr) setDestAndSize(word uint32, zdn ExtOperand) (uint32, error) { @@ -837,6 +1414,195 @@ var arm64Extensions = []ExtInstr{ {Name: "MUL", Summary: "Multiply scalable vector elements by a signed immediate", Word: 0x2530c000, Form: ExtFormSignedImmediate, Size: extSizeBHSD, Feature: ExtFeatureSVE, Ref: "ARM DDI 0487J, C8.2 SVE instruction descriptions: MUL (vectors, immediate)"}, + + // --- the SVE and SVE2.1 predicate family --------------------------------- + // + // The classes below operate on predicates and predicate-as-counter + // registers. Their encodings follow the field layouts the ARM + // Architecture Reference Manual's SVE2.1 predicate classes give, as laid + // out in the toolchain's generated encoding table + // (cmd/internal/obj/arm64/inst_gen.go, itself generated by + // x/arch/arm64/instgen from Arm's official ISA description) and in its + // generated corpus file arm64sveenc.s: the golden test pins every + // corpus line of each class byte for byte, so no class rests on + // transcription alone. The toolchain's spellings are kept, including + // its P prefix on the predicate-register instructions (PRDFFR for + // RDFFR, PPTRUE for PTRUE, PREV for the predicate REV), because those + // are the names the corpus assembles under. + + // Predicate logical operations, SVE2.1: PAND P4.B, P2.B, P1.Z, P14.B. + {Name: "PAND", Summary: "And predicates over a governing predicate's active lanes", + Word: 0x25004000, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PAND (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PANDS", Summary: "And predicates, setting the condition flags", + Word: 0x25404000, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PANDS (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PBIC", Summary: "And-complement predicates over a governing predicate's active lanes", + Word: 0x25004010, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBIC (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PBICS", Summary: "And-complement predicates, setting the condition flags", + Word: 0x25404010, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBICS (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PEOR", Summary: "Exclusive-or predicates over a governing predicate's active lanes", + Word: 0x25004200, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PEOR (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PEORS", Summary: "Exclusive-or predicates, setting the condition flags", + Word: 0x25404200, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PEORS (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PNAND", Summary: "And-complement two predicates into one", + Word: 0x25804210, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PNAND (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PNANDS", Summary: "And-complement two predicates, setting the condition flags", + Word: 0x25c04210, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PNANDS (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PNOR", Summary: "Or-complement two predicates into one", + Word: 0x25804200, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PNOR (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PNORS", Summary: "Or-complement two predicates, setting the condition flags", + Word: 0x25c04200, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PNORS (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PORN", Summary: "Or-complement two predicates into one, complement reversed", + Word: 0x25804010, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PORN (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PORNS", Summary: "Or-complement two predicates, complement reversed, setting the flags", + Word: 0x25c04010, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PORNS (predicates); inst_gen.go + arm64sveenc.s"}, + {Name: "PORR", Summary: "Or two predicates into one", + Word: 0x25804000, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: POR (predicates) as PORR; inst_gen.go + arm64sveenc.s"}, + {Name: "PORRS", Summary: "Or two predicates, setting the condition flags", + Word: 0x25c04000, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PORS (predicates) as PORR; inst_gen.go + arm64sveenc.s"}, + {Name: "PSEL", Summary: "Select predicate elements by a governing predicate", + Word: 0x25004210, Form: ExtFormPredicateSelect, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PSEL (predicates); inst_gen.go + arm64sveenc.s"}, + + // Break operations, SVE2.1 generalised breaks. + {Name: "PBRKA", Summary: "Break after the first true of the governing predicate", + Word: 0x25104000, Form: ExtFormPredicateBreak, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBRKA (break after first true); inst_gen.go + arm64sveenc.s"}, + {Name: "PBRKAS", Summary: "Break after the first false, setting the condition flags", + Word: 0x25504000, Form: ExtFormPredicateBreakZero, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBRKAS (break after first false); inst_gen.go + arm64sveenc.s"}, + {Name: "PBRKB", Summary: "Break before the first true of the governing predicate", + Word: 0x25904000, Form: ExtFormPredicateBreak, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBRKB (break before first true); inst_gen.go + arm64sveenc.s"}, + {Name: "PBRKBS", Summary: "Break before the first false, setting the condition flags", + Word: 0x25d04000, Form: ExtFormPredicateBreakZero, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBRKBS (break before first false); inst_gen.go + arm64sveenc.s"}, + {Name: "PBRKN", Summary: "Break after the first true of the second source", + Word: 0x25184000, Form: ExtFormPredicateLogicalDest, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBRKN (break after first true); inst_gen.go + arm64sveenc.s"}, + {Name: "PBRKNS", Summary: "Break after the first true of the second source, setting the flags", + Word: 0x25584000, Form: ExtFormPredicateLogicalDest, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBRKNS; inst_gen.go + arm64sveenc.s"}, + {Name: "PBRKPA", Summary: "Break after the first true, preserving the first source's leading lanes", + Word: 0x2500c000, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBRKPA (break propagating after); inst_gen.go + arm64sveenc.s"}, + {Name: "PBRKPAS", Summary: "Break propagating after the first false, setting the flags", + Word: 0x2540c000, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBRKPAS; inst_gen.go + arm64sveenc.s"}, + {Name: "PBRKPB", Summary: "Break propagating before the first true", + Word: 0x2500c010, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBRKPB (break propagating before); inst_gen.go + arm64sveenc.s"}, + {Name: "PBRKPBS", Summary: "Break propagating before the first false, setting the flags", + Word: 0x2540c010, Form: ExtFormPredicateLogical, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: PBRKPBS; inst_gen.go + arm64sveenc.s"}, + + // Predicate permutations, SVE2.1. + {Name: "PTRN1", Summary: "Transpose odd predicate elements", + Word: 0x05205000, Form: ExtFormPredicatePermute, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: TRN1 (predicates) as PTRN1; inst_gen.go + arm64sveenc.s"}, + {Name: "PTRN2", Summary: "Transpose even predicate elements", + Word: 0x05205400, Form: ExtFormPredicatePermute, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: TRN2 (predicates) as PTRN2; inst_gen.go + arm64sveenc.s"}, + {Name: "PUZP1", Summary: "Unzip odd predicate elements", + Word: 0x05204800, Form: ExtFormPredicatePermute, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: UZP1 (predicates) as PUZP1; inst_gen.go + arm64sveenc.s"}, + {Name: "PUZP2", Summary: "Unzip even predicate elements", + Word: 0x05204c00, Form: ExtFormPredicatePermute, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: UZP2 (predicates) as PUZP2; inst_gen.go + arm64sveenc.s"}, + {Name: "PZIP1", Summary: "Zip odd predicate elements", + Word: 0x05204000, Form: ExtFormPredicatePermute, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: ZIP1 (predicates) as PZIP1; inst_gen.go + arm64sveenc.s"}, + {Name: "PZIP2", Summary: "Zip even predicate elements", + Word: 0x05204400, Form: ExtFormPredicatePermute, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: ZIP2 (predicates) as PZIP2; inst_gen.go + arm64sveenc.s"}, + + // Predicate initialise, test and permute singles. + {Name: "PPFALSE", Summary: "Initialise a predicate to false", + Word: 0x2518e400, Form: ExtFormPredicateOne, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: PFALSE as PPFALSE; inst_gen.go + arm64sveenc.s"}, + {Name: "PPFIRST", Summary: "Set the first active element of a predicate", + Word: 0x2558c000, Form: ExtFormPredicateFirst, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: PFIRST as PPFIRST; inst_gen.go + arm64sveenc.s"}, + {Name: "PPNEXT", Summary: "Set the next active element of a predicate", + Word: 0x2519c400, Form: ExtFormPredicateNext, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: PNEXT as PPNEXT; inst_gen.go + arm64sveenc.s"}, + {Name: "PPTEST", Summary: "Test a predicate against a governing predicate, setting the flags", + Word: 0x2550c000, Form: ExtFormPredicateTest, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: PTEST as PPTEST; inst_gen.go + arm64sveenc.s"}, + {Name: "PPTRUE", Summary: "Initialise a predicate-as-counter register from a pattern", + Word: 0x25207810, Form: ExtFormPredicateCounter, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: PTRUE (counter form) as PPTRUE; inst_gen.go + arm64sveenc.s"}, + {Name: "PPUNPKHI", Summary: "Unpack the even elements of a half-word predicate", + Word: 0x05314000, Form: ExtFormPredicateUnpack, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: PUNPKHI as PPUNPKHI; inst_gen.go + arm64sveenc.s"}, + {Name: "PPUNPKLO", Summary: "Unpack the odd elements of a half-word predicate", + Word: 0x05304000, Form: ExtFormPredicateUnpack, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: PUNPKLO as PPUNPKLO; inst_gen.go + arm64sveenc.s"}, + + // First-fault register and predicate reverse. + {Name: "PRDFFR", Summary: "Read the first-fault register to a predicate, zeroing", + Word: 0x2518f000, Form: ExtFormPredicateFFRRead, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: RDFFR (predicated) as PRDFFR; inst_gen.go + arm64sveenc.s"}, + {Name: "PRDFFR", Summary: "Read the first-fault register to a predicate whole", + Word: 0x2519f000, Form: ExtFormPredicateOne, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: RDFFR (unpredicated) as PRDFFR; inst_gen.go + arm64sveenc.s"}, + {Name: "PRDFFRS", Summary: "Read the first-fault register to a predicate, zeroing, setting the flags", + Word: 0x2558f000, Form: ExtFormPredicateFFRRead, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: RDFFRS as PRDFFRS; inst_gen.go + arm64sveenc.s"}, + {Name: "PWRFFR", Summary: "Write the first-fault register from a predicate", + Word: 0x25289000, Form: ExtFormPredicateWrite, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: WRFFR as PWRFFR; inst_gen.go + arm64sveenc.s"}, + {Name: "PREV", Summary: "Reverse the elements of a predicate", + Word: 0x05344000, Form: ExtFormPredicateMove, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: REV (predicate) as PREV; inst_gen.go + arm64sveenc.s"}, + {Name: "SETFFR", Summary: "Set the first-fault register to all true", + Word: 0x252c9000, Form: ExtFormNone, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: SETFFR; inst_gen.go + arm64sveenc.s"}, + + // While compares: build a predicate from a scalar loop condition. + {Name: "PWHILEGE", Summary: "Build a predicate while the signed greater-or-equal comparison holds", + Word: 0x25201000, Form: ExtFormWhile, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: WHILEGE as PWHILEGE; inst_gen.go + arm64sveenc.s"}, + {Name: "PWHILEGT", Summary: "Build a predicate while the signed greater comparison holds", + Word: 0x25201010, Form: ExtFormWhile, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: WHILEGT as PWHILEGT; inst_gen.go + arm64sveenc.s"}, + {Name: "PWHILEHI", Summary: "Build a predicate while the unsigned higher comparison holds", + Word: 0x25201810, Form: ExtFormWhile, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: WHILEHI as PWHILEHI; inst_gen.go + arm64sveenc.s"}, + {Name: "PWHILEHS", Summary: "Build a predicate while the unsigned higher-or-same comparison holds", + Word: 0x25201800, Form: ExtFormWhile, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: WHILEHS as PWHILEHS; inst_gen.go + arm64sveenc.s"}, + {Name: "PWHILELE", Summary: "Build a predicate while the signed less-or-equal comparison holds", + Word: 0x25201410, Form: ExtFormWhile, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: WHILELE as PWHILELE; inst_gen.go + arm64sveenc.s"}, + {Name: "PWHILELO", Summary: "Build a predicate while the unsigned lower comparison holds", + Word: 0x25201c00, Form: ExtFormWhile, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: WHILELO as PWHILELO; inst_gen.go + arm64sveenc.s"}, + {Name: "PWHILELS", Summary: "Build a predicate while the unsigned lower-or-same comparison holds", + Word: 0x25201c10, Form: ExtFormWhile, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: WHILELS as PWHILELS; inst_gen.go + arm64sveenc.s"}, + {Name: "PWHILELT", Summary: "Build a predicate while the signed less comparison holds", + Word: 0x25201400, Form: ExtFormWhile, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: WHILELT as PWHILELT; inst_gen.go + arm64sveenc.s"}, + {Name: "PWHILERW", Summary: "Build a predicate over a read-write region", + Word: 0x25203010, Form: ExtFormWhile, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: PWHILERW (SVE2.1); inst_gen.go + arm64sveenc.s"}, + {Name: "PWHILEWR", Summary: "Build a predicate over a write region", + Word: 0x25203000, Form: ExtFormWhile, Size: extSizeBHSD, Feature: ExtFeatureSVE, + Ref: "ARM DDI 0487J: PWHILEWR (SVE2.1); inst_gen.go + arm64sveenc.s"}, } // Extensions returns the extended-instruction layer registered for a, outside diff --git a/arch/arm64_ext_test.go b/arch/arm64_ext_test.go index 6ae1a73..207f26d 100644 --- a/arch/arm64_ext_test.go +++ b/arch/arm64_ext_test.go @@ -279,27 +279,36 @@ func extInstructionQuiet(mnem string, form ExtForm) (ExtInstr, bool) { } // TestArm64ExtTableIntegrity checks the metadata contract: every entry names -// its manual reference, summary and feature, and the element-size field sits -// at bits 23..22 where the manual puts it for every class in the family. +// its manual reference, summary and feature, the element-size field sits +// at bits 23..22 for the classes that carry one, and the destination field +// stays zero in the fixed word: five bits where a Z register is the +// destination, four where a predicate is. func TestArm64ExtTableIntegrity(t *testing.T) { for _, in := range Extensions(ARM64) { if in.Name == "" || in.Summary == "" || in.Ref == "" { t.Errorf("%+v: name, summary and reference are mandatory", in) } - if in.Feature != ExtFeatureSVE && in.Feature != ExtFeatureSVE2 { - t.Errorf("%s: feature %q is neither sve nor sve2", in.Name, in.Feature) + if in.Feature != ExtFeatureSVE && in.Feature != ExtFeatureSVE2 && in.Feature != ExtFeatureSVE2p1 { + t.Errorf("%s: feature %q is neither sve, sve2 nor sve2p1", in.Name, in.Feature) } - if in.Form.Arity() < 2 || in.Form.Arity() > 3 { + if in.Form.Arity() < 0 || in.Form.Arity() > 4 { t.Errorf("%s: form %d carries an unusable arity %d", in.Name, in.Form, in.Form.Arity()) } - if in.Size.Off != 22 || in.Size.Width != 2 { + if in.Size.Width != 0 && (in.Size.Off != 22 || in.Size.Width != 2) { t.Errorf("%s: the size field sits at bits %d..%d, the classes here put it at 23..22", in.Name, in.Size.Off, in.Size.Off+in.Size.Width-1) } // The destination register field and the element-size field are - // operands everywhere in this family, so Word carries both zero; the - // class opcodes live around them and stay where they are. - if in.Word&0x1f != 0 || in.Word&(0x3<<22) != 0 { + // operands wherever they exist, so Word carries both zero; the + // class opcodes live around them and stay where they are. Where + // the class carries no size field, bits 23..22 are class bits and + // stay. + destMask := uint32(0x1f) + if in.Form != ExtFormVectors && in.Form != ExtFormPredicated && + in.Form != ExtFormImmediate && in.Form != ExtFormSignedImmediate { + destMask = 0xf // the predicate destination is a four-bit field + } + if in.Word&destMask != 0 || (in.Size.Width != 0 && in.Word&(0x3<<22) != 0) { t.Errorf("%s: word %08x carries destination or size bits, want them zero", in.Name, in.Word) } } @@ -401,3 +410,389 @@ func TestExtensionsArchBinding(t *testing.T) { t.Error("Extensions(AMD64) is empty") } } + +// TestArm64ExtPredicateGolden pins the SVE and SVE2.1 predicate family +// against the toolchain-generated corpus: every row is one arm64sveenc.s +// line, and the want word is that line's own expected encoding, byte +// reversed out of the file's big-endian comment into the little-endian +// instruction word. The corpus and the toolchain's encoding table +// (inst_gen.go) are two independent generations from Arm's official ISA +// description; both agree with the field layouts encoded here, and the +// classes have no single-byte oracle beyond them (go tool asm carries no +// SVE encodings). +func TestArm64ExtPredicateGolden(t *testing.T) { + for _, tt := range []struct { + name string + mnem string + form ExtForm + ops []ExtOperand + want uint32 + }{ + {"PAND P4.B, P2.B, P1.Z, P14.B", "PAND", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2504444e}, + {"PANDS P4.B, P2.B, P1.Z, P14.B", "PANDS", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2544444e}, + {"PBIC P4.B, P2.B, P1.Z, P14.B", "PBIC", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2504445e}, + {"PBICS P4.B, P2.B, P1.Z, P14.B", "PBICS", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2544445e}, + {"PBRKA P5.B, P9.Z, P2.B", "PBRKA", + ExtFormPredicateBreak, + []ExtOperand{ExtPredicateSized(5, ExtArrB), ExtPredicate(9, ExtQualZeroing), ExtPredicateSized(2, ExtArrB)}, + 0x251064a2}, + {"PBRKAS P5.B, P9.Z, P4.B", "PBRKAS", + ExtFormPredicateBreakZero, + []ExtOperand{ExtPredicateSized(5, ExtArrB), ExtPredicate(9, ExtQualZeroing), ExtPredicateSized(4, ExtArrB)}, + 0x255064a4}, + {"PBRKB P5.B, P9.Z, P2.B", "PBRKB", + ExtFormPredicateBreak, + []ExtOperand{ExtPredicateSized(5, ExtArrB), ExtPredicate(9, ExtQualZeroing), ExtPredicateSized(2, ExtArrB)}, + 0x259064a2}, + {"PBRKBS P5.B, P9.Z, P4.B", "PBRKBS", + ExtFormPredicateBreakZero, + []ExtOperand{ExtPredicateSized(5, ExtArrB), ExtPredicate(9, ExtQualZeroing), ExtPredicateSized(4, ExtArrB)}, + 0x25d064a4}, + {"PBRKN P4.B, P2.B, P1.Z, P4.B", "PBRKN", + ExtFormPredicateLogicalDest, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(4, ExtArrB)}, + 0x25184444}, + {"PBRKNS P4.B, P2.B, P1.Z, P4.B", "PBRKNS", + ExtFormPredicateLogicalDest, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(4, ExtArrB)}, + 0x25584444}, + {"PBRKPA P4.B, P2.B, P1.Z, P14.B", "PBRKPA", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2504c44e}, + {"PBRKPAS P4.B, P2.B, P1.Z, P14.B", "PBRKPAS", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2544c44e}, + {"PBRKPB P4.B, P2.B, P1.Z, P14.B", "PBRKPB", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2504c45e}, + {"PBRKPBS P4.B, P2.B, P1.Z, P14.B", "PBRKPBS", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2544c45e}, + {"PEOR P4.B, P2.B, P1.Z, P14.B", "PEOR", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2504464e}, + {"PEORS P4.B, P2.B, P1.Z, P14.B", "PEORS", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2544464e}, + {"PNAND P4.B, P2.B, P1.Z, P14.B", "PNAND", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2584465e}, + {"PNANDS P4.B, P2.B, P1.Z, P14.B", "PNANDS", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x25c4465e}, + {"PNOR P4.B, P2.B, P1.Z, P14.B", "PNOR", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2584464e}, + {"PNORS P4.B, P2.B, P1.Z, P14.B", "PNORS", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x25c4464e}, + {"PORN P4.B, P2.B, P1.Z, P14.B", "PORN", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2584445e}, + {"PORNS P4.B, P2.B, P1.Z, P14.B", "PORNS", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x25c4445e}, + {"PORR P4.B, P2.B, P1.Z, P14.B", "PORR", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x2584444e}, + {"PORRS P4.B, P2.B, P1.Z, P14.B", "PORRS", + ExtFormPredicateLogical, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtPredicateSized(14, ExtArrB)}, + 0x25c4444e}, + {"PPFALSE P13.B", "PPFALSE", + ExtFormPredicateOne, + []ExtOperand{ExtPredicateSized(13, ExtArrB)}, + 0x2518e40d}, + {"PPFIRST P5.B, P9, P5.B", "PPFIRST", + ExtFormPredicateFirst, + []ExtOperand{ExtPredicateSized(5, ExtArrB), ExtPredicate(9, ExtQualNone), ExtPredicateSized(5, ExtArrB)}, + 0x2558c125}, + {"PPNEXT P5.D, P4, P5.D", "PPNEXT", + ExtFormPredicateNext, + []ExtOperand{ExtPredicateSized(5, ExtArrD), ExtPredicate(4, ExtQualNone), ExtPredicateSized(5, ExtArrD)}, + 0x25d9c485}, + {"PPTEST P14.B, P0", "PPTEST", + ExtFormPredicateTest, + []ExtOperand{ExtPredicateSized(14, ExtArrB), ExtPredicate(0, ExtQualNone)}, + 0x2550c1c0}, + {"PPTRUE PN14.S", "PPTRUE", + ExtFormPredicateCounter, + []ExtOperand{ExtCounterPredicate(14, ExtArrS)}, + 0x25a07816}, + {"PPUNPKHI P14.B, P0.H", "PPUNPKHI", + ExtFormPredicateUnpack, + []ExtOperand{ExtPredicateSized(14, ExtArrB), ExtPredicateSized(0, ExtArrH)}, + 0x053141c0}, + {"PPUNPKLO P14.B, P0.H", "PPUNPKLO", + ExtFormPredicateUnpack, + []ExtOperand{ExtPredicateSized(14, ExtArrB), ExtPredicateSized(0, ExtArrH)}, + 0x053041c0}, + {"PRDFFR P14.Z, P0.B", "PRDFFR", + ExtFormPredicateFFRRead, + []ExtOperand{ExtPredicate(14, ExtQualZeroing), ExtPredicateSized(0, ExtArrB)}, + 0x2518f1c0}, + {"PRDFFR P13.B", "PRDFFR", + ExtFormPredicateOne, + []ExtOperand{ExtPredicateSized(13, ExtArrB)}, + 0x2519f00d}, + {"PRDFFRS P14.Z, P0.B", "PRDFFRS", + ExtFormPredicateFFRRead, + []ExtOperand{ExtPredicate(14, ExtQualZeroing), ExtPredicateSized(0, ExtArrB)}, + 0x2558f1c0}, + {"PREV P14.S, P13.S", "PREV", + ExtFormPredicateMove, + []ExtOperand{ExtPredicateSized(14, ExtArrS), ExtPredicateSized(13, ExtArrS)}, + 0x05b441cd}, + {"PSEL P4.B, P2.B, P1, P14.B", "PSEL", + ExtFormPredicateSelect, + []ExtOperand{ExtPredicateSized(4, ExtArrB), ExtPredicateSized(2, ExtArrB), ExtPredicate(1, ExtQualNone), ExtPredicateSized(14, ExtArrB)}, + 0x2504465e}, + {"PTRN1 P5.D, P4.D, P2.D", "PTRN1", + ExtFormPredicatePermute, + []ExtOperand{ExtPredicateSized(5, ExtArrD), ExtPredicateSized(4, ExtArrD), ExtPredicateSized(2, ExtArrD)}, + 0x05e55082}, + {"PTRN2 P5.D, P4.D, P2.D", "PTRN2", + ExtFormPredicatePermute, + []ExtOperand{ExtPredicateSized(5, ExtArrD), ExtPredicateSized(4, ExtArrD), ExtPredicateSized(2, ExtArrD)}, + 0x05e55482}, + {"PUZP1 P5.D, P4.D, P2.D", "PUZP1", + ExtFormPredicatePermute, + []ExtOperand{ExtPredicateSized(5, ExtArrD), ExtPredicateSized(4, ExtArrD), ExtPredicateSized(2, ExtArrD)}, + 0x05e54882}, + {"PUZP2 P5.D, P4.D, P2.D", "PUZP2", + ExtFormPredicatePermute, + []ExtOperand{ExtPredicateSized(5, ExtArrD), ExtPredicateSized(4, ExtArrD), ExtPredicateSized(2, ExtArrD)}, + 0x05e54c82}, + {"PWRFFR P13.B", "PWRFFR", + ExtFormPredicateWrite, + []ExtOperand{ExtPredicateSized(13, ExtArrB)}, + 0x252891a0}, + {"PZIP1 P5.D, P4.D, P2.D", "PZIP1", + ExtFormPredicatePermute, + []ExtOperand{ExtPredicateSized(5, ExtArrD), ExtPredicateSized(4, ExtArrD), ExtPredicateSized(2, ExtArrD)}, + 0x05e54082}, + {"PZIP2 P5.D, P4.D, P2.D", "PZIP2", + ExtFormPredicatePermute, + []ExtOperand{ExtPredicateSized(5, ExtArrD), ExtPredicateSized(4, ExtArrD), ExtPredicateSized(2, ExtArrD)}, + 0x05e54482}, + {"SETFFR ", "SETFFR", + ExtFormNone, + []ExtOperand{}, + 0x252c9000}, + {"PWHILEGE R2, R10, P10.H", "PWHILEGE", + ExtFormWhile, + []ExtOperand{ExtGeneral(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + 0x2562114a}, + {"PWHILEGT R2, R10, P10.H", "PWHILEGT", + ExtFormWhile, + []ExtOperand{ExtGeneral(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + 0x2562115a}, + {"PWHILEHI R2, R10, P10.H", "PWHILEHI", + ExtFormWhile, + []ExtOperand{ExtGeneral(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + 0x2562195a}, + {"PWHILEHS R2, R10, P10.H", "PWHILEHS", + ExtFormWhile, + []ExtOperand{ExtGeneral(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + 0x2562194a}, + {"PWHILELE R2, R10, P10.H", "PWHILELE", + ExtFormWhile, + []ExtOperand{ExtGeneral(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + 0x2562155a}, + {"PWHILELO R2, R10, P10.H", "PWHILELO", + ExtFormWhile, + []ExtOperand{ExtGeneral(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + 0x25621d4a}, + {"PWHILELS R2, R10, P10.H", "PWHILELS", + ExtFormWhile, + []ExtOperand{ExtGeneral(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + 0x25621d5a}, + {"PWHILELT R2, R10, P10.H", "PWHILELT", + ExtFormWhile, + []ExtOperand{ExtGeneral(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + 0x2562154a}, + {"PWHILERW R2, R10, P10.H", "PWHILERW", + ExtFormWhile, + []ExtOperand{ExtGeneral(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + 0x2562315a}, + {"PWHILEWR R2, R10, P10.H", "PWHILEWR", + ExtFormWhile, + []ExtOperand{ExtGeneral(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + 0x2562314a}, + } { + in := extInstruction(t, tt.mnem, tt.form) + got, err := in.Encode(tt.ops) + if err != nil { + t.Errorf("%s: encode: %v", tt.name, err) + continue + } + if want := hex.EncodeToString(extWordLE(tt.want)); hex.EncodeToString(got) != want { + t.Errorf("%s:\n got %x\n want %s", tt.name, got, want) + } + } +} + +// TestArm64ExtPredicateSources pins the cross-check contract of the +// predicate family: every class the layer defines carries at least one +// golden vector derived from the toolchain-generated corpus, so no class +// rests on transcription alone. +func TestArm64ExtPredicateSources(t *testing.T) { + for _, tt := range []struct { + mnem string + form ExtForm + }{ + {"PAND", ExtFormPredicateLogical}, {"PANDS", ExtFormPredicateLogical}, + {"PBIC", ExtFormPredicateLogical}, {"PBICS", ExtFormPredicateLogical}, + {"PEOR", ExtFormPredicateLogical}, {"PEORS", ExtFormPredicateLogical}, + {"PNAND", ExtFormPredicateLogical}, {"PNANDS", ExtFormPredicateLogical}, + {"PNOR", ExtFormPredicateLogical}, {"PNORS", ExtFormPredicateLogical}, + {"PORN", ExtFormPredicateLogical}, {"PORNS", ExtFormPredicateLogical}, + {"PORR", ExtFormPredicateLogical}, {"PORRS", ExtFormPredicateLogical}, + {"PSEL", ExtFormPredicateSelect}, + {"PBRKA", ExtFormPredicateBreak}, {"PBRKAS", ExtFormPredicateBreakZero}, + {"PBRKB", ExtFormPredicateBreak}, {"PBRKBS", ExtFormPredicateBreakZero}, + {"PBRKN", ExtFormPredicateLogicalDest}, {"PBRKNS", ExtFormPredicateLogicalDest}, + {"PBRKPA", ExtFormPredicateLogical}, {"PBRKPAS", ExtFormPredicateLogical}, + {"PBRKPB", ExtFormPredicateLogical}, {"PBRKPBS", ExtFormPredicateLogical}, + {"PTRN1", ExtFormPredicatePermute}, {"PTRN2", ExtFormPredicatePermute}, + {"PUZP1", ExtFormPredicatePermute}, {"PUZP2", ExtFormPredicatePermute}, + {"PZIP1", ExtFormPredicatePermute}, {"PZIP2", ExtFormPredicatePermute}, + {"PPFALSE", ExtFormPredicateOne}, + {"PPFIRST", ExtFormPredicateFirst}, + {"PPNEXT", ExtFormPredicateNext}, + {"PPTEST", ExtFormPredicateTest}, + {"PPTRUE", ExtFormPredicateCounter}, + {"PPUNPKHI", ExtFormPredicateUnpack}, {"PPUNPKLO", ExtFormPredicateUnpack}, + {"PRDFFR", ExtFormPredicateFFRRead}, {"PRDFFR", ExtFormPredicateOne}, + {"PRDFFRS", ExtFormPredicateFFRRead}, + {"PWRFFR", ExtFormPredicateWrite}, + {"PREV", ExtFormPredicateMove}, + {"SETFFR", ExtFormNone}, + {"PWHILEGE", ExtFormWhile}, {"PWHILEGT", ExtFormWhile}, + {"PWHILEHI", ExtFormWhile}, {"PWHILEHS", ExtFormWhile}, + {"PWHILELE", ExtFormWhile}, {"PWHILELO", ExtFormWhile}, + {"PWHILELS", ExtFormWhile}, {"PWHILELT", ExtFormWhile}, + {"PWHILERW", ExtFormWhile}, {"PWHILEWR", ExtFormWhile}, + } { + if _, ok := extInstructionQuiet(tt.mnem, tt.form); !ok { + t.Errorf("the table lacks %s with the %s form", tt.mnem, tt.form) + } + } +} + +// TestArm64ExtPredicateRejects pins the diagnostics the predicate forms +// give the operands their classes cannot carry: the register ranges the +// field widths imply, the arrangements the classes fix, and the read-back +// constraint of the destination-shared shapes. +func TestArm64ExtPredicateRejects(t *testing.T) { + pb := func(r int) ExtOperand { return ExtPredicateSized(r, ExtArrB) } + for _, tt := range []struct { + name string + mnem string + form ExtForm + ops []ExtOperand + quote string + }{ + {"Pm beyond the three-bit field", "PAND", ExtFormPredicateLogical, + []ExtOperand{pb(8), pb(2), ExtPredicate(1, ExtQualZeroing), pb(14)}, + "outside P0-P7"}, + {"governing predicate beyond the narrow field", "PAND", ExtFormPredicateLogical, + []ExtOperand{pb(4), pb(2), ExtPredicate(8, ExtQualZeroing), pb(14)}, + "outside P0-P7"}, + {"wrong arrangement on the second source", "PAND", ExtFormPredicateLogical, + []ExtOperand{pb(4), ExtPredicateSized(2, ExtArrS), ExtPredicate(1, ExtQualZeroing), pb(14)}, + "want .B"}, + {"bare governing predicate", "PAND", ExtFormPredicateLogical, + []ExtOperand{pb(4), pb(2), ExtPredicate(1, ExtQualNone), pb(14)}, + "zeroing qualifier"}, + {"quadword arrangement", "PSEL", ExtFormPredicateSelect, + []ExtOperand{ExtPredicateSized(4, ExtArrQ), ExtPredicateSized(2, ExtArrQ), ExtPredicate(1, ExtQualNone), ExtPredicateSized(14, ExtArrQ)}, + "want .B"}, + {"qualifier where PSEL wants none", "PSEL", ExtFormPredicateSelect, + []ExtOperand{pb(4), pb(2), ExtPredicate(1, ExtQualZeroing), pb(14)}, + "takes no qualifier"}, + {"read-back operands disagree", "PBRKN", ExtFormPredicateLogicalDest, + []ExtOperand{pb(4), pb(2), ExtPredicate(1, ExtQualZeroing), pb(9)}, + "same register Pdm"}, + {"merging qualifier on the zeroing break", "PBRKAS", ExtFormPredicateBreakZero, + []ExtOperand{pb(5), ExtPredicate(9, ExtQualMerging), pb(4)}, + "zeroing qualifier"}, + {"governing predicate beyond the wide field", "PBRKA", ExtFormPredicateBreak, + []ExtOperand{pb(5), ExtPredicate(16, ExtQualZeroing), pb(2)}, + "outside P0-P15"}, + {"permutations disagree on arrangement", "PTRN1", ExtFormPredicatePermute, + []ExtOperand{ExtPredicateSized(5, ExtArrD), ExtPredicateSized(4, ExtArrD), ExtPredicateSized(2, ExtArrS)}, + "want .D"}, + {"bare permutation operand", "PUZP1", ExtFormPredicatePermute, + []ExtOperand{ExtPredicateSized(5, ExtArrD), {Kind: ExtPReg, Reg: 4}, ExtPredicateSized(2, ExtArrD)}, + "no arrangement suffix"}, + {"read-back operands disagree on arrangement", "PPNEXT", ExtFormPredicateNext, + []ExtOperand{ExtPredicateSized(5, ExtArrD), ExtPredicate(4, ExtQualNone), ExtPredicateSized(5, ExtArrS)}, + "same register Pdn"}, + {"qualifier on the bare source", "PPFIRST", ExtFormPredicateFirst, + []ExtOperand{pb(5), ExtPredicate(9, ExtQualZeroing), pb(5)}, + "takes no qualifier"}, + {"PFIRST outside .B", "PPFIRST", ExtFormPredicateFirst, + []ExtOperand{ExtPredicateSized(5, ExtArrS), ExtPredicate(9, ExtQualNone), ExtPredicateSized(5, ExtArrS)}, + "want .B"}, + {"merging qualifier on the FFR read", "PRDFFR", ExtFormPredicateFFRRead, + []ExtOperand{ExtPredicate(14, ExtQualMerging), pb(0)}, + "zeroing qualifier"}, + {"wrong unpack destination arrangement", "PPUNPKHI", ExtFormPredicateUnpack, + []ExtOperand{pb(14), ExtPredicateSized(0, ExtArrB)}, + "want .H"}, + {"qualifier on the tested predicate", "PPTEST", ExtFormPredicateTest, + []ExtOperand{pb(14), ExtPredicate(0, ExtQualZeroing)}, + "takes no qualifier"}, + {"general register beyond R30", "PWHILELT", ExtFormWhile, + []ExtOperand{ExtGeneral(31), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + "outside R0-R30"}, + {"predicate where the register belongs", "PWHILELT", ExtFormWhile, + []ExtOperand{pb(2), ExtGeneral(10), ExtPredicateSized(10, ExtArrH)}, + "wants a general register"}, + {"counter predicate spelled plain", "PPTRUE", ExtFormPredicateCounter, + []ExtOperand{ExtPredicateSized(6, ExtArrS)}, + "predicate-as-counter register"}, + {"counter below the counter range", "PPTRUE", ExtFormPredicateCounter, + []ExtOperand{ExtCounterPredicate(7, ExtArrS)}, + "outside PN8-PN15"}, + {"operand where none belongs", "SETFFR", ExtFormNone, + []ExtOperand{pb(13)}, + "takes 0 operands"}, + } { + in := extInstruction(t, tt.mnem, tt.form) + _, err := in.Encode(tt.ops) + if err == nil { + t.Errorf("%s: encode succeeded, want an error", tt.name) + continue + } + if !strings.Contains(err.Error(), tt.quote) { + t.Errorf("%s: error %q lacks %q", tt.name, err, tt.quote) + } + } +} diff --git a/asm/arm64_ext.go b/asm/arm64_ext.go index b300a10..5eed75e 100644 --- a/asm/arm64_ext.go +++ b/asm/arm64_ext.go @@ -11,7 +11,11 @@ // // The spellings are the layer's own Plan 9 forms, the ones its metadata // documents: Zn, Zm, Zd for the unpredicated three-vector class, Zm, Pg/M, -// Zdn for the predicated class, imm{, LSL #8}, Zdn for the immediate classes. +// Zdn for the predicated class, imm{, LSL #8}, Zdn for the immediate +// classes, and for the predicate family Pm.B, Pn.B, Pg/Z (or Pg.Z), Pd.B +// for the logical operations, Pn.B, Pg.Z, Pd.B for the breaks, Pm.T, Pn.T, +// Pd.T for the permutations, Rm, Rn, Pd.T for the while compares, PN8-PN15 +// for the counter destinations, and the bare SETFFR. package asm @@ -27,14 +31,16 @@ import ( // arm64ExtStatement converts one instruction's operands into the extended // layer's operand form. pinned reports that the statement belongs to the // layer: the mnemonic is registered in the registry and the operand list -// carries at least one scalable vector or predicate register. A pinned -// statement can only encode through the layer, so every operand is read -// here and its diagnostic replaces whatever the scalar paths would have -// said about operands they cannot read; err is non-nil for a pinned -// statement whose operands the layer refuses, and extops is complete only -// when err is nil. Unpinned means the statement is nobody's: the caller -// falls through to the ordinary arm64 encoders, which keep their exact -// behaviour for every scalar, NEON and FP operand list. +// carries at least one scalable vector, predicate or predicate-as-counter +// register, or no operands at all (the zero-operand forms such as SETFFR, +// which no scalar path could mean instead). A pinned statement can only +// encode through the layer, so every operand is read here and its +// diagnostic replaces whatever the scalar paths would have said about +// operands they cannot read; err is non-nil for a pinned statement whose +// operands the layer refuses, and extops is complete only when err is nil. +// Unpinned means the statement is nobody's: the caller falls through to the +// ordinary arm64 encoders, which keep their exact behaviour for every +// scalar, NEON and FP operand list. func arm64ExtStatement(mnem string, ops []*ast.Operand) (extops []arch.ExtOperand, pinned bool, err error) { if _, ok := LookupExtension(arch.ARM64, mnem); !ok { return nil, false, nil @@ -77,18 +83,31 @@ func arm64ExtStatement(mnem string, ops []*ast.Operand) (extops []arch.ExtOperan out = append(out, ext) continue } - return nil, true, fmt.Errorf("%s: operand %d (%s) is not an extended-layer operand: want a scalable vector register, a predicate register or an immediate", mnem, i+1, op.Raw) + if ext, ok := arm64ExtCounter(text); ok { + out = append(out, ext) + continue + } + if ext, ok := arm64ExtGeneral(text); ok { + out = append(out, ext) + continue + } + return nil, true, fmt.Errorf("%s: operand %d (%s) is not an extended-layer operand: want a scalable vector, predicate, general or counter register, or an immediate", mnem, i+1, op.Raw) } return out, true, nil } -// arm64ExtPinned reports whether any operand is a scalable vector or -// predicate register, the shapes only the extension layer reads. The test +// arm64ExtPinned reports whether the statement belongs to the layer: any +// operand is a scalable vector, predicate or predicate-as-counter register, +// the shapes only the extension layer reads, or the statement carries no +// operands at all and the mnemonic's zero-operand forms claim it. The test // is deliberately loose about the suffixes: P0/B is not a spelling the // layer takes, but the P of it makes the statement the layer's, and the // conversion then diagnoses the operand precisely instead of leaving it to // a scalar path that would report an unrelated register error. func arm64ExtPinned(ops []*ast.Operand) bool { + if len(ops) == 0 { + return true + } for _, op := range ops { if op.Kind == ast.OpImmediate { continue @@ -104,16 +123,20 @@ func arm64ExtPinned(ops []*ast.Operand) bool { return false } -// arm64ExtPredicateShape reports whether text spells a predicate register at -// all: P, digits, an optional arrangement suffix and an optional qualifier -// after a slash, whatever the qualifier says. The strict parse in -// arm64ExtPredicate judges the suffix; this shape only decides who the -// operand belongs to. +// arm64ExtPredicateShape reports whether text spells a predicate or +// predicate-as-counter register at all: PN or P, digits, an optional +// arrangement suffix and an optional qualifier after a slash, whatever the +// qualifier says. The strict parses in arm64ExtPredicate and +// arm64ExtCounter judge the suffix; this shape only decides who the operand +// belongs to. func arm64ExtPredicateShape(text string) bool { if text == "" || text[0] != 'P' { return false } text = text[1:] + if rest, found := strings.CutPrefix(text, "N"); found { + text = rest + } if i := strings.IndexByte(text, '/'); i >= 0 { text = text[:i] } @@ -160,7 +183,8 @@ func arm64ExtVector(text string) (arch.ExtOperand, bool) { } // arm64ExtPredicate parses a predicate register operand: P0..P15 with an -// optional element-size suffix and an optional qualifier, P0/M, P0.Z, P0.B/M. +// optional element-size suffix (P0.B) and an optional qualifier in either +// spelling the corpus and the wired forms use, P0/M and P0.Z. func arm64ExtPredicate(text string) (arch.ExtOperand, bool) { qual := arch.ExtQualNone if base, suffix, found := strings.Cut(text, "/"); found { @@ -173,6 +197,15 @@ func arm64ExtPredicate(text string) (arch.ExtOperand, bool) { return arch.ExtOperand{}, false } text = base + } else if base, suffix, found := strings.Cut(text, "."); found && + (suffix == "Z" || suffix == "M") { + // The dot qualifier stands in place of an arrangement, the spelling + // the toolchain's corpus writes (P1.Z, P14.M). + qual = arch.ExtQualMerging + if suffix == "Z" { + qual = arch.ExtQualZeroing + } + text = base } reg, arr, ok := arm64ExtReg(text, 'P') if !ok { @@ -181,6 +214,63 @@ func arm64ExtPredicate(text string) (arch.ExtOperand, bool) { return arch.ExtOperand{Kind: arch.ExtPReg, Reg: reg, Arr: arr, Qual: qual}, true } +// arm64ExtCounter parses a predicate-as-counter register operand: PN8..PN15 +// with an optional element-size suffix, PN14.S. The register range is the +// counter range the layer's convention carries; the encoding validates it. +func arm64ExtCounter(text string) (arch.ExtOperand, bool) { + rest, ok := strings.CutPrefix(text, "PN") + if !ok { + return arch.ExtOperand{}, false + } + reg, arr, ok := arm64ExtRegDigits(rest) + if !ok { + return arch.ExtOperand{}, false + } + return arch.ExtOperand{Kind: arch.ExtPNReg, Reg: reg, Arr: arr}, true +} + +// arm64ExtGeneral parses a general register operand: R0..R30, the plain +// spelling the while-compare forms take. The register range is left to the +// encoding, whose diagnostics name it. +func arm64ExtGeneral(text string) (arch.ExtOperand, bool) { + rest, ok := strings.CutPrefix(text, "R") + if !ok { + return arch.ExtOperand{}, false + } + reg, arr, ok := arm64ExtRegDigits(rest) + if !ok || arr != arch.ExtArrNone { + return arch.ExtOperand{}, false + } + return arch.ExtOperand{Kind: arch.ExtGReg, Reg: reg}, true +} + +// arm64ExtRegDigits parses the digits and optional arrangement suffix of a +// register spelling once the letter prefix is gone. +func arm64ExtRegDigits(text string) (reg int, arr arch.ExtArrangement, ok bool) { + if base, suffix, found := strings.Cut(text, "."); found { + switch suffix { + case "B": + arr = arch.ExtArrB + case "H": + arr = arch.ExtArrH + case "S": + arr = arch.ExtArrS + case "D": + arr = arch.ExtArrD + case "Q": + arr = arch.ExtArrQ + default: + return 0, 0, false + } + text = base + } + n, err := strconv.Atoi(text) + if err != nil || n < 0 { + return 0, 0, false + } + return n, arr, true +} + // arm64ExtReg parses Pn or Zn with an optional arrangement suffix off a // normalised operand text. The register range is left to the encoding: the // layer's own diagnostics name the range a form carries. diff --git a/asm/arm64_ext_asm_test.go b/asm/arm64_ext_asm_test.go index 5228bc5..5872c60 100644 --- a/asm/arm64_ext_asm_test.go +++ b/asm/arm64_ext_asm_test.go @@ -119,7 +119,7 @@ func TestArm64AssembleExtensionRefusals(t *testing.T) { {"ADD Z1.S, P9/M, Z0.S", "outside P0-P7"}, {"ADD $300, Z0.S", "immediate 300"}, {"ADD $255<<8, Z0.S", "not an immediate the layer can read"}, - {"ADD Z1.S, P0/M, R0", "not an extended-layer operand"}, + {"ADD Z1.S, P0/M, R0", "wants a scalable vector register"}, {"ADD Z1.S, P0/B, Z0.S", "not an extended-layer operand"}, {"MUL $200, Z0.B", "outside the signed 8-bit range"}, {"ADD Z1.S, LSL #8, Z0.S", "LSL belongs straight after an immediate"}, @@ -132,6 +132,148 @@ func TestArm64AssembleExtensionRefusals(t *testing.T) { } } +// TestArm64AssembleExtensionPredicateGolden drives the predicate family +// through the full assembler: corpus spellings in, corpus words out. The +// want words are the same arm64sveenc.s lines the arch-level golden test +// pins; these prove the text-to-bytes path parses the dot and slash +// qualifiers, the general registers and the counter spelling the statements +// write. +func TestArm64AssembleExtensionPredicateGolden(t *testing.T) { + tests := []struct { + stmt string + want uint32 + }{ + // The logical operations, .B alone, the governing predicate with the + // zeroing qualifier in its dot spelling. + {"PAND P4.B, P2.B, P1.Z, P14.B", 0x2504444e}, + {"PANDS P4.B, P2.B, P1.Z, P14.B", 0x2544444e}, + {"PBIC P4.B, P2.B, P1.Z, P14.B", 0x2504445e}, + {"PEOR P4.B, P2.B, P1.Z, P14.B", 0x2504464e}, + {"PNAND P4.B, P2.B, P1.Z, P14.B", 0x2584465e}, + {"PORR P4.B, P2.B, P1.Z, P14.B", 0x2584444e}, + // The slash qualifier spells the same operand the dot spelling does. + {"PAND P4.B, P2.B, P1/Z, P14.B", 0x2504444e}, + // The select and the breaks. + {"PSEL P4.B, P2.B, P1, P14.B", 0x2504465e}, + {"PBRKA P5.B, P9.Z, P2.B", 0x251064a2}, + {"PBRKAS P5.B, P9.Z, P4.B", 0x255064a4}, + {"PBRKN P4.B, P2.B, P1.Z, P4.B", 0x25184444}, + {"PBRKPA P4.B, P2.B, P1.Z, P14.B", 0x2504c44e}, + // The permutations carry the arrangement into the size field. + {"PTRN1 P5.D, P4.D, P2.D", 0x05e55082}, + {"PUZP2 P5.D, P4.D, P2.D", 0x05e54c82}, + {"PZIP1 P5.H, P4.H, P2.H", 0x05654082}, + // The singles and the first-fault group. + {"PPFALSE P13.B", 0x2518e40d}, + {"PPFIRST P5.B, P9, P5.B", 0x2558c125}, + {"PPNEXT P5.D, P4, P5.D", 0x25d9c485}, + {"PPTEST P14.B, P0", 0x2550c1c0}, + {"PPUNPKHI P14.B, P0.H", 0x053141c0}, + {"PRDFFR P13.B", 0x2519f00d}, + {"PRDFFR P14.Z, P0.B", 0x2518f1c0}, + {"PRDFFRS P14.Z, P0.B", 0x2558f1c0}, + {"PWRFFR P13.B", 0x252891a0}, + {"PREV P14.S, P13.S", 0x05b441cd}, + {"SETFFR", 0x252c9000}, + // The while compares: general registers in, a sized predicate out. + {"PWHILEGE R2, R10, P10.H", 0x2562114a}, + {"PWHILELT R2, R10, P10.H", 0x2562154a}, + {"PWHILELS R2, R10, P10.B", 0x25221d5a}, + {"PWHILERW R2, R10, P10.H", 0x2562315a}, + } + for _, tt := range tests { + words := assembleArm64Words(t, arm64ExtProbeHead+"\t"+tt.stmt+"\n\tRET\n") + if len(words) != 2 { + t.Fatalf("%s: got %d words, want the statement and RET", tt.stmt, len(words)) + } + if words[0] != tt.want { + t.Errorf("%s:\n got %08x\n want %08x", tt.stmt, words[0], tt.want) + } + if words[1] != 0xd65f03c0 { + t.Errorf("%s: RET encoded %08x", tt.stmt, words[1]) + } + } +} + +// TestArm64AssembleExtensionPredicateRefusals pins the diagnostics the +// predicate statements get from the layer. +func TestArm64AssembleExtensionPredicateRefusals(t *testing.T) { + tests := []struct { + stmt string + want string + }{ + {"PAND P4.B, P2.B, P1.M, P14.B", "zeroing qualifier"}, + {"PAND P4.B, P2.B, P9.Z, P14.B", "outside P0-P7"}, + {"PAND P8.B, P2.B, P1.Z, P14.B", "outside P0-P7"}, + {"PAND P4.S, P2.B, P1.Z, P14.B", "want .B"}, + {"PBRKN P4.B, P2.B, P1.Z, P9.B", "same register Pdm"}, + {"PBRKAS P5.B, P9.M, P4.B", "zeroing qualifier"}, + {"PSEL P4.B, P2.B, P1.Z, P14.B", "takes no qualifier"}, + {"PTRN1 P5.D, P4.S, P2.D", "want .D"}, + {"PPFALSE P13.S", "want .B"}, + {"PPTRUE P6.S", "predicate-as-counter register"}, + {"PPTRUE PN6.S", "outside PN8-PN15"}, + {"PWHILELT R2, R31, P10.H", "outside R0-R30"}, + {"PWHILELT R2, R10, P10.Q", "no size encoding"}, + {"SETFFR P0.B", "takes 0 operands"}, + } + for _, tt := range tests { + got := assembleArm64SrcError(t, arm64ExtProbeHead+"\t"+tt.stmt+"\n\tRET\n") + if !strings.Contains(got, tt.want) { + t.Errorf("%s: error %q does not name %q", tt.stmt, got, tt.want) + } + } +} + +// TestArm64AssembleExtensionPredicateLeavesScalarsAlone pins the non- +// invasion promise across the new operand kinds: general-register +// statements whose mnemonics the layer also carries for predicates keep +// their scalar behaviour whenever no vector, predicate or counter operand +// appears. +func TestArm64AssembleExtensionPredicateLeavesScalarsAlone(t *testing.T) { + tests := []struct { + stmt string + want uint32 + }{ + {"ADD R0, R1, R2", 0x8b000022}, + {"SUB R0, R1, R2", 0xcb000022}, + } + for _, tt := range tests { + words := assembleArm64Words(t, arm64ExtProbeHead+"\t"+tt.stmt+"\n\tRET\n") + if len(words) != 2 { + t.Fatalf("%s: got %d words, want the statement and RET", tt.stmt, len(words)) + } + if words[0] != tt.want { + t.Errorf("%s:\n got %08x\n want %08x", tt.stmt, words[0], tt.want) + } + } +} + +// TestArm64AssembleExtensionPredicateLabelOffsets proves pass 1 and pass 2 +// agree on a function mixing the predicate family with the vector family: +// PWHILELT (4 bytes) and SETFFR (4 bytes) shift the label by exactly the +// words pass 2 lays down. +func TestArm64AssembleExtensionPredicateLabelOffsets(t *testing.T) { + src := arm64ExtProbeHead + ` + PWHILELT R2, R10, P10.H +loop: + SETFFR + B loop + PPFALSE P13.B + RET +` + words := assembleArm64Words(t, src) + want := []uint32{0x2562154a, 0x252c9000, 0x17ffffff, 0x2518e40d, 0xd65f03c0} + if len(words) != len(want) { + t.Fatalf("got %d words, want %d", len(words), len(want)) + } + for i := range want { + if words[i] != want[i] { + t.Errorf("word %d: got %08x, want %08x", i, words[i], want[i]) + } + } +} + // TestArm64AssembleExtensionLabelOffsets proves pass 1 and pass 2 agree on a // function that mixes the layer with ordinary statements: the label after an // SVE instruction lands on the 4 bytes the encoder laid down, and the branch diff --git a/asm/extension_test.go b/asm/extension_test.go index ee19de3..4596d92 100644 --- a/asm/extension_test.go +++ b/asm/extension_test.go @@ -205,12 +205,28 @@ func TestExtensionArchIsolation(t *testing.T) { // TestExtensionNamesARM64 checks the completion-facing name list: every // distinct mnemonic of the family, first-occurrence order, no duplicates. func TestExtensionNamesARM64(t *testing.T) { - want := []string{"ADD", "SUB", "SQADD", "UQADD", "SQSUB", "UQSUB", "MUL", "SMULH", "UMULH", "SUBR"} + want := []string{ + // The SVE integer add/subtract/multiply family. + "ADD", "SUB", "SQADD", "UQADD", "SQSUB", "UQSUB", "MUL", "SMULH", "UMULH", "SUBR", + // The SVE and SVE2.1 predicate family: the logical operations, the + // breaks, the permutations, the singles, the first-fault group and + // the while compares. + "PAND", "PANDS", "PBIC", "PBICS", "PEOR", "PEORS", + "PNAND", "PNANDS", "PNOR", "PNORS", "PORN", "PORNS", "PORR", "PORRS", + "PSEL", + "PBRKA", "PBRKAS", "PBRKB", "PBRKBS", "PBRKN", "PBRKNS", + "PBRKPA", "PBRKPAS", "PBRKPB", "PBRKPBS", + "PTRN1", "PTRN2", "PUZP1", "PUZP2", "PZIP1", "PZIP2", + "PPFALSE", "PPFIRST", "PPNEXT", "PPTEST", "PPTRUE", "PPUNPKHI", "PPUNPKLO", + "PRDFFR", "PRDFFRS", "PWRFFR", "PREV", "SETFFR", + "PWHILEGE", "PWHILEGT", "PWHILEHI", "PWHILEHS", + "PWHILELE", "PWHILELO", "PWHILELS", "PWHILELT", "PWHILERW", "PWHILEWR", + } got := ExtensionNames(arch.ARM64) if strings.Join(got, ",") != strings.Join(want, ",") { t.Errorf("ExtensionNames(ARM64) = %v, want %v", got, want) } - if n := len(arch.Extensions(arch.ARM64)); n != 23 { - t.Errorf("the family registers %d instructions, want 23", n) + if n := len(arch.Extensions(arch.ARM64)); n != 77 { + t.Errorf("the family registers %d instructions, want 77", n) } }