From eda8b16c5fb6fa48e3950a7960fe7fca4d6d8ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Wed, 7 Oct 2026 21:22:08 +0200 Subject: [PATCH] feat(arch): the shift-immediate tsz:imm3 scheme Assisted-by: GLM 5.3 Flash --- arch/arm64_ext.go | 270 ++++++++++++++++++++++++++++++++++++++ arch/arm64_ext_test.go | 178 +++++++++++++++++++++++++ asm/arm64_ext_asm_test.go | 81 ++++++++++++ asm/extension_test.go | 28 +++- 4 files changed, 555 insertions(+), 2 deletions(-) diff --git a/arch/arm64_ext.go b/arch/arm64_ext.go index 3f0638a..9d01d51 100644 --- a/arch/arm64_ext.go +++ b/arch/arm64_ext.go @@ -134,6 +134,12 @@ func (a ExtArrangement) Width() int { } } +// elemBits returns the arrangement's element size in bits, the scale the +// shift-immediate classes encode their amounts against. +func (a ExtArrangement) elemBits() int { + return a.Width() * 8 +} + // sizeBits maps the arrangement onto the two-bit size field the integer SVE // classes carry at bits 23..22: 00=B, 01=H, 10=S, 11=D. ok is false for the // arrangements no such class accepts (.Q and the bare spelling). @@ -407,6 +413,18 @@ var ( extFieldPnc = ExtField{0, 3} // predicate-as-counter destination PN8-PN15, field holds reg-8 extFieldPn4 = ExtField{5, 4} // predicate source P0-P15 at bits 8..5 (counter family) extFieldImm6 = ExtField{5, 6} // the signed imm6 of the vector-length arithmetic + // The tsz:imm3 shift-immediate fields: the amount rides a 5..7-bit + // integer C, split imm3 (the low three bits), a two-bit tsz chunk and + // a one- or two-bit tsz top. The three-vector classes split into + // bits 18..16, 20..19 and 22; the predicated class into bits 7..5, + // 9..8 and 23..22. The tsz chunks are the arrangement's own spelling + // and stand in for the size field these classes do not carry. + extFieldImm3T16 = ExtField{16, 3} // the low three bits of C, three-vector classes + extFieldTszM19 = ExtField{19, 2} // the middle tsz chunk of the three-vector classes + extFieldTszH22 = ExtField{22, 1} // the top tsz bit of the three-vector classes + extFieldImm3T5 = ExtField{5, 3} // the low three bits of C, predicated class + extFieldTszM8 = ExtField{8, 2} // the middle tsz chunk of the predicated class + extFieldTszH22W = ExtField{22, 2} // the top tsz chunk of the predicated class ) // ExtForm enumerates the operand shapes the extension layer defines, in Plan @@ -742,6 +760,31 @@ const ( // destination slot, and the predicate's arrangement feeds the size // field. ExtFormCountPnVec + // ExtFormShiftImmPred is the predicated shift by immediate, ZSQSHL + // $6, Z23.B, P1.M, Z23.B. Operands: imm, Zdn.T, Pg/M, Zdn.T, the + // destination spelled twice and encoded once. The amount rides the + // class's tsz:imm3 fields: C = esize + amount, split imm3 into bits + // 7..5, the low tsz pair into bits 9..8 and the top tsz pair into + // bits 23..22, where the tsz bits are the arrangement's own spelling + // and double as its size encoding; no separate size field exists. + // The amount runs 1..esize-1 and the merging qualifier is the only + // one the class spells. + ExtFormShiftImmPred + // ExtFormShiftImmNarrow is the three-vector narrowing shift by + // immediate, ZSHRNB $9, Z7.S, Z23.H: a right shift of the wide source + // whose result the narrow destination keeps, one element step down + // (.H+.B, .S+.H, .D+.S). Operands: imm, Zn.Tb, Zd.T. C = 2 x esize + // - amount over the destination's element size, split imm3 into bits + // 18..16, the low tsz pair into bits 20..19 and the top tsz bit into + // bit 22; the amount runs 1..esize. + ExtFormShiftImmNarrow + // ExtFormShiftImmWide is the three-vector widening shift by + // immediate, ZSSHLLB $5, Z22.S, Z10.D: a left shift of the narrow + // source into a destination one element step up (.B+.H, .H+.S, + // .S+.D). Operands: imm, Zn.Tb, Zd.T. C = esize + amount over the + // source's element size, split like the narrowing class; the amount + // runs 0..esize-1. + ExtFormShiftImmWide ) // Arity returns the operand count the form takes. @@ -780,6 +823,10 @@ func (f ExtForm) Arity() int { return 2 case ExtFormCountPnVec: return 2 + case ExtFormShiftImmPred: + return 4 + case ExtFormShiftImmNarrow, ExtFormShiftImmWide: + return 3 case ExtFormReduceReadBack: return 4 case ExtFormSveLoad, ExtFormSveStore, ExtFormNarrow, ExtFormQuadReduce, ExtFormShiftWide, @@ -848,6 +895,10 @@ func (f ExtForm) Kinds() []ExtOperandKind { return []ExtOperandKind{ExtPReg, ExtGReg} case ExtFormCountPnVec: return []ExtOperandKind{ExtPReg, ExtZReg} + case ExtFormShiftImmPred: + return []ExtOperandKind{ExtImm, ExtZReg, ExtPReg, ExtZReg} + case ExtFormShiftImmNarrow, ExtFormShiftImmWide: + return []ExtOperandKind{ExtImm, ExtZReg, ExtZReg} case ExtFormCountPnW: return []ExtOperandKind{ExtGReg, ExtPReg, ExtGReg} case ExtFormReduce: @@ -984,6 +1035,12 @@ func (f ExtForm) String() string { return "counter step, result dropped" case ExtFormCountPnVec: return "counter step on a vector" + case ExtFormShiftImmPred: + return "predicated shift by immediate" + case ExtFormShiftImmNarrow: + return "narrowing shift by immediate" + case ExtFormShiftImmWide: + return "widening shift by immediate" case ExtFormCountPnW: return "counter step to a register" case ExtFormReduce: @@ -1260,6 +1317,12 @@ func (in ExtInstr) Encode(ops []ExtOperand) ([]byte, error) { return in.encodeCountPn(ops) case ExtFormCountPnVec: return in.encodeCountPnVec(ops) + case ExtFormShiftImmPred: + return in.encodeShiftImmPred(ops) + case ExtFormShiftImmNarrow: + return in.encodeShiftImmNarrow(ops) + case ExtFormShiftImmWide: + return in.encodeShiftImmWide(ops) case ExtFormCountPnW: return in.encodeCountPnW(ops) case ExtFormReduce: @@ -2393,6 +2456,139 @@ func (in ExtInstr) encodeCountPnVec(ops []ExtOperand) ([]byte, error) { return extWordLE(word), nil } +// shiftImmAmount validates the immediate shift operand against the closed +// range lo..hi and refuses the LSL spelling, which belongs to the +// immediate-constant classes and not to the shift-immediate ones. +func (in ExtInstr) shiftImmAmount(op ExtOperand, position, lo, hi int) (int, error) { + if op.Kind != ExtImm { + return 0, fmt.Errorf("%s: operand %d wants an immediate, got %s", in.Name, position, op.Kind) + } + if op.HasShift { + return 0, fmt.Errorf("%s: the shift-immediate class takes no LSL shift", in.Name) + } + v := int(op.Imm) + if v < lo || v > hi { + return 0, fmt.Errorf("%s: shift amount %d is outside the range %d..%d in this class", + in.Name, v, lo, hi) + } + return v, nil +} + +// encodeShiftImmPred fills the predicated shift by immediate: imm, Zdn.T, +// Pg/M, Zdn.T, the destination spelled twice and encoded once. The amount +// rides the class's tsz:imm3 fields: C = esize + amount, split imm3 into +// bits 7..5, the low tsz pair into bits 9..8 and the top tsz pair into +// bits 23..22, where the tsz bits spell the arrangement and stand in for +// the size field the class does not carry. +func (in ExtInstr) encodeShiftImmPred(ops []ExtOperand) ([]byte, error) { + imm, zdn, pg, zdn2 := ops[0], ops[1], ops[2], ops[3] + if err := in.zVector(zdn, 2); err != nil { + return nil, err + } + if _, ok := zdn.Arr.sizeBits(); !ok { + return nil, fmt.Errorf("%s: arrangement %s has no size encoding in this class", + in.Name, zdn.Arr) + } + esize := zdn.Arr.elemBits() + v, err := in.shiftImmAmount(imm, 1, 1, esize-1) + if err != nil { + return nil, err + } + if err := in.predicateOperand(pg, 3, 0, 15, ExtArrNone); err != nil { + return nil, err + } + if pg.Qual != ExtQualMerging { + return nil, fmt.Errorf("%s: operand 3 wants the merging qualifier /M, got %q", + in.Name, pg.Qual) + } + if err := in.zVector(zdn2, 4); err != nil { + return nil, err + } + if zdn2.Reg != zdn.Reg || zdn2.Arr != zdn.Arr { + return nil, fmt.Errorf("%s: operands 2 and 4 are the same register Zdn, got Z%d and Z%d", + in.Name, zdn.Reg, zdn2.Reg) + } + c := esize + v + word := in.Word + word = extSet(word, extFieldImm3T5, uint32(c)) + word = extSet(word, extFieldTszM8, uint32(c>>3)) + word = extSet(word, extFieldTszH22W, uint32(c>>5)) + word = extSet(word, extFieldRd, uint32(zdn.Reg)) + word = extSet(word, extFieldPgW, uint32(pg.Reg)) + return extWordLE(word), nil +} + +// encodeShiftImmNarrow fills the three-vector narrowing shift by immediate: +// imm, Zn.Tb, Zd.T, a right shift of the wide source whose result the +// destination one element step down keeps (.H+.B, .S+.H, .D+.S). The +// amount runs 1..esize over the destination's element size and C = 2 x +// esize - amount, split imm3 into bits 18..16, the low tsz pair into bits +// 20..19 and the top tsz bit into bit 22. +func (in ExtInstr) encodeShiftImmNarrow(ops []ExtOperand) ([]byte, error) { + imm, zn, zd := ops[0], ops[1], ops[2] + if err := in.zVector(zn, 2); err != nil { + return nil, err + } + if zn.Arr < ExtArrH || zn.Arr > ExtArrD { + return nil, fmt.Errorf("%s: operand 2 carries arrangement %s, want .H, .S or .D", in.Name, zn.Arr) + } + if err := in.zVector(zd, 3); err != nil { + return nil, err + } + if zd.Arr != zn.Arr-1 { + return nil, fmt.Errorf("%s: operand 3 carries arrangement %s, want one element step below %s", + in.Name, zd.Arr, zn.Arr) + } + esize := zd.Arr.elemBits() + v, err := in.shiftImmAmount(imm, 1, 1, esize) + if err != nil { + return nil, err + } + c := 2*esize - v + word := in.Word + word = extSet(word, extFieldImm3T16, uint32(c)) + word = extSet(word, extFieldTszM19, uint32(c>>3)) + word = extSet(word, extFieldTszH22, uint32(c>>5)) + word = extSet(word, extFieldRn, uint32(zn.Reg)) + word = extSet(word, extFieldRd, uint32(zd.Reg)) + return extWordLE(word), nil +} + +// encodeShiftImmWide fills the three-vector widening shift by immediate: +// imm, Zn.Tb, Zd.T, a left shift of the narrow source into a destination +// one element step up (.B+.H, .H+.S, .S+.D). The amount runs +// 0..esize-1 over the source's element size and C = esize + amount, split +// like the narrowing class. +func (in ExtInstr) encodeShiftImmWide(ops []ExtOperand) ([]byte, error) { + imm, zn, zd := ops[0], ops[1], ops[2] + if err := in.zVector(zn, 2); err != nil { + return nil, err + } + if zn.Arr < ExtArrB || zn.Arr > ExtArrS { + return nil, fmt.Errorf("%s: operand 2 carries arrangement %s, want .B, .H or .S", in.Name, zn.Arr) + } + if err := in.zVector(zd, 3); err != nil { + return nil, err + } + if zd.Arr != zn.Arr+1 { + return nil, fmt.Errorf("%s: operand 3 carries arrangement %s, want one element step above %s", + in.Name, zd.Arr, zn.Arr) + } + esize := zn.Arr.elemBits() + v, err := in.shiftImmAmount(imm, 1, 0, esize-1) + if err != nil { + return nil, err + } + c := esize + v + word := in.Word + word = extSet(word, extFieldImm3T16, uint32(c)) + word = extSet(word, extFieldTszM19, uint32(c>>3)) + word = extSet(word, extFieldTszH22, uint32(c>>5)) + word = extSet(word, extFieldRn, uint32(zn.Reg)) + word = extSet(word, extFieldRd, uint32(zd.Reg)) + return extWordLE(word), nil +} + // encodeCountPnW fills the predicate-counter step with a scalar result: // Rd, Pn.T, Rd, the destination spelled twice. The predicate occupies the // four-bit field at bits 8..5 and its arrangement feeds the size field. @@ -4853,6 +5049,80 @@ var arm64Extensions = []ExtInstr{ Word: 0xe540a000, Form: ExtFormSveStore, Arr: ExtArrD, Feature: ExtFeatureSVE, SveMode: extMemVecBase, SveShift: 0, SveBase: ExtArrD, Ref: "ARM DDI 0487J: gather/scatter (vector base alone); inst_gen.go + arm64sveenc.s"}, + // The shift-immediate classes: the SVE2 narrowing and widening + // three-vector shifts and the predicated saturating left shifts. The + // amount rides the class's tsz:imm3 fields, whose tsz bits double as + // the arrangement spelling: right shifts take C = 2 x esize - amount + // over the destination's element size, left shifts C = esize + amount + // over the source's (or the destination's own, in the predicated + // class), and C splits imm3, a two-bit tsz chunk and a one- or + // two-bit tsz top. Provenance as above: the toolchain encoding table + // and its generated corpus, both produced from Arm's official ISA + // description, and the golden test pins every corpus line of the + // covered classes. + {Name: "ZSQSHRUNB", Summary: "Shift right narrowing, unsigned results from signed, bottom", + Word: 0x45200000, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SQSHRUN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSQSHRUNT", Summary: "Shift right narrowing, unsigned results from signed, top", + Word: 0x45200400, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SQSHRUN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSHRNB", Summary: "Shift right narrowing by immediate, bottom", + Word: 0x45201000, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSHRNT", Summary: "Shift right narrowing by immediate, top", + Word: 0x45201400, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZRSHRNB", Summary: "Rounding shift right narrowing by immediate, bottom", + Word: 0x45201800, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: RSHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZRSHRNT", Summary: "Rounding shift right narrowing by immediate, top", + Word: 0x45201c00, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: RSHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSQSHRNB", Summary: "Saturating signed shift right narrowing by immediate, bottom", + Word: 0x45202000, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SQSHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSQSHRNT", Summary: "Saturating signed shift right narrowing by immediate, top", + Word: 0x45202400, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SQSHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSQRSHRNB", Summary: "Saturating rounding signed shift right narrowing by immediate, bottom", + Word: 0x45202800, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SQRSHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSQRSHRNT", Summary: "Saturating rounding signed shift right narrowing by immediate, top", + Word: 0x45202c00, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SQRSHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZUQSHRNB", Summary: "Saturating unsigned shift right narrowing by immediate, bottom", + Word: 0x45203000, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: UQSHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZUQSHRNT", Summary: "Saturating unsigned shift right narrowing by immediate, top", + Word: 0x45203400, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: UQSHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZUQRSHRNB", Summary: "Saturating rounding unsigned shift right narrowing by immediate, bottom", + Word: 0x45203800, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: UQRSHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZUQRSHRNT", Summary: "Saturating rounding unsigned shift right narrowing by immediate, top", + Word: 0x45203c00, Form: ExtFormShiftImmNarrow, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: UQRSHRN (narrowing, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSSHLLB", Summary: "Signed shift left widening by immediate, bottom", + Word: 0x4500a000, Form: ExtFormShiftImmWide, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SSHLL (widening, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSSHLLT", Summary: "Signed shift left widening by immediate, top", + Word: 0x4500a400, Form: ExtFormShiftImmWide, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SSHLL (widening, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZUSHLLB", Summary: "Unsigned shift left widening by immediate, bottom", + Word: 0x4500a800, Form: ExtFormShiftImmWide, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: USHLL (widening, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZUSHLLT", Summary: "Unsigned shift left widening by immediate, top", + Word: 0x4500ac00, Form: ExtFormShiftImmWide, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: USHLL (widening, immediate); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSQSHL", Summary: "Saturating signed shift left by immediate, over a governing predicate", + Word: 0x04068000, Form: ExtFormShiftImmPred, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SQSHL (immediate, predicated); inst_gen.go + arm64sveenc.s"}, + {Name: "ZSQSHLU", Summary: "Saturating signed shift left by immediate, unsigned bases, over a governing predicate", + Word: 0x040f8000, Form: ExtFormShiftImmPred, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: SQSHLU (immediate, predicated); inst_gen.go + arm64sveenc.s"}, + {Name: "ZUQSHL", Summary: "Saturating unsigned shift left by immediate, over a governing predicate", + Word: 0x04078000, Form: ExtFormShiftImmPred, Feature: ExtFeatureSVE2, + Ref: "ARM DDI 0487J SVE2: UQSHL (immediate, predicated); 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 91fc23f..bb17d62 100644 --- a/arch/arm64_ext_test.go +++ b/arch/arm64_ext_test.go @@ -1683,6 +1683,184 @@ func extSveRow(t *testing.T, mnem string, mode uint8, shift int, base, arr ExtAr return ExtInstr{} } +// TestArm64ExtShiftImmGolden pins the shift-immediate classes against the +// toolchain-generated corpus at the metadata layer: the narrowing and +// widening three-vector shifts and the predicated saturating left shifts. +// Every row is one arm64sveenc.s line and the want word is that line's own +// encoding; the amount's tsz:imm3 split is derived in the encoders, right +// shifts taking C = 2 x esize - amount over the destination's element size +// and left shifts C = esize + amount over the source's (or the +// destination's own, in the predicated class). +func TestArm64ExtShiftImmGolden(t *testing.T) { + for _, tt := range []struct { + name string + mnem string + form ExtForm + ops []ExtOperand + want uint32 + }{ + // The narrowing family, one corpus line each over .S+.H. + {"ZSHRNB $9, Z7.S, Z23.H", "ZSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453710f7}, + {"ZSHRNT $9, Z7.S, Z23.H", "ZSHRNT", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453714f7}, + {"ZRSHRNB $9, Z7.S, Z23.H", "ZRSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453718f7}, + {"ZRSHRNT $9, Z7.S, Z23.H", "ZRSHRNT", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x45371cf7}, + {"ZSQSHRNB $9, Z7.S, Z23.H", "ZSQSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453720f7}, + {"ZSQSHRNT $9, Z7.S, Z23.H", "ZSQSHRNT", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453724f7}, + {"ZSQRSHRNB $9, Z7.S, Z23.H", "ZSQRSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453728f7}, + {"ZSQRSHRNT $9, Z7.S, Z23.H", "ZSQRSHRNT", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x45372cf7}, + {"ZSQSHRUNB $9, Z7.S, Z23.H", "ZSQSHRUNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453700f7}, + {"ZSQSHRUNT $9, Z7.S, Z23.H", "ZSQSHRUNT", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453704f7}, + {"ZUQSHRNB $9, Z7.S, Z23.H", "ZUQSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453730f7}, + {"ZUQSHRNT $9, Z7.S, Z23.H", "ZUQSHRNT", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453734f7}, + {"ZUQRSHRNB $9, Z7.S, Z23.H", "ZUQRSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x453738f7}, + {"ZUQRSHRNT $9, Z7.S, Z23.H", "ZUQRSHRNT", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + 0x45373cf7}, + // The width extensions the corpus does not carry: .H+.B and .D+.S, + // derived the same way and pinned to the words the fields give. + {"ZSHRNB $8, Z6.H, Z22.B", "ZSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(8), ExtVector(6, ExtArrH), ExtVector(22, ExtArrB)}, + 0x452810d6}, + {"ZSQSHRNT $16, Z5.D, Z4.S", "ZSQSHRNT", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(16), ExtVector(5, ExtArrD), ExtVector(4, ExtArrS)}, + 0x457024a4}, + // The widening family, one corpus line each over .S+.D. + {"ZSSHLLB $5, Z22.S, Z10.D", "ZSSHLLB", ExtFormShiftImmWide, + []ExtOperand{ExtImmediate(5), ExtVector(22, ExtArrS), ExtVector(10, ExtArrD)}, + 0x4545a2ca}, + {"ZSSHLLT $5, Z22.S, Z10.D", "ZSSHLLT", ExtFormShiftImmWide, + []ExtOperand{ExtImmediate(5), ExtVector(22, ExtArrS), ExtVector(10, ExtArrD)}, + 0x4545a6ca}, + {"ZUSHLLB $5, Z22.S, Z10.D", "ZUSHLLB", ExtFormShiftImmWide, + []ExtOperand{ExtImmediate(5), ExtVector(22, ExtArrS), ExtVector(10, ExtArrD)}, + 0x4545aaca}, + {"ZUSHLLT $5, Z22.S, Z10.D", "ZUSHLLT", ExtFormShiftImmWide, + []ExtOperand{ExtImmediate(5), ExtVector(22, ExtArrS), ExtVector(10, ExtArrD)}, + 0x4545aeca}, + {"ZSSHLLB $0, Z3.B, Z2.H", "ZSSHLLB", ExtFormShiftImmWide, + []ExtOperand{ExtImmediate(0), ExtVector(3, ExtArrB), ExtVector(2, ExtArrH)}, + 0x4508a062}, + // The predicated saturating left shifts, one corpus line each. + {"ZSQSHL $6, Z23.B, P1.M, Z23.B", "ZSQSHL", ExtFormShiftImmPred, + []ExtOperand{ExtImmediate(6), ExtVector(23, ExtArrB), ExtPredicate(1, ExtQualMerging), ExtVector(23, ExtArrB)}, + 0x040685d7}, + {"ZSQSHLU $6, Z23.B, P1.M, Z23.B", "ZSQSHLU", ExtFormShiftImmPred, + []ExtOperand{ExtImmediate(6), ExtVector(23, ExtArrB), ExtPredicate(1, ExtQualMerging), ExtVector(23, ExtArrB)}, + 0x040f85d7}, + {"ZUQSHL $6, Z23.B, P1.M, Z23.B", "ZUQSHL", ExtFormShiftImmPred, + []ExtOperand{ExtImmediate(6), ExtVector(23, ExtArrB), ExtPredicate(1, ExtQualMerging), ExtVector(23, ExtArrB)}, + 0x040785d7}, + {"ZSQSHL $20, Z8.S, P4.M, Z8.S", "ZSQSHL", ExtFormShiftImmPred, + []ExtOperand{ExtImmediate(20), ExtVector(8, ExtArrS), ExtPredicate(4, ExtQualMerging), ExtVector(8, ExtArrS)}, + 0x04469288}, + } { + in := extInstructionMeta(t, tt.mnem, tt.form, 0, ExtArrNone) + 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) + } + } +} + +// TestArm64ExtShiftImmRejects pins the diagnostics the shift-immediate +// forms give: the element-step pairs the classes take, the amount ranges +// and the read-back and qualifier rules of the predicated class. +func TestArm64ExtShiftImmRejects(t *testing.T) { + for _, tt := range []struct { + name string + mnem string + form ExtForm + ops []ExtOperand + quote string + }{ + {"source not one step wide", "ZSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrD), ExtVector(23, ExtArrH)}, + "one element step below .D"}, + {"destination not one step narrow", "ZSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrS), ExtVector(23, ExtArrS)}, + "one element step below"}, + {"byte source", "ZSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(9), ExtVector(7, ExtArrB), ExtVector(23, ExtArrB)}, + "want .H, .S or .D"}, + {"amount over the destination element", "ZSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(17), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + "outside the range 1..16"}, + {"amount zero on a right shift", "ZSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtImmediate(0), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + "outside the range 1..16"}, + {"LSL spelling refused", "ZSHRNB", ExtFormShiftImmNarrow, + []ExtOperand{ExtShiftedImmediate(9, 8), ExtVector(7, ExtArrS), ExtVector(23, ExtArrH)}, + "takes no LSL shift"}, + {"source not one step narrow", "ZSSHLLB", ExtFormShiftImmWide, + []ExtOperand{ExtImmediate(5), ExtVector(22, ExtArrS), ExtVector(10, ExtArrS)}, + "one element step above .S"}, + {"destination not one step wide", "ZSSHLLB", ExtFormShiftImmWide, + []ExtOperand{ExtImmediate(5), ExtVector(22, ExtArrS), ExtVector(10, ExtArrQ)}, + "one element step above"}, + {"amount over the source element", "ZSSHLLB", ExtFormShiftImmWide, + []ExtOperand{ExtImmediate(32), ExtVector(22, ExtArrS), ExtVector(10, ExtArrD)}, + "outside the range 0..31"}, + {"zeroing qualifier", "ZSQSHL", ExtFormShiftImmPred, + []ExtOperand{ExtImmediate(6), ExtVector(23, ExtArrB), ExtPredicate(1, ExtQualZeroing), ExtVector(23, ExtArrB)}, + "/M"}, + {"destination read-back mismatch", "ZSQSHL", ExtFormShiftImmPred, + []ExtOperand{ExtImmediate(6), ExtVector(23, ExtArrB), ExtPredicate(1, ExtQualMerging), ExtVector(22, ExtArrB)}, + "the same register Zdn"}, + {"destination arrangement mismatch", "ZSQSHL", ExtFormShiftImmPred, + []ExtOperand{ExtImmediate(6), ExtVector(23, ExtArrB), ExtPredicate(1, ExtQualMerging), ExtVector(23, ExtArrH)}, + "the same register Zdn"}, + {"quadword has no size encoding", "ZSQSHL", ExtFormShiftImmPred, + []ExtOperand{ExtImmediate(6), ExtVector(23, ExtArrQ), ExtPredicate(1, ExtQualMerging), ExtVector(23, ExtArrQ)}, + "no size encoding"}, + {"amount over the element size", "ZSQSHL", ExtFormShiftImmPred, + []ExtOperand{ExtImmediate(8), ExtVector(23, ExtArrB), ExtPredicate(1, ExtQualMerging), ExtVector(23, ExtArrB)}, + "outside the range 1..7"}, + {"amount zero", "ZSQSHL", ExtFormShiftImmPred, + []ExtOperand{ExtImmediate(0), ExtVector(23, ExtArrB), ExtPredicate(1, ExtQualMerging), ExtVector(23, ExtArrB)}, + "outside the range 1..7"}, + } { + 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) + } + } +} + // TestArm64ExtGatherGolden pins the gather/scatter family against the // toolchain-generated corpus at the metadata layer: one row per addressing // mode, the want word that corpus line's own encoding. The asm-level diff --git a/asm/arm64_ext_asm_test.go b/asm/arm64_ext_asm_test.go index 7c6501f..3373987 100644 --- a/asm/arm64_ext_asm_test.go +++ b/asm/arm64_ext_asm_test.go @@ -956,3 +956,84 @@ func TestArm64AssembleExtensionGatherRefusals(t *testing.T) { } } } + +// TestArm64AssembleExtensionShiftImmGolden drives the shift-immediate +// classes through the full assembler: every corpus line of the three +// families, corpus text in, corpus word out. The narrowing and widening +// three-vector shifts and the predicated saturating left shifts carry the +// amount in the class's tsz:imm3 fields, whose tsz bits double as the +// arrangement spelling. +func TestArm64AssembleExtensionShiftImmGolden(t *testing.T) { + tests := []struct { + stmt string + want uint32 + }{ + {"ZSHRNB $9, Z7.S, Z23.H", 0x453710f7}, + {"ZSHRNT $9, Z7.S, Z23.H", 0x453714f7}, + {"ZRSHRNB $9, Z7.S, Z23.H", 0x453718f7}, + {"ZRSHRNT $9, Z7.S, Z23.H", 0x45371cf7}, + {"ZSQSHRNB $9, Z7.S, Z23.H", 0x453720f7}, + {"ZSQSHRNT $9, Z7.S, Z23.H", 0x453724f7}, + {"ZSQRSHRNB $9, Z7.S, Z23.H", 0x453728f7}, + {"ZSQRSHRNT $9, Z7.S, Z23.H", 0x45372cf7}, + {"ZSQSHRUNB $9, Z7.S, Z23.H", 0x453700f7}, + {"ZSQSHRUNT $9, Z7.S, Z23.H", 0x453704f7}, + {"ZUQSHRNB $9, Z7.S, Z23.H", 0x453730f7}, + {"ZUQSHRNT $9, Z7.S, Z23.H", 0x453734f7}, + {"ZUQRSHRNB $9, Z7.S, Z23.H", 0x453738f7}, + {"ZUQRSHRNT $9, Z7.S, Z23.H", 0x45373cf7}, + {"ZSSHLLB $5, Z22.S, Z10.D", 0x4545a2ca}, + {"ZSSHLLT $5, Z22.S, Z10.D", 0x4545a6ca}, + {"ZUSHLLB $5, Z22.S, Z10.D", 0x4545aaca}, + {"ZUSHLLT $5, Z22.S, Z10.D", 0x4545aeca}, + {"ZSQSHL $6, Z23.B, P1.M, Z23.B", 0x040685d7}, + {"ZSQSHLU $6, Z23.B, P1.M, Z23.B", 0x040f85d7}, + {"ZUQSHL $6, Z23.B, P1.M, Z23.B", 0x040785d7}, + // Width extensions beyond the corpus lines. + {"ZSHRNB $8, Z6.H, Z22.B", 0x452810d6}, + {"ZSQSHRNT $16, Z5.D, Z4.S", 0x457024a4}, + {"ZSSHLLB $0, Z3.B, Z2.H", 0x4508a062}, + {"ZSQSHL $20, Z8.S, P4.M, Z8.S", 0x04469288}, + } + 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]) + } + } +} + +// TestArm64AssembleExtensionShiftImmRefusals pins the diagnostics the +// shift-immediate statements get from the layer: the element-step pairs +// the classes take, the amount ranges and the read-back and qualifier +// rules of the predicated class. +func TestArm64AssembleExtensionShiftImmRefusals(t *testing.T) { + tests := []struct { + stmt string + want string + }{ + {"ZSHRNB $9, Z7.S, Z23.S", "one element step below"}, + {"ZSHRNB $9, Z7.S, Z23.B", "one element step below"}, + {"ZSHRNB $17, Z7.S, Z23.H", "outside the range 1..16"}, + {"ZSHRNB $0, Z7.S, Z23.H", "outside the range 1..16"}, + {"ZSHRNB $9, LSL #8, Z7.S, Z23.H", "takes no LSL shift"}, + {"ZSSHLLB $32, Z22.S, Z10.D", "outside the range 0..31"}, + {"ZSSHLLB $5, Z22.S, Z10.Q", "one element step above .S"}, + {"ZSQSHL $6, Z23.B, P1.Z, Z23.B", "/M"}, + {"ZSQSHL $6, Z23.B, P1.M, Z22.B", "the same register Zdn"}, + {"ZSQSHL $6, Z23.Q, P1.M, Z23.Q", "no size encoding"}, + {"ZSQSHL $8, Z23.B, P1.M, Z23.B", "outside the range 1..7"}, + } + 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) + } + } +} diff --git a/asm/extension_test.go b/asm/extension_test.go index 8383ec6..2c47417 100644 --- a/asm/extension_test.go +++ b/asm/extension_test.go @@ -492,12 +492,36 @@ func TestExtensionNamesARM64(t *testing.T) { "ZST1D", "ZST1H", "ZST1W", + // The shift-immediate classes: the narrowing and widening + // three-vector shifts and the predicated saturating left shifts, + // in first-occurrence order. + "ZSQSHRUNB", + "ZSQSHRUNT", + "ZSHRNB", + "ZSHRNT", + "ZRSHRNB", + "ZRSHRNT", + "ZSQSHRNB", + "ZSQSHRNT", + "ZSQRSHRNB", + "ZSQRSHRNT", + "ZUQSHRNB", + "ZUQSHRNT", + "ZUQRSHRNB", + "ZUQRSHRNT", + "ZSSHLLB", + "ZSSHLLT", + "ZUSHLLB", + "ZUSHLLT", + "ZSQSHL", + "ZSQSHLU", + "ZUQSHL", } 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 != 507 { - t.Errorf("the family registers %d instructions, want 507", n) + if n := len(arch.Extensions(arch.ARM64)); n != 528 { + t.Errorf("the family registers %d instructions, want 528", n) } }