diff --git a/arch/arm64_ext.go b/arch/arm64_ext.go index 6ca42f8..89bef17 100644 --- a/arch/arm64_ext.go +++ b/arch/arm64_ext.go @@ -41,6 +41,10 @@ const ( ExtGReg // general register R0-R30 ExtPNReg // predicate-as-counter register PN8-PN15 ExtVReg // 128-bit SIMD register V0-V31, the scalar lanes of Z0-Z31 + // ExtSveMem is the arm64 gather/scatter memory operand, the parenthesised + // register pair or immediate-offset base the SVE loads and stores address + // through: (R6)(R14), (Z23.D<<1)(R24), (Z4.S.UXTW)(R3), 6(Z7.S). + ExtSveMem ) // String returns a short label for the kind. @@ -72,6 +76,8 @@ func (k ExtOperandKind) String() string { return "predicate-as-counter register" case ExtVReg: return "SIMD register" + case ExtSveMem: + return "SVE memory operand" default: return "operand" } @@ -213,6 +219,16 @@ type ExtOperand struct { // of this layer accept one spelling or the other, never a plain R31, so // the marker carries the distinction into the diagnostics. Reg31 uint8 // 0 = a register 0..30, 1 = ZR, 2 = RSP + // The arm64 gather/scatter memory operand (Kind ExtSveMem) carries its + // addressing in these fields beside Reg, Arr, Imm, Shift and Reg31: the + // first parenthesised register in Reg (general or scalable vector, told + // apart by BaseVec, its element size in Arr), the optional UXTW or SXTW + // extension in Extend, the < 30 { + return 0, fmt.Errorf("%s: operand %d's base is R%d, outside R0-R30", in.Name, position, op.Reg) + } + word = extSet(word, extFieldRm, uint32(op.Reg)) + word, err := in.sveMemOff(word, op, position) + if err != nil { + return 0, err + } + return word, nil + case extMemVecOff, extMemExt32: + if !op.BaseVec { + return 0, fmt.Errorf("%s: operand %d wants a vector base, (Zn.%s{<<%d})(Rm)", + in.Name, position, in.SveBase, in.SveShift) + } + if op.Arr != in.SveBase { + return 0, fmt.Errorf("%s: operand %d's base carries arrangement %s, want %s", + in.Name, position, op.Arr, in.SveBase) + } + if in.SveMode == extMemExt32 && op.Extend != 1 { + return 0, fmt.Errorf("%s: operand %d wants the UXTW extension on the base", in.Name, position) + } + if in.SveMode == extMemVecOff && op.Extend != 0 { + return 0, fmt.Errorf("%s: operand %d takes no extension on the base", in.Name, position) + } + if op.Shift != in.SveShift { + return 0, fmt.Errorf("%s: operand %d wants the base scaled by <<%d, got <<%d", + in.Name, position, in.SveShift, op.Shift) + } + if op.Reg < 0 || op.Reg > 31 { + return 0, fmt.Errorf("%s: operand %d's base is Z%d, outside Z0-Z31", in.Name, position, op.Reg) + } + word = extSet(word, extFieldRm, uint32(op.Reg)) + word, err := in.sveMemOff(word, op, position) + if err != nil { + return 0, err + } + return word, nil + case extMemImm: + if !op.BaseVec || op.Extend != 0 || op.Shift != 0 || op.Off != -1 { + return 0, fmt.Errorf("%s: operand %d wants an immediate offset on a vector base, imm(Zn.%s)", + in.Name, position, in.SveBase) + } + if op.Arr != in.SveBase { + return 0, fmt.Errorf("%s: operand %d's base carries arrangement %s, want %s", + in.Name, position, op.Arr, in.SveBase) + } + if op.Reg < 0 || op.Reg > 31 { + return 0, fmt.Errorf("%s: operand %d's base is Z%d, outside Z0-Z31", in.Name, position, op.Reg) + } + esize := int64(1) << uint(in.SveShift) + if op.Imm < 0 || op.Imm%esize != 0 || op.Imm/esize > 31 { + return 0, fmt.Errorf("%s: operand %d's displacement %d is not a multiple of %d up to %d", + in.Name, position, op.Imm, esize, 31*esize) + } + word = extSet(word, extFieldRm, uint32(op.Imm/esize)) + word = extSet(word, extFieldRn, uint32(op.Reg)) + return word, nil + case extMemVecBase: + if !op.BaseVec || op.Extend != 0 || op.Shift != 0 || op.Off != -1 { + return 0, fmt.Errorf("%s: operand %d wants the vector base alone, (Zn.%s)", + in.Name, position, in.SveBase) + } + if op.Arr != in.SveBase { + return 0, fmt.Errorf("%s: operand %d's base carries arrangement %s, want %s", + in.Name, position, op.Arr, in.SveBase) + } + if op.Reg < 0 || op.Reg > 31 { + return 0, fmt.Errorf("%s: operand %d's base is Z%d, outside Z0-Z31", in.Name, position, op.Reg) + } + return extSet(word, extFieldRn, uint32(op.Reg)), nil + default: + return 0, fmt.Errorf("%s: the row names no addressing mode", in.Name) + } +} + +// sveMemOff validates the offset register of the parenthesised memory modes +// and fills the first-source slot: a plain R0-R30, or the stack-pointer +// spelling of the thirty-first slot. +func (in ExtInstr) sveMemOff(word uint32, op ExtOperand, position int) (uint32, error) { + if op.Off < 0 { + return 0, fmt.Errorf("%s: operand %d wants a second register in the memory operand", + in.Name, position) + } + if op.Reg31 == 2 && op.Off == 31 { + return extSet(word, extFieldRn, 31), nil + } + if op.Reg31 != 0 || op.Off < 0 || op.Off > 30 { + return 0, fmt.Errorf("%s: operand %d's offset register is outside R0-R30, with RSP allowed", + in.Name, position) + } + return extSet(word, extFieldRn, uint32(op.Off)), nil +} + +// svePredicate validates the governing predicate of a gather or scatter: the +// narrow three-bit field, no arrangement suffix, zeroing on the loads and +// bare on the stores. +func (in ExtInstr) svePredicate(op ExtOperand, position int, zeroing bool) (uint32, error) { + if err := in.predicateOperand(op, position, 0, 7, ExtArrNone); err != nil { + return 0, err + } + if op.Arr != ExtArrNone { + return 0, fmt.Errorf("%s: operand %d carries no arrangement suffix, got %s", + in.Name, position, op.Arr) + } + if zeroing && op.Qual != ExtQualZeroing { + return 0, fmt.Errorf("%s: operand %d wants the zeroing qualifier, got %q", + in.Name, position, op.Qual) + } + if !zeroing && op.Qual != ExtQualNone { + return 0, fmt.Errorf("%s: operand %d takes no qualifier, got %q", + in.Name, position, op.Qual) + } + return uint32(op.Reg), nil +} + +// sveList validates the destination or source vector of a gather or scatter +// against the row's locked list arrangement. +func (in ExtInstr) sveList(op ExtOperand, position int) (uint32, error) { + if op.Kind != ExtZReg { + return 0, fmt.Errorf("%s: operand %d wants a scalable vector register, got %s", + in.Name, position, op.Kind) + } + if op.Reg < 0 || op.Reg > 31 { + return 0, fmt.Errorf("%s: operand %d is Z%d, outside Z0-Z31", in.Name, position, op.Reg) + } + if op.Arr != in.Arr { + return 0, fmt.Errorf("%s: operand %d carries arrangement %s, want %s", + in.Name, position, op.Arr, in.Arr) + } + return uint32(op.Reg), nil +} + +// encodeSveLoad fills the gather load: mem, Pg/Z, Zt.T. The addressing mode +// is the row's own, its fixed word carrying the mode and list-size bits, and +// the operands fill the register slots the mode names. +func (in ExtInstr) encodeSveLoad(ops []ExtOperand) ([]byte, error) { + word, err := in.sveMemFill(in.Word, ops[0], 1) + if err != nil { + return nil, err + } + pg, err := in.svePredicate(ops[1], 2, true) + if err != nil { + return nil, err + } + zt, err := in.sveList(ops[2], 3) + if err != nil { + return nil, err + } + word = extSet(word, extFieldPgN, pg) + word = extSet(word, extFieldRd, zt) + return extWordLE(word), nil +} + +// encodeSveStore fills the scatter store: Zt.T, Pg, mem, mirroring the load +// with the list first and a bare governing predicate. +func (in ExtInstr) encodeSveStore(ops []ExtOperand) ([]byte, error) { + zt, err := in.sveList(ops[0], 1) + if err != nil { + return nil, err + } + pg, err := in.svePredicate(ops[1], 2, false) + if err != nil { + return nil, err + } + word, err := in.sveMemFill(in.Word, ops[2], 3) + if err != nil { + return nil, err + } + word = extSet(word, extFieldPgN, pg) + word = extSet(word, extFieldRd, zt) + 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. @@ -2983,6 +3277,540 @@ var arm64Extensions = []ExtInstr{ {Name: "ZFADDAD", Summary: "Strict floating-point accumulation of the active doubleword elements", Word: 0x65d82000, Form: ExtFormReduceReadBack, Arr: ExtArrD, Feature: ExtFeatureSVE, Ref: "ARM DDI 0487J: FADDA as ZFADDAD; inst_gen.go + arm64sveenc.s"}, + + // --- the SVE gather loads and scatter stores -------------------------------- + // + // The core addressing modes of the gather/scatter family over the plain, + // sign-extended and first-fault loads and the stores: the two-general- + // register pair (Rn{< 0 && i == strings.LastIndexByte(text, '(') { + disp, err := strconv.ParseUint(text[:i], 10, 32) + if err == nil && strings.HasSuffix(text, ")") { + op, ok := arm64ExtSveMemGroup(text[i+1 : len(text)-1]) + if !ok { + return arch.ExtOperand{}, false + } + if !op.BaseVec || op.Extend != 0 || op.Shift != 0 { + return arch.ExtOperand{}, false + } + op.Imm = int64(disp) + return op, true + } + } + // The parenthesised forms: one group or two. + rest, ok := strings.CutPrefix(text, "(") + if !ok || !strings.HasSuffix(text, ")") { + return arch.ExtOperand{}, false + } + rest = rest[:len(rest)-1] + first := rest + op := arch.ExtOperand{Off: -1} + if base, second, found := strings.Cut(rest, ")("); found { + first = base + off, ok := arm64ExtSveMemOffset(second) + if !ok { + return arch.ExtOperand{}, false + } + op = off + } + group, ok := arm64ExtSveMemGroup(first) + if !ok { + return arch.ExtOperand{}, false + } + group.Off = op.Off + group.Reg31 = op.Reg31 + return group, true +} + +// arm64ExtSveMemGroup parses one parenthesised memory register: R6, R6<<3, +// Z23.D, Z23.D<<1, Z4.S.UXTW or Z7.D.SXTW. The general registers run +// R0-R30 and the scalable vectors Z0-Z31 with an .S or .D element size and +// an optional UXTW or SXTW extension; the ranges are left to the encoding. +func arm64ExtSveMemGroup(text string) (arch.ExtOperand, bool) { + op := arch.ExtOperand{Kind: arch.ExtSveMem, Off: -1} + if base, shift, found := strings.Cut(text, "<<"); found { + n, err := strconv.Atoi(shift) + if err != nil || n < 0 { + return arch.ExtOperand{}, false + } + op.Shift = n + text = base + } + parts := strings.Split(text, ".") + switch parts[0][0] { + case 'R': + reg, err := strconv.Atoi(parts[0][1:]) + if err != nil || len(parts) != 1 { + return arch.ExtOperand{}, false + } + op.Reg = reg + case 'Z': + reg, err := strconv.Atoi(parts[0][1:]) + if err != nil || len(parts) < 2 || len(parts) > 3 { + return arch.ExtOperand{}, false + } + switch parts[1] { + case "S": + op.Arr = arch.ExtArrS + case "D": + op.Arr = arch.ExtArrD + default: + return arch.ExtOperand{}, false + } + op.Reg = reg + op.BaseVec = true + if len(parts) == 3 { + switch parts[2] { + case "UXTW": + op.Extend = 1 + case "SXTW": + op.Extend = 2 + default: + return arch.ExtOperand{}, false + } + } + default: + return arch.ExtOperand{}, false + } + return op, true +} + +// arm64ExtSveMemOffset parses the second parenthesis of a gather/scatter +// memory operand: a plain R0-R30 or the stack-pointer spelling RSP. +func arm64ExtSveMemOffset(text string) (arch.ExtOperand, bool) { + if text == "RSP" { + return arch.ExtOperand{Off: 31, Reg31: 2}, true + } + if len(text) < 2 || text[0] != 'R' { + return arch.ExtOperand{}, false + } + reg, err := strconv.Atoi(text[1:]) + if err != nil { + return arch.ExtOperand{}, false + } + return arch.ExtOperand{Off: 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) { diff --git a/asm/arm64_ext_asm_test.go b/asm/arm64_ext_asm_test.go index bf6a874..4f4ddf2 100644 --- a/asm/arm64_ext_asm_test.go +++ b/asm/arm64_ext_asm_test.go @@ -562,3 +562,212 @@ func TestArm64AssembleExtensionStage3Refusals(t *testing.T) { } } } + +// TestArm64AssembleExtensionGatherGolden drives the gather/scatter family +// through the full assembler: every corpus line of the scope the layer +// covers, corpus text in, corpus word out. The scope is the plain, +// sign-extended and first-fault loads and the stores over the core +// addressing modes: the two-general-register pair, the vector base with a +// general offset, the extended 32-bit offsets, the immediate offset on a +// vector base and the lone vector base of the stores. +func TestArm64AssembleExtensionGatherGolden(t *testing.T) { + tests := []struct { + stmt string + want uint32 + }{ + {"ZLD1B (R6)(R14), P4.Z, [Z13.B]", 0xa40651cd}, + {"ZLD1B (R6)(R14), P4.Z, [Z13.H]", 0xa42651cd}, + {"ZLD1B (R6)(R14), P4.Z, [Z13.S]", 0xa44651cd}, + {"ZLD1B (R6)(R14), P4.Z, [Z13.D]", 0xa46651cd}, + {"ZLD1B (Z10.D)(R19), P3.Z, [Z15.D]", 0xc44ace6f}, + {"ZLD1B (Z4.S.UXTW)(R3), P3.Z, [Z4.S]", 0x84044c64}, + {"ZLD1B (Z6.D)(R14), P4.Z, [Z13.D]", 0xc446d1cd}, + {"ZLD1D (R6<<3)(R14), P4.Z, [Z13.D]", 0xa5e651cd}, + {"ZLD1D (R6<<3)(R14), P4.Z, [Z13.Q]", 0xa58691cd}, + {"ZLD1D (Z23.D<<3)(R24), P1.Z, [Z22.D]", 0xc5f7c716}, + {"ZLD1D (Z10.D)(R19), P3.Z, [Z15.D]", 0xc5cace6f}, + {"ZLD1D (Z6.D<<3)(R14), P4.Z, [Z13.D]", 0xc5e6d1cd}, + {"ZLD1D (Z6.D)(R14), P4.Z, [Z13.D]", 0xc5c6d1cd}, + {"ZLD1H (R6<<1)(R14), P4.Z, [Z13.H]", 0xa4a651cd}, + {"ZLD1H (R6<<1)(R14), P4.Z, [Z13.S]", 0xa4c651cd}, + {"ZLD1H (R6<<1)(R14), P4.Z, [Z13.D]", 0xa4e651cd}, + {"ZLD1H (Z4.S.UXTW<<1)(R3), P3.Z, [Z4.S]", 0x84a44c64}, + {"ZLD1H (Z23.D<<1)(R24), P1.Z, [Z22.D]", 0xc4f7c716}, + {"ZLD1H (Z10.D)(R19), P3.Z, [Z15.D]", 0xc4cace6f}, + {"ZLD1H (Z4.S.UXTW)(R3), P3.Z, [Z4.S]", 0x84844c64}, + {"ZLD1H (Z6.D<<1)(R14), P4.Z, [Z13.D]", 0xc4e6d1cd}, + {"ZLD1H (Z6.D)(R14), P4.Z, [Z13.D]", 0xc4c6d1cd}, + {"ZLD1SB (R6)(R14), P4.Z, [Z13.H]", 0xa5c651cd}, + {"ZLD1SB (R6)(R14), P4.Z, [Z13.S]", 0xa5a651cd}, + {"ZLD1SB (R6)(R14), P4.Z, [Z13.D]", 0xa58651cd}, + {"ZLD1SB (Z10.D)(R19), P3.Z, [Z15.D]", 0xc44a8e6f}, + {"ZLD1SB (Z4.S.UXTW)(R3), P3.Z, [Z4.S]", 0x84040c64}, + {"ZLD1SB (Z6.D)(R14), P4.Z, [Z13.D]", 0xc44691cd}, + {"ZLD1SH (R6<<1)(R14), P4.Z, [Z13.S]", 0xa52651cd}, + {"ZLD1SH (R6<<1)(R14), P4.Z, [Z13.D]", 0xa50651cd}, + {"ZLD1SH (Z4.S.UXTW<<1)(R3), P3.Z, [Z4.S]", 0x84a40c64}, + {"ZLD1SH (Z23.D<<1)(R24), P1.Z, [Z22.D]", 0xc4f78716}, + {"ZLD1SH (Z10.D)(R19), P3.Z, [Z15.D]", 0xc4ca8e6f}, + {"ZLD1SH (Z4.S.UXTW)(R3), P3.Z, [Z4.S]", 0x84840c64}, + {"ZLD1SH (Z6.D<<1)(R14), P4.Z, [Z13.D]", 0xc4e691cd}, + {"ZLD1SH (Z6.D)(R14), P4.Z, [Z13.D]", 0xc4c691cd}, + {"ZLD1SW (R6<<2)(R14), P4.Z, [Z13.D]", 0xa48651cd}, + {"ZLD1SW (Z23.D<<2)(R24), P1.Z, [Z22.D]", 0xc5778716}, + {"ZLD1SW (Z10.D)(R19), P3.Z, [Z15.D]", 0xc54a8e6f}, + {"ZLD1SW (Z6.D<<2)(R14), P4.Z, [Z13.D]", 0xc56691cd}, + {"ZLD1SW (Z6.D)(R14), P4.Z, [Z13.D]", 0xc54691cd}, + {"ZLD1W (R6<<2)(R14), P4.Z, [Z13.S]", 0xa54651cd}, + {"ZLD1W (R6<<2)(R14), P4.Z, [Z13.D]", 0xa56651cd}, + {"ZLD1W (R6<<2)(R14), P4.Z, [Z13.Q]", 0xa50691cd}, + {"ZLD1W (Z4.S.UXTW<<2)(R3), P3.Z, [Z4.S]", 0x85244c64}, + {"ZLD1W (Z23.D<<2)(R24), P1.Z, [Z22.D]", 0xc577c716}, + {"ZLD1W (Z10.D)(R19), P3.Z, [Z15.D]", 0xc54ace6f}, + {"ZLD1W (Z4.S.UXTW)(R3), P3.Z, [Z4.S]", 0x85044c64}, + {"ZLD1W (Z6.D<<2)(R14), P4.Z, [Z13.D]", 0xc566d1cd}, + {"ZLD1W (Z6.D)(R14), P4.Z, [Z13.D]", 0xc546d1cd}, + {"ZLDFF1B (R6)(R14), P4.Z, [Z13.B]", 0xa40671cd}, + {"ZLDFF1B (R6)(R14), P4.Z, [Z13.H]", 0xa42671cd}, + {"ZLDFF1B (R6)(R14), P4.Z, [Z13.S]", 0xa44671cd}, + {"ZLDFF1B (R6)(R14), P4.Z, [Z13.D]", 0xa46671cd}, + {"ZLDFF1B (Z10.D)(R19), P3.Z, [Z15.D]", 0xc44aee6f}, + {"ZLDFF1B (Z4.S.UXTW)(R3), P3.Z, [Z4.S]", 0x84046c64}, + {"ZLDFF1B (Z6.D)(R14), P4.Z, [Z13.D]", 0xc446f1cd}, + {"ZLDFF1D (R6<<3)(R14), P4.Z, [Z13.D]", 0xa5e671cd}, + {"ZLDFF1D (Z23.D<<3)(R24), P1.Z, [Z22.D]", 0xc5f7e716}, + {"ZLDFF1D (Z10.D)(R19), P3.Z, [Z15.D]", 0xc5caee6f}, + {"ZLDFF1D (Z6.D<<3)(R14), P4.Z, [Z13.D]", 0xc5e6f1cd}, + {"ZLDFF1D (Z6.D)(R14), P4.Z, [Z13.D]", 0xc5c6f1cd}, + {"ZLDFF1H (R6<<1)(R14), P4.Z, [Z13.H]", 0xa4a671cd}, + {"ZLDFF1H (R6<<1)(R14), P4.Z, [Z13.S]", 0xa4c671cd}, + {"ZLDFF1H (R6<<1)(R14), P4.Z, [Z13.D]", 0xa4e671cd}, + {"ZLDFF1H (Z4.S.UXTW<<1)(R3), P3.Z, [Z4.S]", 0x84a46c64}, + {"ZLDFF1H (Z23.D<<1)(R24), P1.Z, [Z22.D]", 0xc4f7e716}, + {"ZLDFF1H (Z10.D)(R19), P3.Z, [Z15.D]", 0xc4caee6f}, + {"ZLDFF1H (Z4.S.UXTW)(R3), P3.Z, [Z4.S]", 0x84846c64}, + {"ZLDFF1H (Z6.D<<1)(R14), P4.Z, [Z13.D]", 0xc4e6f1cd}, + {"ZLDFF1H (Z6.D)(R14), P4.Z, [Z13.D]", 0xc4c6f1cd}, + {"ZLDFF1SB (R6)(R14), P4.Z, [Z13.H]", 0xa5c671cd}, + {"ZLDFF1SB (R6)(R14), P4.Z, [Z13.S]", 0xa5a671cd}, + {"ZLDFF1SB (R6)(R14), P4.Z, [Z13.D]", 0xa58671cd}, + {"ZLDFF1SB (Z10.D)(R19), P3.Z, [Z15.D]", 0xc44aae6f}, + {"ZLDFF1SB (Z4.S.UXTW)(R3), P3.Z, [Z4.S]", 0x84042c64}, + {"ZLDFF1SB (Z6.D)(R14), P4.Z, [Z13.D]", 0xc446b1cd}, + {"ZLDFF1SH (R6<<1)(R14), P4.Z, [Z13.S]", 0xa52671cd}, + {"ZLDFF1SH (R6<<1)(R14), P4.Z, [Z13.D]", 0xa50671cd}, + {"ZLDFF1SH (Z4.S.UXTW<<1)(R3), P3.Z, [Z4.S]", 0x84a42c64}, + {"ZLDFF1SH (Z23.D<<1)(R24), P1.Z, [Z22.D]", 0xc4f7a716}, + {"ZLDFF1SH (Z10.D)(R19), P3.Z, [Z15.D]", 0xc4caae6f}, + {"ZLDFF1SH (Z4.S.UXTW)(R3), P3.Z, [Z4.S]", 0x84842c64}, + {"ZLDFF1SH (Z6.D<<1)(R14), P4.Z, [Z13.D]", 0xc4e6b1cd}, + {"ZLDFF1SH (Z6.D)(R14), P4.Z, [Z13.D]", 0xc4c6b1cd}, + {"ZLDFF1SW (R6<<2)(R14), P4.Z, [Z13.D]", 0xa48671cd}, + {"ZLDFF1SW (Z23.D<<2)(R24), P1.Z, [Z22.D]", 0xc577a716}, + {"ZLDFF1SW (Z10.D)(R19), P3.Z, [Z15.D]", 0xc54aae6f}, + {"ZLDFF1SW (Z6.D<<2)(R14), P4.Z, [Z13.D]", 0xc566b1cd}, + {"ZLDFF1SW (Z6.D)(R14), P4.Z, [Z13.D]", 0xc546b1cd}, + {"ZLDFF1W (R6<<2)(R14), P4.Z, [Z13.S]", 0xa54671cd}, + {"ZLDFF1W (R6<<2)(R14), P4.Z, [Z13.D]", 0xa56671cd}, + {"ZLDFF1W (Z4.S.UXTW<<2)(R3), P3.Z, [Z4.S]", 0x85246c64}, + {"ZLDFF1W (Z23.D<<2)(R24), P1.Z, [Z22.D]", 0xc577e716}, + {"ZLDFF1W (Z10.D)(R19), P3.Z, [Z15.D]", 0xc54aee6f}, + {"ZLDFF1W (Z4.S.UXTW)(R3), P3.Z, [Z4.S]", 0x85046c64}, + {"ZLDFF1W (Z6.D<<2)(R14), P4.Z, [Z13.D]", 0xc566f1cd}, + {"ZLDFF1W (Z6.D)(R14), P4.Z, [Z13.D]", 0xc546f1cd}, + {"ZST1B [Z7.D], P4, (R21)(R7)", 0xe47550e7}, + {"ZST1B [Z6.D], P4, (Z21.D.UXTW)(R7)", 0xe41590e6}, + {"ZST1B [Z6.S], P4, (Z21.S.UXTW)(R7)", 0xe45590e6}, + {"ZST1B [Z8.D], P3, (Z6.D)(RSP)", 0xe406afe8}, + {"ZST1D [Z8.D], P3, (R6<<3)(RSP)", 0xe5e64fe8}, + {"ZST1D [Z8.Q], P3, (R6<<3)(RSP)", 0xe5c64fe8}, + {"ZST1D [Z6.D], P4, (Z21.D.UXTW<<3)(R7)", 0xe5b590e6}, + {"ZST1D [Z6.D], P4, (Z21.D.UXTW)(R7)", 0xe59590e6}, + {"ZST1D [Z8.D], P3, (Z6.D<<3)(RSP)", 0xe5a6afe8}, + {"ZST1D [Z8.D], P3, (Z6.D)(RSP)", 0xe586afe8}, + {"ZST1H [Z7.D], P4, (R21<<1)(R7)", 0xe4f550e7}, + {"ZST1H [Z6.S], P4, (Z21.S.UXTW<<1)(R7)", 0xe4f590e6}, + {"ZST1H [Z6.D], P4, (Z21.D.UXTW<<1)(R7)", 0xe4b590e6}, + {"ZST1H [Z6.D], P4, (Z21.D.UXTW)(R7)", 0xe49590e6}, + {"ZST1H [Z6.S], P4, (Z21.S.UXTW)(R7)", 0xe4d590e6}, + {"ZST1H [Z8.D], P3, (Z6.D<<1)(RSP)", 0xe4a6afe8}, + {"ZST1H [Z8.D], P3, (Z6.D)(RSP)", 0xe486afe8}, + {"ZST1W [Z7.D], P4, (R21<<2)(R7)", 0xe57550e7}, + {"ZST1W [Z8.Q], P3, (R6<<2)(RSP)", 0xe5064fe8}, + {"ZST1W [Z6.S], P4, (Z21.S.UXTW<<2)(R7)", 0xe57590e6}, + {"ZST1W [Z6.D], P4, (Z21.D.UXTW<<2)(R7)", 0xe53590e6}, + {"ZST1W [Z6.D], P4, (Z21.D.UXTW)(R7)", 0xe51590e6}, + {"ZST1W [Z6.S], P4, (Z21.S.UXTW)(R7)", 0xe55590e6}, + {"ZST1W [Z8.D], P3, (Z6.D<<2)(RSP)", 0xe526afe8}, + {"ZST1W [Z8.D], P3, (Z6.D)(RSP)", 0xe506afe8}, + {"ZLD1B 6(Z7.S), P4.Z, [Z13.S]", 0x8426d0ed}, + {"ZLD1B 6(Z7.D), P4.Z, [Z13.D]", 0xc426d0ed}, + {"ZLD1D 8(Z14.D), P6.Z, [Z6.D]", 0xc5a1d9c6}, + {"ZLD1H 6(Z7.S), P4.Z, [Z13.S]", 0x84a3d0ed}, + {"ZLD1H 6(Z7.D), P4.Z, [Z13.D]", 0xc4a3d0ed}, + {"ZLD1SB 6(Z7.S), P4.Z, [Z13.S]", 0x842690ed}, + {"ZLD1SB 6(Z7.D), P4.Z, [Z13.D]", 0xc42690ed}, + {"ZLD1SH 6(Z7.S), P4.Z, [Z13.S]", 0x84a390ed}, + {"ZLD1SH 6(Z7.D), P4.Z, [Z13.D]", 0xc4a390ed}, + {"ZLD1SW 12(Z0.D), P6.Z, [Z25.D]", 0xc5239819}, + {"ZLD1W 12(Z0.S), P6.Z, [Z25.S]", 0x8523d819}, + {"ZLD1W 12(Z0.D), P6.Z, [Z25.D]", 0xc523d819}, + {"ZLDFF1B 6(Z7.S), P4.Z, [Z13.S]", 0x8426f0ed}, + {"ZLDFF1B 6(Z7.D), P4.Z, [Z13.D]", 0xc426f0ed}, + {"ZLDFF1D 8(Z14.D), P6.Z, [Z6.D]", 0xc5a1f9c6}, + {"ZLDFF1H 6(Z7.S), P4.Z, [Z13.S]", 0x84a3f0ed}, + {"ZLDFF1H 6(Z7.D), P4.Z, [Z13.D]", 0xc4a3f0ed}, + {"ZLDFF1SB 6(Z7.S), P4.Z, [Z13.S]", 0x8426b0ed}, + {"ZLDFF1SB 6(Z7.D), P4.Z, [Z13.D]", 0xc426b0ed}, + {"ZLDFF1SH 6(Z7.S), P4.Z, [Z13.S]", 0x84a3b0ed}, + {"ZLDFF1SH 6(Z7.D), P4.Z, [Z13.D]", 0xc4a3b0ed}, + {"ZLDFF1SW 12(Z0.D), P6.Z, [Z25.D]", 0xc523b819}, + {"ZLDFF1W 12(Z0.S), P6.Z, [Z25.S]", 0x8523f819}, + {"ZLDFF1W 12(Z0.D), P6.Z, [Z25.D]", 0xc523f819}, + {"ZST1B [Z8.S], P3, 6(Z15.S)", 0xe466ade8}, + {"ZST1B [Z8.D], P3, 6(Z15.D)", 0xe446ade8}, + {"ZST1D [Z16.D], P2, (Z5.D)", 0xe5c0a8b0}, + {"ZST1H [Z8.S], P3, 6(Z15.S)", 0xe4e3ade8}, + {"ZST1H [Z8.D], P3, 6(Z15.D)", 0xe4c3ade8}, + {"ZST1W [Z16.S], P2, (Z5.S)", 0xe560a8b0}, + {"ZST1W [Z16.D], P2, (Z5.D)", 0xe540a8b0}, + } + 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) + } + } +} + +// 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 +// loads against the bare predicates of the stores, and the immediate +// scaling. +func TestArm64AssembleExtensionGatherRefusals(t *testing.T) { + tests := []struct { + stmt string + want string + }{ + {"ZLD1B (R6<<1)(R14), P4.Z, [Z13.B]", "scaled by <<0"}, + {"ZLD1B (Z6.D)(R14), P4.Z, [Z13.B]", "wants two general registers"}, + {"ZLD1B (Z6.S)(R14), P4.Z, [Z13.S]", "wants two general registers"}, + {"ZLD1B (R6)(R14), P4/M, [Z13.B]", "wants the zeroing qualifier"}, + {"ZLD1B (R6)(R14), P9.Z, [Z13.B]", "outside P0-P7"}, + {"ZLD1B (R6)(ZR), P4.Z, [Z13.B]", "is not an extended-layer operand"}, + {"ZLD1B (RSP)(R14), P4.Z, [Z13.B]", "is not an extended-layer operand"}, + {"ZLD1D 12(Z14.D), P6.Z, [Z6.D]", "wants two general registers"}, + {"ZLD1D 264(Z14.D), P6.Z, [Z6.D]", "wants two general registers"}, + {"ZST1B [Z7.D], P4/Z, (R21)(R7)", "takes no qualifier"}, + {"ZST1B [Z7.S], P4, (R21)(R7)", "carries arrangement .S, want .D"}, + {"ZST1B [Z7.D], P4, (Z21.S)(R7)", "wants two general registers"}, + } + 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 a3973cd..9670e52 100644 --- a/asm/extension_test.go +++ b/asm/extension_test.go @@ -360,12 +360,32 @@ func TestExtensionNamesARM64(t *testing.T) { "ZFADDAH", "ZFADDAS", "ZFADDAD", + // The gather loads and scatter stores: plain, sign-extended and + // first-fault loads, and the stores, in first-occurrence order. + "ZLD1B", + "ZLD1D", + "ZLD1H", + "ZLD1SB", + "ZLD1SH", + "ZLD1SW", + "ZLD1W", + "ZLDFF1B", + "ZLDFF1D", + "ZLDFF1H", + "ZLDFF1SB", + "ZLDFF1SH", + "ZLDFF1SW", + "ZLDFF1W", + "ZST1B", + "ZST1D", + "ZST1H", + "ZST1W", } 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 != 251 { - t.Errorf("the family registers %d instructions, want 251", n) + if n := len(arch.Extensions(arch.ARM64)); n != 381 { + t.Errorf("the family registers %d instructions, want 381", n) } }