feat(asm): encode the arm64 SVE2 crypto, counter and reduction families

Assisted-by: GLM 5.3
This commit is contained in:
petrbalvin committed 2026-10-07 13:51:10 +02:00
1 parent dac0a5b51b
commit 6faf850793
5 files changed
+1211 -16

No files matched your search

+54 -13
View File
@@ -15,7 +15,11 @@
// 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.
// for the counter destinations, and the bare SETFFR. Stage three adds the
// crypto family (Zn.T, Zd.T, Zd.T read-back and the in-place Zd.T, Zd.T),
// the predicate counters (Pn.T, Pg, Rd; Pn.T, ZR; Rd, Pn.T, Rd; ZR and R
// terminators) and the reductions (Zn.T, Pg, Vd over the SIMD register
// V0-V31, with ZR and RSP accepted where the classes take them).
package asm
@@ -45,7 +49,7 @@ func arm64ExtStatement(mnem string, ops []*ast.Operand) (extops []arch.ExtOperan
if _, ok := LookupExtension(arch.ARM64, mnem); !ok {
return nil, false, nil
}
if !arm64ExtPinned(ops) {
if !arm64ExtPinned(mnem, ops) {
return nil, false, nil
}
out := make([]arch.ExtOperand, 0, len(ops))
@@ -87,6 +91,18 @@ func arm64ExtStatement(mnem string, ops []*ast.Operand) (extops []arch.ExtOperan
out = append(out, ext)
continue
}
if text == "ZR" {
out = append(out, arch.ExtZeroRegister())
continue
}
if text == "RSP" {
out = append(out, arch.ExtStackPointer())
continue
}
if ext, ok := arm64ExtSIMD(text); ok {
out = append(out, ext)
continue
}
if ext, ok := arm64ExtGeneral(text); ok {
out = append(out, ext)
continue
@@ -96,18 +112,27 @@ func arm64ExtStatement(mnem string, ops []*ast.Operand) (extops []arch.ExtOperan
return out, true, nil
}
// 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 {
// arm64ExtPinned reports whether the statement belongs to the layer. A
// mnemonic the extension layer registers on its own, one the generated
// arm64 table does not know, owns every one of its statements: no scalar
// path could mean it instead, and the layer's diagnostics replace the
// unsupported-instruction complaint. A mnemonic both tables carry (the
// SVE aliases of ADD, SUB and MUL) keeps the operand-shape test: 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 shape 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(mnem string, ops []*ast.Operand) bool {
if len(ops) == 0 {
return true
}
if _, shared := a64InstrTable[mnem]; !shared {
return true
}
for _, op := range ops {
if op.Kind == ast.OpImmediate {
continue
@@ -230,8 +255,9 @@ func arm64ExtCounter(text string) (arch.ExtOperand, bool) {
}
// 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.
// spelling the while-compare forms take, beside the ZR and RSP spellings of
// the thirty-first slot the conversion above reads. 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 {
@@ -244,6 +270,21 @@ func arm64ExtGeneral(text string) (arch.ExtOperand, bool) {
return arch.ExtOperand{Kind: arch.ExtGReg, Reg: reg}, true
}
// arm64ExtSIMD parses a 128-bit SIMD register operand: V0..V31, written
// bare, the scalar destination the reductions and the crypto read-back
// forms take. The register range is left to the encoding.
func arm64ExtSIMD(text string) (arch.ExtOperand, bool) {
rest, ok := strings.CutPrefix(text, "V")
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.ExtVReg, 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) {
+138
View File
@@ -424,3 +424,141 @@ func TestArm64AssembleExtensionStage2Golden(t *testing.T) {
}
}
}
// TestArm64AssembleExtensionStage3Golden drives the stage-three families
// through the full assembler: corpus text in, corpus word out. The
// statements carry the spellings the layer reads: the read-back crypto
// destinations, the ZR of the dropped counter results, the bare governing
// predicates and the V destinations of the reductions.
func TestArm64AssembleExtensionStage3Golden(t *testing.T) {
tests := []struct {
stmt string
want uint32
}{
// The crypto family.
{"ZADCLB Z7.D, Z23.D, Z13.D", 0x4547d2ed},
{"ZADCLT Z7.D, Z23.D, Z13.D", 0x4547d6ed},
{"ZSBCLB Z7.D, Z23.D, Z13.D", 0x45c7d2ed},
{"ZSBCLT Z7.D, Z23.D, Z13.D", 0x45c7d6ed},
{"ZRAX1 Z7.D, Z6.D, Z23.D", 0x4527f4d7},
{"ZSM4EKEY Z7.S, Z6.S, Z23.S", 0x4527f0d7},
{"ZSM4E Z7.S, Z6.S, Z6.S", 0x4523e0e6},
{"ZAESD Z7.B, Z6.B, Z6.B", 0x4522e4e6},
{"ZAESE Z7.B, Z6.B, Z6.B", 0x4522e0e6},
{"ZAESIMC Z11.B, Z11.B", 0x4520e40b},
{"ZAESMC Z11.B, Z11.B", 0x4520e00b},
// The terminators and counters.
{"CTERMEQ ZR, R25", 0x25ff2320},
{"CTERMEQW ZR, R25", 0x25bf2320},
{"CTERMNE ZR, R25", 0x25ff2330},
{"CTERMNEW ZR, R25", 0x25bf2330},
{"CTERMEQ R0, R25", 0x25e02320},
{"PCNTP P2.B, P14, R2", 0x2520b842},
{"PFIRSTP P2.B, P14, R2", 0x2521b842},
{"PLASTP P2.B, P14, R2", 0x2522b842},
{"PDECP P14.S, ZR", 0x25ad89df},
{"PINCP P14.S, ZR", 0x25ac89df},
{"PSQDECP P14.S, ZR", 0x25aa8ddf},
{"PSQINCP P14.S, ZR", 0x25a88ddf},
{"PUQDECP P14.S, ZR", 0x25ab8ddf},
{"PUQINCP P14.S, ZR", 0x25a98ddf},
{"PUQDECPW P14.S, ZR", 0x25ab89df},
{"PUQINCPW P14.S, ZR", 0x25a989df},
{"PSQDECPW R8, P10.D, R8", 0x25ea8948},
{"PSQINCPW R8, P10.D, R8", 0x25e88948},
{"PWHILEGEW R2, R10, P10.H", 0x2562014a},
{"PWHILEGTW R2, R10, P10.H", 0x2562015a},
{"PWHILEHIW R2, R10, P10.H", 0x2562095a},
{"PWHILEHSW R2, R10, P10.H", 0x2562094a},
{"PWHILELEW R2, R10, P10.H", 0x2562055a},
{"PWHILELOW R2, R10, P10.H", 0x25620d4a},
{"PWHILELSW R2, R10, P10.H", 0x25620d5a},
{"PWHILELTW R2, R10, P10.H", 0x2562054a},
// The reductions.
{"ZSADDVD Z6.B, P3, V2", 0x04002cc2},
{"ZUADDVD Z10.D, P3, V15", 0x04c12d4f},
{"ZANDVB Z6.B, P3, V2", 0x041a2cc2},
{"ZANDVH Z3.H, P1, V29", 0x045a247d},
{"ZANDVS Z17.S, P1, V27", 0x049a263b},
{"ZANDVD Z10.D, P3, V15", 0x04da2d4f},
{"ZEORVB Z6.B, P3, V2", 0x04192cc2},
{"ZEORVD Z10.D, P3, V15", 0x04d92d4f},
{"ZORVB Z6.B, P3, V2", 0x04182cc2},
{"ZORVD Z10.D, P3, V15", 0x04d82d4f},
{"ZSMAXVB Z6.B, P3, V2", 0x04082cc2},
{"ZSMAXVH Z3.H, P1, V29", 0x0448247d},
{"ZSMAXVS Z17.S, P1, V27", 0x0488263b},
{"ZSMAXVD Z10.D, P3, V15", 0x04c82d4f},
{"ZSMINVB Z6.B, P3, V2", 0x040a2cc2},
{"ZSMINVD Z10.D, P3, V15", 0x04ca2d4f},
{"ZUMAXVB Z6.B, P3, V2", 0x04092cc2},
{"ZUMAXVD Z10.D, P3, V15", 0x04c92d4f},
{"ZUMINVB Z6.B, P3, V2", 0x040b2cc2},
{"ZUMINVD Z10.D, P3, V15", 0x04cb2d4f},
{"ZFADDVH Z3.H, P1, V29", 0x6540247d},
{"ZFADDVS Z17.S, P1, V27", 0x6580263b},
{"ZFADDVD Z10.D, P3, V15", 0x65c02d4f},
{"ZFMAXNMVH Z3.H, P1, V29", 0x6544247d},
{"ZFMAXNMVD Z10.D, P3, V15", 0x65c42d4f},
{"ZFMAXVH Z3.H, P1, V29", 0x6546247d},
{"ZFMAXVD Z10.D, P3, V15", 0x65c62d4f},
{"ZFMINNMVH Z3.H, P1, V29", 0x6545247d},
{"ZFMINNMVD Z10.D, P3, V15", 0x65c52d4f},
{"ZFMINVH Z3.H, P1, V29", 0x6547247d},
{"ZFMINVD Z10.D, P3, V15", 0x65c72d4f},
{"ZFADDAH Z8.H, V15, P2, V15", 0x6558290f},
{"ZFADDAS Z26.S, V30, P7, V30", 0x65983f5e},
{"ZFADDAD Z9.D, V10, P2, V10", 0x65d8292a},
}
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])
}
}
}
// TestArm64AssembleExtensionStage3Refusals pins the diagnostics the
// stage-three statements get from the layer: the read-back mismatches, the
// locked arrangements, the dropped-result zero register and the narrow
// governing predicates.
func TestArm64AssembleExtensionStage3Refusals(t *testing.T) {
tests := []struct {
stmt string
want string
}{
{"ZSM4E Z7.S, Z6.S, Z5.S", "the same register Zd"},
{"ZSM4E Z7.D, Z6.D, Z6.D", "want .S"},
{"ZAESIMC Z11.B, Z12.B", "the same register Zd"},
{"ZADCLB Z7.S, Z23.S, Z13.S", "want .D"},
{"CTERMEQ RSP, R25", "wants ZR here"},
{"CTERMEQ R31, R25", "outside R0-R30"},
{"PCNTP P2.B, P14, R31", "outside R0-R30"},
{"PCNTP P2.Q, P14, R2", "no size encoding"},
{"PDECP P14.S, R0", "spelled ZR"},
{"PDECP P14.Q, ZR", "no size encoding"},
{"PSQDECPW R8, P10.D, R9", "the same register"},
{"ZSADDVD Z6.B, P9, V2", "outside P0-P7"},
{"ZSADDVD Z6.B, P3/M, V2", "takes no qualifier"},
{"ZSADDVD Z6.B, P3, V32", "outside V0-V31"},
{"ZSADDVD Z6.B, P3, R2", "wants a SIMD register"},
{"ZANDVB Z6.S, P3, V2", "want .B"},
{"ZFADDAD Z9.D, V10, P2, V11", "the same register Vd"},
{"PWHILEGEW R2, R10, P10.H", ""},
}
for _, tt := range tests {
if tt.want == "" {
continue
}
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)
}
}
}
+89 -2
View File
@@ -273,12 +273,99 @@ func TestExtensionNamesARM64(t *testing.T) {
"ZZIP2",
"ZZIPQ1",
"ZZIPQ2",
// 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.
"ZADCLB",
"ZADCLT",
"ZSBCLB",
"ZSBCLT",
"ZRAX1",
"ZSM4EKEY",
"ZSM4E",
"ZAESD",
"ZAESE",
"ZAESIMC",
"ZAESMC",
"CTERMEQ",
"CTERMEQW",
"CTERMNE",
"CTERMNEW",
"PCNTP",
"PFIRSTP",
"PLASTP",
"PDECP",
"PINCP",
"PSQDECP",
"PSQINCP",
"PUQDECP",
"PUQINCP",
"PUQDECPW",
"PUQINCPW",
"PSQDECPW",
"PSQINCPW",
"PWHILEGEW",
"PWHILEGTW",
"PWHILEHIW",
"PWHILEHSW",
"PWHILELEW",
"PWHILELOW",
"PWHILELSW",
"PWHILELTW",
"ZSADDVD",
"ZUADDVD",
"ZANDVB",
"ZANDVH",
"ZANDVS",
"ZANDVD",
"ZEORVB",
"ZEORVH",
"ZEORVS",
"ZEORVD",
"ZORVB",
"ZORVH",
"ZORVS",
"ZORVD",
"ZSMAXVB",
"ZSMAXVH",
"ZSMAXVS",
"ZSMAXVD",
"ZSMINVB",
"ZSMINVH",
"ZSMINVS",
"ZSMINVD",
"ZUMAXVB",
"ZUMAXVH",
"ZUMAXVS",
"ZUMAXVD",
"ZUMINVB",
"ZUMINVH",
"ZUMINVS",
"ZUMINVD",
"ZFADDVH",
"ZFADDVS",
"ZFADDVD",
"ZFMAXNMVH",
"ZFMAXNMVS",
"ZFMAXNMVD",
"ZFMAXVH",
"ZFMAXVS",
"ZFMAXVD",
"ZFMINNMVH",
"ZFMINNMVS",
"ZFMINNMVD",
"ZFMINVH",
"ZFMINVS",
"ZFMINVD",
"ZFADDAH",
"ZFADDAS",
"ZFADDAD",
}
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 != 167 {
t.Errorf("the family registers %d instructions, want 164", n)
if n := len(arch.Extensions(arch.ARM64)); n != 251 {
t.Errorf("the family registers %d instructions, want 251", n)
}
}