diff --git a/arch/arm64_ext.go b/arch/arm64_ext.go index 3a4b0d9..a63360a 100644 --- a/arch/arm64_ext.go +++ b/arch/arm64_ext.go @@ -666,6 +666,19 @@ const ( // Operands: Zt.T (the row's locked list arrangement), Pg (bare, P0-P7), // mem. The field layout mirrors the load with the list first. ExtFormSveStore + // ExtFormNarrow is the SVE2.1 two-to-one narrowing form, ZADDHNB Z22.S, + // Z10.S, Z8.H: two wide vectors sum and the destination keeps the high + // half of each result. Operands: Zm.T2, Zn.T2, Zd.T, where T2 is one + // step wider than T (.H+.B, .S+.H, .D+.S). The single size field + // over-determines the pair, so the arrangements are validated together. + ExtFormNarrow + // ExtFormQuadReduce is the SVE2.1 quadword reduction, the accumulator + // first: ZADDQV Z25.S, P3, V5.S4. Operands: Zdn.T, Pg (bare, P0-P7), + // Vn with the counted quadword spelling .B16, .H8, .S4 or .D2 matching + // Zdn's arrangement. The fields place Zdn in the first-source slot, + // the predicate in the narrow governing field and the vector in the + // destination slot, the reduction class's own layout. + ExtFormQuadReduce ) // Arity returns the operand count the form takes. @@ -703,7 +716,7 @@ func (f ExtForm) Arity() int { return 2 case ExtFormReduceReadBack: return 4 - case ExtFormSveLoad, ExtFormSveStore: + case ExtFormSveLoad, ExtFormSveStore, ExtFormNarrow, ExtFormQuadReduce: return 3 case ExtFormNone: return 0 @@ -774,6 +787,10 @@ func (f ExtForm) Kinds() []ExtOperandKind { return []ExtOperandKind{ExtSveMem, ExtPReg, ExtZReg} case ExtFormSveStore: return []ExtOperandKind{ExtZReg, ExtPReg, ExtSveMem} + case ExtFormNarrow: + return []ExtOperandKind{ExtZReg, ExtZReg, ExtZReg} + case ExtFormQuadReduce: + return []ExtOperandKind{ExtZReg, ExtPReg, ExtVReg} case ExtFormNone: return nil default: @@ -896,6 +913,10 @@ func (f ExtForm) String() string { return "gather load" case ExtFormSveStore: return "scatter store" + case ExtFormNarrow: + return "narrowing two-to-one" + case ExtFormQuadReduce: + return "quadword reduction" case ExtFormNone: return "no operands" default: @@ -1148,6 +1169,10 @@ func (in ExtInstr) Encode(ops []ExtOperand) ([]byte, error) { return in.encodeSveLoad(ops) case ExtFormSveStore: return in.encodeSveStore(ops) + case ExtFormNarrow: + return in.encodeNarrow(ops) + case ExtFormQuadReduce: + return in.encodeQuadReduce(ops) case ExtFormNone: if len(ops) != 0 { return nil, fmt.Errorf("%s: the %s form takes no operands, got %d", @@ -2522,6 +2547,77 @@ func (in ExtInstr) encodeSveStore(ops []ExtOperand) ([]byte, error) { return extWordLE(word), nil } +// encodeNarrow fills the two-to-one narrowing form: Zm.T2, Zn.T2, Zd.T, +// where T is one step below T2. The wide sources combine and the +// destination keeps the high half of each result; the single size field +// serves both widths, the wide ladder on the sources and the narrow ladder +// on the destination carrying the same value, so the arrangement pair is +// validated as a whole. +func (in ExtInstr) encodeNarrow(ops []ExtOperand) ([]byte, error) { + zm, zn, zd := ops[0], ops[1], ops[2] + for i, op := range []ExtOperand{zm, zn, zd} { + if err := in.zVector(op, i+1); err != nil { + return nil, err + } + } + if zm.Arr != zn.Arr { + return nil, fmt.Errorf("%s: operands 1 and 2 carry arrangements %s and %s, they must match", + in.Name, zm.Arr, zn.Arr) + } + if zm.Arr < ExtArrH || zm.Arr > ExtArrD { + return nil, fmt.Errorf("%s: the wide sources carry arrangement %s, want .H, .S or .D", + in.Name, zm.Arr) + } + if zd.Arr != zm.Arr-1 { + return nil, fmt.Errorf("%s: the destination carries arrangement %s, want %s", + in.Name, zd.Arr, zm.Arr-1) + } + size, _ := zm.Arr.sizeBits() + word := in.Word + word = extSet(word, extFieldRm, uint32(zm.Reg)) + word = extSet(word, extFieldRn, uint32(zn.Reg)) + word = extSet(word, extFieldRd, uint32(zd.Reg)) + word = extSet(word, in.Size, size) + return extWordLE(word), nil +} + +// encodeQuadReduce fills the SVE2.1 quadword reduction, the accumulator +// first: Zdn.T, Pg (bare), Vn.T2. The scalable vector register accumulates +// the reduction of the quadword vector, whose counted spelling (.B16, .H8, +// .S4, .D2) names the lane width the class reduces over and must match the +// accumulator's arrangement; the encoding carries the accumulator's own +// size field. The fields mirror the reduction class: Zdn in the +// first-source slot, the predicate in the narrow governing field, the +// vector in the destination slot. +func (in ExtInstr) encodeQuadReduce(ops []ExtOperand) ([]byte, error) { + zdn, pg, vn := ops[0], ops[1], ops[2] + if err := in.zVector(zdn, 1); err != nil { + return nil, err + } + if err := in.barePredicate(pg, 2, 0, 7); err != nil { + return nil, err + } + if err := in.simdReg(vn, 3); err != nil { + return nil, err + } + if vn.Arr != zdn.Arr { + return nil, fmt.Errorf("%s: operand 3 carries arrangement %s, want %s", + in.Name, vn.Arr, zdn.Arr) + } + size, err := in.zSize(zdn.Arr) + if err != nil { + return nil, err + } + word := in.Word + word = extSet(word, extFieldRn, uint32(zdn.Reg)) + word = extSet(word, extFieldPgN, uint32(pg.Reg)) + word = extSet(word, extFieldRd, uint32(vn.Reg)) + if size != 0 { + 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. @@ -3059,6 +3155,40 @@ var arm64Extensions = []ExtInstr{ {Name: "ZZIPQ2", Summary: "Z-alias of the unpredicated three-vector form", Word: 0x4400e400, Form: ExtFormVectorsZm, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, Ref: "ARM DDI 0487J SVE2.1: ZZIPQ2; inst_gen.go + arm64sveenc.s"}, + // --- the SVE2.1 narrowing two-to-one family ------------------------------ + // + // The add, rounding add, subtract and rounding subtract narrow steps: + // two wide vectors combine and the destination keeps the high half of + // each result, bottom or top. The sources carry .H, .S or .D and the + // destination the arrangement one step below; one size field serves + // both. 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 family. + {Name: "ZADDHNB", Summary: "Add narrow high, bottom half destination", + Word: 0x45206000, Form: ExtFormNarrow, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: ZADDHNB; inst_gen.go + arm64sveenc.s"}, + {Name: "ZADDHNT", Summary: "Add narrow high, top half destination", + Word: 0x45206400, Form: ExtFormNarrow, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: ZADDHNT; inst_gen.go + arm64sveenc.s"}, + {Name: "ZRADDHNB", Summary: "Add narrow high with rounding, bottom half destination", + Word: 0x45206800, Form: ExtFormNarrow, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: ZRADDHNB; inst_gen.go + arm64sveenc.s"}, + {Name: "ZRADDHNT", Summary: "Add narrow high with rounding, top half destination", + Word: 0x45206c00, Form: ExtFormNarrow, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: ZRADDHNT; inst_gen.go + arm64sveenc.s"}, + {Name: "ZRSUBHNB", Summary: "Subtract narrow high with rounding, bottom half destination", + Word: 0x45207800, Form: ExtFormNarrow, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: ZRSUBHNB; inst_gen.go + arm64sveenc.s"}, + {Name: "ZRSUBHNT", Summary: "Subtract narrow high with rounding, top half destination", + Word: 0x45207c00, Form: ExtFormNarrow, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: ZRSUBHNT; inst_gen.go + arm64sveenc.s"}, + {Name: "ZSUBHNB", Summary: "Subtract narrow high, bottom half destination", + Word: 0x45207000, Form: ExtFormNarrow, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: ZSUBHNB; inst_gen.go + arm64sveenc.s"}, + {Name: "ZSUBHNT", Summary: "Subtract narrow high, top half destination", + Word: 0x45207400, Form: ExtFormNarrow, Size: extSizeBHSD, Feature: ExtFeatureSVE2p1, + Ref: "ARM DDI 0487J SVE2.1: ZSUBHNT; inst_gen.go + arm64sveenc.s"}, + // --- the SVE2 crypto family ---------------------------------------------- // // The multi-precision carry steps (ADCLB, ADCLT, SBCLB, SBCLT), the SHA3 diff --git a/arch/arm64_ext_test.go b/arch/arm64_ext_test.go index f603916..b34701b 100644 --- a/arch/arm64_ext_test.go +++ b/arch/arm64_ext_test.go @@ -1338,6 +1338,62 @@ func TestArm64ExtStage3Golden(t *testing.T) { } } +// TestArm64ExtStage4Golden pins the SVE2.1 narrowing two-to-one family +// (ZADDHNB and friends) against the toolchain-generated corpus, the same +// provenance as the families above: every row is one arm64sveenc.s line and +// the want word is that line's own encoding, with two extra vectors pinning +// the widths the corpus line does not show (.H over .B, .D over .S). +func TestArm64ExtStage4Golden(t *testing.T) { + for _, tt := range []struct { + name string + mnem string + form ExtForm + ops []ExtOperand + want uint32 + }{ + {"ZADDHNB Z22.S, Z10.S, Z8.H", "ZADDHNB", ExtFormNarrow, + []ExtOperand{ExtVector(22, ExtArrS), ExtVector(10, ExtArrS), ExtVector(8, ExtArrH)}, + 0x45b66148}, + {"ZADDHNT Z22.S, Z10.S, Z8.H", "ZADDHNT", ExtFormNarrow, + []ExtOperand{ExtVector(22, ExtArrS), ExtVector(10, ExtArrS), ExtVector(8, ExtArrH)}, + 0x45b66548}, + {"ZRADDHNB Z22.S, Z10.S, Z8.H", "ZRADDHNB", ExtFormNarrow, + []ExtOperand{ExtVector(22, ExtArrS), ExtVector(10, ExtArrS), ExtVector(8, ExtArrH)}, + 0x45b66948}, + {"ZRADDHNT Z22.S, Z10.S, Z8.H", "ZRADDHNT", ExtFormNarrow, + []ExtOperand{ExtVector(22, ExtArrS), ExtVector(10, ExtArrS), ExtVector(8, ExtArrH)}, + 0x45b66d48}, + {"ZRSUBHNB Z22.S, Z10.S, Z8.H", "ZRSUBHNB", ExtFormNarrow, + []ExtOperand{ExtVector(22, ExtArrS), ExtVector(10, ExtArrS), ExtVector(8, ExtArrH)}, + 0x45b67948}, + {"ZRSUBHNT Z22.S, Z10.S, Z8.H", "ZRSUBHNT", ExtFormNarrow, + []ExtOperand{ExtVector(22, ExtArrS), ExtVector(10, ExtArrS), ExtVector(8, ExtArrH)}, + 0x45b67d48}, + {"ZSUBHNB Z22.S, Z10.S, Z8.H", "ZSUBHNB", ExtFormNarrow, + []ExtOperand{ExtVector(22, ExtArrS), ExtVector(10, ExtArrS), ExtVector(8, ExtArrH)}, + 0x45b67148}, + {"ZSUBHNT Z22.S, Z10.S, Z8.H", "ZSUBHNT", ExtFormNarrow, + []ExtOperand{ExtVector(22, ExtArrS), ExtVector(10, ExtArrS), ExtVector(8, ExtArrH)}, + 0x45b67548}, + {"ZADDHNB Z8.H, Z10.H, Z11.B", "ZADDHNB", ExtFormNarrow, + []ExtOperand{ExtVector(8, ExtArrH), ExtVector(10, ExtArrH), ExtVector(11, ExtArrB)}, + 0x4568614b}, + {"ZSUBHNT Z26.D, Z4.D, Z13.S", "ZSUBHNT", ExtFormNarrow, + []ExtOperand{ExtVector(26, ExtArrD), ExtVector(4, ExtArrD), ExtVector(13, ExtArrS)}, + 0x45fa748d}, + } { + in := extInstructionMeta(t, tt.mnem, tt.form, ExtQualNone, 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) + } + } +} + // extSveRow finds the gather/scatter row whose mnemonic, addressing mode, // base size, shift and list arrangement all match. func extSveRow(t *testing.T, mnem string, mode uint8, shift int, base, arr ExtArrangement) ExtInstr { diff --git a/asm/arm64_ext_asm_test.go b/asm/arm64_ext_asm_test.go index 4f53f36..df2fd08 100644 --- a/asm/arm64_ext_asm_test.go +++ b/asm/arm64_ext_asm_test.go @@ -745,6 +745,62 @@ func TestArm64AssembleExtensionGatherGolden(t *testing.T) { } } +// TestArm64AssembleExtensionStage4Golden drives the SVE2.1 narrowing +// two-to-one family through the full assembler: corpus text in, corpus word +// out, with the two width extensions the arch-level golden test pins. +func TestArm64AssembleExtensionStage4Golden(t *testing.T) { + tests := []struct { + stmt string + want uint32 + }{ + {"ZADDHNB Z22.S, Z10.S, Z8.H", 0x45b66148}, + {"ZADDHNT Z22.S, Z10.S, Z8.H", 0x45b66548}, + {"ZRADDHNB Z22.S, Z10.S, Z8.H", 0x45b66948}, + {"ZRADDHNT Z22.S, Z10.S, Z8.H", 0x45b66d48}, + {"ZRSUBHNB Z22.S, Z10.S, Z8.H", 0x45b67948}, + {"ZRSUBHNT Z22.S, Z10.S, Z8.H", 0x45b67d48}, + {"ZSUBHNB Z22.S, Z10.S, Z8.H", 0x45b67148}, + {"ZSUBHNT Z22.S, Z10.S, Z8.H", 0x45b67548}, + {"ZADDHNB Z8.H, Z10.H, Z11.B", 0x4568614b}, + {"ZSUBHNT Z26.D, Z4.D, Z13.S", 0x45fa748d}, + } + 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]) + } + } +} + +// TestArm64AssembleExtensionStage4Refusals pins the diagnostics the +// narrowing statements get from the layer: the arrangement pair the single +// size field over-determines. +func TestArm64AssembleExtensionStage4Refusals(t *testing.T) { + tests := []struct { + stmt string + want string + }{ + {"ZADDHNB Z22.B, Z10.B, Z8.H", "want .H, .S or .D"}, + {"ZADDHNB Z22.S, Z10.H, Z8.H", "must match"}, + {"ZADDHNB Z22.S, Z10.S, Z8.S", "want .H"}, + {"ZADDHNB Z22.D, Z10.D, Z8.H", "want .S"}, + {"ZADDHNB Z22.Q, Z10.Q, Z8.D", "want .H, .S or .D"}, + {"ZADDHNB Z22.S, Z10.S, Z8", "carries no arrangement suffix"}, + } + 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) + } + } +} + // TestArm64AssembleExtensionGatherRefusals pins the diagnostics the // gather/scatter statements get from the layer: the addressing-mode // mismatches, the locked list arrangements, the zeroing qualifier of the diff --git a/asm/extension_test.go b/asm/extension_test.go index 9670e52..5e1abb4 100644 --- a/asm/extension_test.go +++ b/asm/extension_test.go @@ -273,6 +273,15 @@ func TestExtensionNamesARM64(t *testing.T) { "ZZIP2", "ZZIPQ1", "ZZIPQ2", + // The stage-four families: the SVE2.1 narrowing two-to-one set. + "ZADDHNB", + "ZADDHNT", + "ZRADDHNB", + "ZRADDHNT", + "ZRSUBHNB", + "ZRSUBHNT", + "ZSUBHNB", + "ZSUBHNT", // The stage-three families: the SVE2 crypto group, the predicate // counters and loop terminators with the 32-bit while compares, and // the reductions into a SIMD register. @@ -385,7 +394,7 @@ func TestExtensionNamesARM64(t *testing.T) { if strings.Join(got, ",") != strings.Join(want, ",") { t.Errorf("ExtensionNames(ARM64) = %v, want %v", got, want) } - if n := len(arch.Extensions(arch.ARM64)); n != 381 { - t.Errorf("the family registers %d instructions, want 381", n) + if n := len(arch.Extensions(arch.ARM64)); n != 389 { + t.Errorf("the family registers %d instructions, want 389", n) } }