fix(asm): tighten the arm64 acceptance toward the toolchain
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
82d741514a
commit
8ab99c9b0c
4 files changed
+250
-29
No files matched your search
+129
-6
@@ -890,13 +890,38 @@ func encodeARM64DPSR(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
if rn < 0 || rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
// SP rules the toolchain enforces on the logical immediates: a
|
||||
// two-operand spelling with SP as the source is rejected outright
|
||||
// (illegal source register), a flag-setting logical rejects SP as
|
||||
// the destination, and a plain logical to SP takes the fast
|
||||
// bitmask path only inside the addcon band (asm7.go cases 62/13).
|
||||
if !zrDest && len(ops) == 2 && rn == 31 {
|
||||
if name := operandRegName(ops[1]); strings.EqualFold(name, "RSP") {
|
||||
return nil, fmt.Errorf("%s: illegal source register RSP", mnem)
|
||||
}
|
||||
}
|
||||
rspDest := false
|
||||
if len(ops) == 3 {
|
||||
rspDest = strings.EqualFold(operandRegName(ops[2]), "RSP")
|
||||
}
|
||||
sLogical := mnem == "ANDS" || mnem == "ANDSW" || mnem == "BICS" || mnem == "BICSW"
|
||||
if rspDest && sLogical {
|
||||
return nil, fmt.Errorf("%s: illegal combination: the destination cannot be RSP", mnem)
|
||||
}
|
||||
n, immr, imms, ok := a64LogicalImm(v, width)
|
||||
// The toolchain's logical-immediate rows take a real destination
|
||||
// The toolchain.s logical-immediate rows take a real destination
|
||||
// only (omovconst guards the bitmask path with rt != REGZERO): a
|
||||
// non-flag-setting logical to ZR materialises the constant into
|
||||
// REGTMP (R27) and takes the register form. RSP is a real
|
||||
// register here, and the flags-only TST spellings keep the fast
|
||||
// path: ANDS ZR, Rn, #imm is their form.
|
||||
// register here, but keeps the fast bitmask path inside the
|
||||
// addcon band alone, and the flags-only TST spellings keep the
|
||||
// fast path: ANDS ZR, Rn, #imm is their form.
|
||||
if rspDest {
|
||||
inBand := v > 0 && (v <= 0xFFF || (v&0xFFF == 0 && v>>12 <= 0xFFF))
|
||||
if !ok || !inBand {
|
||||
return nil, fmt.Errorf("%s: illegal combination: the destination cannot be RSP", mnem)
|
||||
}
|
||||
}
|
||||
if ok && (isCmp || !zrDest) {
|
||||
opc := (baseOp >> 29) & 7
|
||||
sf := (baseOp >> 31) & 1
|
||||
@@ -967,10 +992,15 @@ func encodeARM64DPSR(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
spInvolved = true
|
||||
}
|
||||
}
|
||||
if spInvolved && strings.HasPrefix(mnem, "ADD") || spInvolved && strings.HasPrefix(mnem, "SUB") {
|
||||
if spInvolved && (strings.HasPrefix(mnem, "ADD") || strings.HasPrefix(mnem, "SUB") || isCmp) {
|
||||
if shiftBits != 0 {
|
||||
return nil, fmt.Errorf("%s: right shift not encodable against SP", mnem)
|
||||
}
|
||||
// The extend field carries 0..4 only (asm7.go: shift amount
|
||||
// out of range 0 to 4).
|
||||
if amount > 4 {
|
||||
return nil, fmt.Errorf("%s: shift amount out of range 0 to 4", mnem)
|
||||
}
|
||||
opt := uint32(3) // UXTX
|
||||
if strings.HasSuffix(mnem, "W") {
|
||||
opt = 2 // UXTW
|
||||
@@ -982,6 +1012,13 @@ func encodeARM64DPSR(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
} else {
|
||||
baseOp |= 1<<21 | extendOpt<<13 | uint32(amount)<<10
|
||||
}
|
||||
// The flag-setting add/sub family rejects SP as its destination.
|
||||
switch mnem {
|
||||
case "ADDS", "ADDSW", "SUBS", "SUBSW":
|
||||
if strings.EqualFold(operandRegName(ops[len(ops)-1]), "RSP") {
|
||||
return nil, fmt.Errorf("%s: illegal destination register RSP", mnem)
|
||||
}
|
||||
}
|
||||
rd := arm64RegNum(operandRegName(ops[len(ops)-1]))
|
||||
rn := rd
|
||||
if len(ops) == 3 {
|
||||
@@ -1028,6 +1065,13 @@ func encodeARM64DPSR(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
if rm < 0 || rn < 0 || rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
// The flag-setting add/sub family rejects SP as its destination.
|
||||
switch mnem {
|
||||
case "ADDS", "ADDSW", "SUBS", "SUBSW":
|
||||
if strings.EqualFold(operandRegName(ops[2]), "RSP") {
|
||||
return nil, fmt.Errorf("%s: illegal destination register RSP", mnem)
|
||||
}
|
||||
}
|
||||
// ADD/SUB against SP take the extended-register form with the
|
||||
// identity extend, the toolchain's spelling of a plain register
|
||||
// operand against the stack pointer (asm7.go opxrrr against C_RSP).
|
||||
@@ -1335,6 +1379,14 @@ func encodeARM64AddSubImm(mnem string, ops []*ast.Operand) ([]byte, error) {
|
||||
if rn < 0 || rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
// The flag-setting add/sub family rejects SP as its destination
|
||||
// (asm7.go: illegal destination register).
|
||||
switch mnem {
|
||||
case "ADDS", "ADDSW", "SUBS", "SUBSW":
|
||||
if strings.EqualFold(operandRegName(ops[len(ops)-1]), "RSP") {
|
||||
return nil, fmt.Errorf("%s: illegal destination register RSP", mnem)
|
||||
}
|
||||
}
|
||||
// CMP/CMN discard the destination.
|
||||
if mnem == "CMP" || mnem == "CMPW" || mnem == "CMN" || mnem == "CMNW" {
|
||||
rd = 31 // ZR
|
||||
@@ -2105,6 +2157,15 @@ func encodeARM64MemOp(mnem string, mem *ast.Operand, reg int, load bool, fi arm6
|
||||
if rn < 0 {
|
||||
return nil, fmt.Errorf("invalid memory operand")
|
||||
}
|
||||
// The MOV family addresses memory through a general register only.
|
||||
if mem.Addr.Base != "" && arm64RegClassOf(mem.Addr.Base) == arm64ClsFP {
|
||||
return nil, fmt.Errorf("%s: illegal combination: the base register cannot be FP", mnem)
|
||||
}
|
||||
// Writeback with the base doubling as the data register is constrained
|
||||
// unpredictable.
|
||||
if wb != "" && rn == reg && rn != 31 {
|
||||
return nil, fmt.Errorf("%s: constrained unpredictable behavior: the base rides the data register", mnem)
|
||||
}
|
||||
lt, ok := a64LoadTable[mnem]
|
||||
if !ok {
|
||||
// MOV defaults to MOVD (64-bit load/store).
|
||||
@@ -2968,6 +3029,11 @@ func encodeARM64CSEL(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
mnem == "CNEG" || mnem == "CNEGW"
|
||||
|
||||
if isAlias {
|
||||
// The aliases invert the condition, which is undefined for AL and NV
|
||||
// (asm7.go: invalid condition).
|
||||
if condName := operandRegName(ops[0]); strings.EqualFold(condName, "AL") || strings.EqualFold(condName, "NV") {
|
||||
return nil, fmt.Errorf("%s: invalid condition %s", mnem, condName)
|
||||
}
|
||||
is2op := mnem == "CSET" || mnem == "CSETW" || mnem == "CSETM" || mnem == "CSETMW"
|
||||
if is2op {
|
||||
// CSET cond, Rd → CSEL XZR, XZR, Rd, inverted_cond
|
||||
@@ -3112,6 +3178,14 @@ func encodeARM64Excl(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
// The single-register opcodes pre-set the unused Rs (bits 20:16)
|
||||
// and Rt2 (bits 14:10) fields to 31; the pair forms carry a real
|
||||
// Rt2 and keep Rs at 31.
|
||||
// Constrained unpredictable: the base rides no pair member and
|
||||
// the pair registers differ.
|
||||
if rt1 == rt2 {
|
||||
return nil, fmt.Errorf("%s: constrained unpredictable behavior: the pair registers match", mnem)
|
||||
}
|
||||
if bname := operandRegName(ops[0]); strings.EqualFold(bname, fmt.Sprintf("R%d", rt1)) || strings.EqualFold(bname, fmt.Sprintf("R%d", rt2)) {
|
||||
return nil, fmt.Errorf("%s: constrained unpredictable behavior: the base rides a pair register", mnem)
|
||||
}
|
||||
return a64wordLE(baseOp | 0x1F<<16 | uint32(rt2)<<10 | uint32(rn)<<5 | uint32(rt1)), nil
|
||||
}
|
||||
rt := arm64RegNum(operandRegName(ops[1]))
|
||||
@@ -3128,17 +3202,35 @@ func encodeARM64Excl(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The status register cannot be SP (asm7.go: illegal combination).
|
||||
if strings.EqualFold(operandRegName(ops[2]), "RSP") {
|
||||
return nil, fmt.Errorf("%s: illegal combination: the status register cannot be RSP", mnem)
|
||||
}
|
||||
rs := arm64RegNum(operandRegName(ops[2]))
|
||||
if rs < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
if rt1, rt2, ok := arm64PairOf(ops[0]); ok {
|
||||
// Constrained unpredictable (asm7.go case 59): the pair registers
|
||||
// differ, and the status register differs from both pair members and
|
||||
// from a non-SP base.
|
||||
if rt1 == rt2 {
|
||||
return nil, fmt.Errorf("%s: constrained unpredictable behavior: the pair registers match", mnem)
|
||||
}
|
||||
if rs == rt1 || rs == rt2 || (rs == rn && rn != 31) {
|
||||
return nil, fmt.Errorf("%s: constrained unpredictable behavior: the status register rides a pair register or the base", mnem)
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(rs)<<16 | uint32(rt2)<<10 | uint32(rn)<<5 | uint32(rt1)), nil
|
||||
}
|
||||
// Constrained unpredictable (asm7.go case 59): the status register
|
||||
// differs from Rt and from a non-SP base.
|
||||
rt := arm64RegNum(operandRegName(ops[0]))
|
||||
if rt < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
if rs == rt || (rs == rn && rn != 31) {
|
||||
return nil, fmt.Errorf("%s: constrained unpredictable behavior: the status register matches Rt or the base", mnem)
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(rs)<<16 | uint32(rn)<<5 | uint32(rt)), nil
|
||||
}
|
||||
|
||||
@@ -3157,6 +3249,10 @@ func encodeARM64LSEAtom(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The result register cannot be SP (asm7.go: illegal combination).
|
||||
if strings.EqualFold(operandRegName(ops[2]), "RSP") {
|
||||
return nil, fmt.Errorf("%s: illegal combination: the result register cannot be RSP", mnem)
|
||||
}
|
||||
rt := arm64RegNum(operandRegName(ops[2]))
|
||||
if rt < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
@@ -3681,6 +3777,30 @@ func encodeARM64Pair(mnem string, baseOp uint32, ops []*ast.Operand, pc int, fi
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s expects a register pair (Rt1, Rt2)", mnem)
|
||||
}
|
||||
// Constrained unpredictable: the pair registers differ, and a
|
||||
// writeback base rides no pair member.
|
||||
if rt1 == rt2 {
|
||||
return nil, fmt.Errorf("%s: constrained unpredictable behavior: the pair registers match", mnem)
|
||||
}
|
||||
if wb != "" {
|
||||
if strings.EqualFold(operandRegName(memOp), fmt.Sprintf("R%d", rt1)) || strings.EqualFold(operandRegName(memOp), fmt.Sprintf("R%d", rt2)) {
|
||||
return nil, fmt.Errorf("%s: constrained unpredictable behavior: the base rides a pair register", mnem)
|
||||
}
|
||||
}
|
||||
// The FP pairs take FP registers against a GP base (asm7.go: invalid
|
||||
// register pair).
|
||||
if strings.HasPrefix(mnem, "FLDP") || strings.HasPrefix(mnem, "FSTP") {
|
||||
first := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(pairOp.Raw), "("))
|
||||
if i := strings.IndexAny(first, ",)"); i >= 0 {
|
||||
first = strings.TrimSpace(first[:i])
|
||||
}
|
||||
if !strings.HasPrefix(first, "F") {
|
||||
return nil, fmt.Errorf("%s: invalid register pair %s", mnem, pairOp.Raw)
|
||||
}
|
||||
if strings.HasPrefix(strings.TrimLeft(operandRegName(memOp), "( "), "F") {
|
||||
return nil, fmt.Errorf("%s: invalid register pair: the base must be a general register", mnem)
|
||||
}
|
||||
}
|
||||
|
||||
// Pair access against a static symbol: ADRP R27, sym; ADD R27, R27, #lo;
|
||||
// LDP/STP (R27), (Rt1, Rt2), with the R_ADDRARM64 pair riding the first
|
||||
@@ -4274,7 +4394,7 @@ func arm64SimdHasElement(ops []*ast.Operand) bool {
|
||||
// admits it. It returns the arrangement's index, with a64Arr8B for a bare
|
||||
// V/F spelling.
|
||||
func arm64SimdArrs(mnem string, arrs []string, allowed uint16) (int, error) {
|
||||
sel := a64Arr8B
|
||||
sel := -1
|
||||
for _, a := range arrs {
|
||||
if a == "" {
|
||||
continue
|
||||
@@ -4283,11 +4403,14 @@ func arm64SimdArrs(mnem string, arrs []string, allowed uint16) (int, error) {
|
||||
if i < 0 || specBit(i)&allowed == 0 {
|
||||
return 0, fmt.Errorf("%s: invalid arrangement %q", mnem, a)
|
||||
}
|
||||
if sel != a64Arr8B && sel != i {
|
||||
if sel >= 0 && sel != i {
|
||||
return 0, fmt.Errorf("%s: mixed arrangements", mnem)
|
||||
}
|
||||
sel = i
|
||||
}
|
||||
if sel < 0 {
|
||||
sel = a64Arr8B
|
||||
}
|
||||
return sel, nil
|
||||
}
|
||||
|
||||
|
||||
+19
-23
@@ -565,16 +565,12 @@ func init() {
|
||||
a64InstrTable["BFXILW"] = a64Enc{format: a64FBitfieldAlias, op: 0<<31 | 1<<29 | 0x26<<23}
|
||||
a64InstrTable["SBFIZ"] = a64Enc{format: a64FBitfieldAlias, op: 0x93400000}
|
||||
a64InstrTable["SBFIZW"] = a64Enc{format: a64FBitfieldAlias, op: 0x13000000}
|
||||
a64InstrTable["UBFIZ"] = a64Enc{format: a64FBitfieldAlias, op: 0x53000000}
|
||||
a64InstrTable["UBFIZW"] = a64Enc{format: a64FBitfieldAlias, op: 0x33000000}
|
||||
a64InstrTable["UBFIZ"] = a64Enc{format: a64FBitfieldAlias, op: 0xd3400000}
|
||||
a64InstrTable["UBFIZW"] = a64Enc{format: a64FBitfieldAlias, op: 0x53000000}
|
||||
a64InstrTable["SBFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 0<<29 | 0x26<<23 | 1<<22}
|
||||
a64InstrTable["SBFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 0<<29 | 0x26<<23 | 0<<22}
|
||||
a64InstrTable["UBFM"] = a64Enc{format: a64FBitfield, op: 1<<31 | 2<<29 | 0x26<<23 | 1<<22}
|
||||
a64InstrTable["UBFMW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}
|
||||
a64InstrTable["BFI"] = a64Enc{format: a64FBitfield, op: 1<<31 | 2<<29 | 0x26<<23 | 1<<22}
|
||||
a64InstrTable["BFIW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}
|
||||
a64InstrTable["BFXIL"] = a64Enc{format: a64FBitfield, op: 1<<31 | 1<<29 | 0x26<<23 | 1<<22}
|
||||
a64InstrTable["BFXILW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 1<<29 | 0x26<<23 | 0<<22}
|
||||
|
||||
// ---- FP 3-operand (Rm, Rn, Rd): FADD, FSUB, FMUL, FDIV, FMAX, FMIN, FNMUL ----
|
||||
fp3 := map[string]uint32{
|
||||
@@ -1071,7 +1067,7 @@ func a64ElemLetter(s string) bool {
|
||||
// arrangement"). fpAcrossArrs bounds the across-vector reductions, which do
|
||||
// take the half width.
|
||||
var fpSimdArrs = uint16(1<<a64Arr2S | 1<<a64Arr4S | 1<<a64Arr2D)
|
||||
var fpAcrossArrs = uint16(1<<a64Arr4H | 1<<a64Arr8H | 1<<a64Arr2S | 1<<a64Arr4S)
|
||||
var fpAcrossArrs = uint16(1 << a64Arr4S)
|
||||
|
||||
// a64FPArrBits carries the bits an arrangement contributes to the FP SIMD
|
||||
// words: the FP size field is a single bit at bit 22 (0 for the S widths, 1
|
||||
@@ -1148,31 +1144,31 @@ var a64SimdVTable = map[string]a64SimdVSpec{
|
||||
// Saturating, halving, polynomial and pairwise arithmetic, the logical
|
||||
// VBIT/VBSL family and the FP pairwise forms: word-verified against go
|
||||
// tool asm.
|
||||
"VBIC": {0x0e601c00, 0x7f, false, false},
|
||||
"VBIF": {0x2ee01c00, 0x7f, false, false},
|
||||
"VBIT": {0x6ea01c00, 0x7f, false, false},
|
||||
"VBSL": {0x6e601c00, 0x7f, false, false},
|
||||
"VBIC": {0x0e601c00, 0x03, false, false}, // logical ops accept 8B and 16B only
|
||||
"VBIF": {0x2ee01c00, 0x03, false, false},
|
||||
"VBIT": {0x6ea01c00, 0x03, false, false},
|
||||
"VBSL": {0x6e601c00, 0x03, false, false},
|
||||
"VCMTST": {0x0e208c00, 0x7f, false, false},
|
||||
"VFADDP": {0x2e20d400, fpSimdArrs, false, true},
|
||||
"VFMAXP": {0x2e20f400, fpSimdArrs, false, true},
|
||||
"VFMINP": {0x6ea0f400, fpSimdArrs, false, true},
|
||||
"VFMAXNMP": {0x2e20c400, fpSimdArrs, false, true},
|
||||
"VFMINNMP": {0x6ea0c400, fpSimdArrs, false, true},
|
||||
"VMLA": {0x4ea09400, 0x7f, false, false},
|
||||
"VMLS": {0x6ea09400, 0x7f, false, false},
|
||||
"VORN": {0x4ee01c00, 0x7f, false, false},
|
||||
"VMLA": {0x4ea09400, 0x3f, false, false}, // no 2D: integer multiply stops at 4S
|
||||
"VMLS": {0x6ea09400, 0x3f, false, false},
|
||||
"VORN": {0x4ee01c00, 0x03, false, false},
|
||||
"VSHADD": {0x4ea00400, 0x7f, false, false},
|
||||
"VSRHADD": {0x4ea01400, 0x7f, false, false},
|
||||
"VUHADD": {0x6ea00400, 0x7f, false, false},
|
||||
"VURHADD": {0x6ea01400, 0x7f, false, false},
|
||||
"VSMAX": {0x4ea06400, 0x7f, false, false},
|
||||
"VSMIN": {0x4ea06c00, 0x7f, false, false},
|
||||
"VSMAXP": {0x4ea0a400, 0x7f, false, false},
|
||||
"VSMINP": {0x4ea0ac00, 0x7f, false, false},
|
||||
"VUMAX": {0x2e206400, 0x7f, false, false},
|
||||
"VUMIN": {0x2e206c00, 0x7f, false, false},
|
||||
"VUMAXP": {0x6ea0a400, 0x7f, false, false},
|
||||
"VUMINP": {0x6ea0ac00, 0x7f, false, false},
|
||||
"VSMAX": {0x4ea06400, 0x3f, false, false}, // no 2D: integer max stops at 4S
|
||||
"VSMIN": {0x4ea06c00, 0x3f, false, false},
|
||||
"VSMAXP": {0x4ea0a400, 0x3f, false, false},
|
||||
"VSMINP": {0x4ea0ac00, 0x3f, false, false},
|
||||
"VUMAX": {0x2e206400, 0x3f, false, false},
|
||||
"VUMIN": {0x2e206c00, 0x3f, false, false},
|
||||
"VUMAXP": {0x6ea0a400, 0x3f, false, false},
|
||||
"VUMINP": {0x6ea0ac00, 0x3f, false, false},
|
||||
"VSQADD": {0x4ea00c00, 0x7f, false, false},
|
||||
"VUQADD": {0x6ea00c00, 0x7f, false, false},
|
||||
"VSQSUB": {0x4ea02c00, 0x7f, false, false},
|
||||
@@ -1297,7 +1293,7 @@ var a64SimdV2Table = map[string]a64SimdVSpec{
|
||||
"VNOT": {0x2e205800, 0x7f, false, false},
|
||||
"VSQABS": {0x0e207800, 0x7f, false, false},
|
||||
"VSQNEG": {0x2e207800, 0x7f, false, false},
|
||||
"VRBIT": {0x2e605800, 0x7f, false, false},
|
||||
"VRBIT": {0x2e605800, 0x03, false, false}, // 8B and 16B only
|
||||
"VSCVTF": {0x4e21d800, fpSimdArrs, false, true},
|
||||
"VUCVTF": {0x6e21d800, fpSimdArrs, false, true},
|
||||
"VFCVTZS": {0x4ea1b800, fpSimdArrs, false, true},
|
||||
|
||||
@@ -2185,3 +2185,4 @@ func TestArm64ZRNameNotNumber(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package asm
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||
)
|
||||
|
||||
// arm64AcceptedErrorShapes lists the toolchain's arm64error.s spellings gasm
|
||||
// still accepts, each an acceptance superset with a known shape. The list
|
||||
// only shrinks: every tightening of the encoder moves spellings out of it,
|
||||
// and a spelling reappearing here means a regression.
|
||||
var arm64AcceptedErrorShapes = []string{
|
||||
// VLD1/VST1 post-index shapes: the register post-index without the .P
|
||||
// spelling, the .P immediate against the aggregate register size, the
|
||||
// scaled register increment and the replicating register count.
|
||||
"VLD1 (R8)(R13), [V2.B16]",
|
||||
"VST1 [V1.B16], (R8)(R13)",
|
||||
"VST1.P [V4.S4,V5.S4], 48(R1)",
|
||||
"VST1.P [V4.S4], 8(R1)",
|
||||
"VLD1.P 32(R1), [V8.S4, V9.S4, V10.S4]",
|
||||
"VLD1.P 48(R1), [V7.S4, V8.S4, V9.S4, V10.S4]",
|
||||
"VLD1.P (R8)(R9<<2), [V2.B16]",
|
||||
"VST1.P [V1.B16], (R8)(R9<<1)",
|
||||
"VLD3R.P 24(R15), [V15.H4,V16.H4,V17.H4]",
|
||||
// REGTMP (R27) as an explicit operand the toolchain refuses, the pair
|
||||
// split paths included.
|
||||
"ADD $0x1234567, R27, R3",
|
||||
"ADD $0x3fffffffc000, R27, R5",
|
||||
"AND $0x22220000, R27, R4",
|
||||
"ANDW $0x6006000060060, R27, R5",
|
||||
"STP (R3, R4), 0x1234567(R27)",
|
||||
"LDP 0x1234567(R27), (R3, R4)",
|
||||
"STP (R26, R27), 700(R2)",
|
||||
// TLBI operand arity and the range-prefetch operand shapes.
|
||||
"TLBI VMALLE1IS, R0",
|
||||
"TLBI ALLE3OS, ZR",
|
||||
"TLBI VAE1IS",
|
||||
"TLBI RVALE3",
|
||||
"RPRFM (R1), RSP, PLDKEEP",
|
||||
// The GP register pairs on the FP mnemonics, the other way round.
|
||||
"LDP (R0), (F0, F1)",
|
||||
"STP (F2, F3), (R0)",
|
||||
// FCVTL exists for the S to D pair alone.
|
||||
"VFCVTL V1.H4, V2.S4",
|
||||
}
|
||||
|
||||
// TestArm64ToolchainErrorParity walks the toolchain's arm64error.s (Go
|
||||
// 1.27, arm64) and requires gasm to reject every case the toolchain rejects,
|
||||
// the documented acceptance supersets above excepted. A live Go toolchain
|
||||
// is needed for the source file; the test skips without one or in -short.
|
||||
func TestArm64ToolchainErrorParity(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("live arm64error.s corpus: skipped in -short mode")
|
||||
}
|
||||
goroot := os.Getenv("GOROOT")
|
||||
if goroot == "" {
|
||||
t.Skip("no GOROOT")
|
||||
}
|
||||
path := filepath.Join(goroot, "src", "cmd", "asm", "internal", "asm", "testdata", "arm64error.s")
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
allowed := map[string]bool{}
|
||||
for _, s := range arm64AcceptedErrorShapes {
|
||||
allowed[s] = true
|
||||
}
|
||||
for raw := range strings.SplitSeq(string(data), "\n") {
|
||||
line := strings.TrimSpace(raw)
|
||||
if line == "" || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "TEXT") || !strings.Contains(line, "ERROR") {
|
||||
continue
|
||||
}
|
||||
body := line
|
||||
if i := strings.Index(body, "//"); i >= 0 {
|
||||
body = strings.TrimSpace(body[:i])
|
||||
}
|
||||
body = strings.ReplaceAll(body, "\t", " ")
|
||||
body = strings.Join(strings.Fields(body), " ")
|
||||
// Label-relative and non-operand lines need their function context.
|
||||
if strings.Contains(body, "(PC)") || strings.Contains(body, "(SB)") || strings.Contains(body, "PCALIGN") ||
|
||||
strings.HasPrefix(body, "RET") || strings.HasPrefix(body, "NOP") || strings.HasPrefix(body, "BRK") ||
|
||||
strings.Contains(body, "again") || strings.Contains(body, "next") || strings.Contains(body, "loop") {
|
||||
continue
|
||||
}
|
||||
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\t" + body + "\n\tRET\n"
|
||||
f, perr := parser.Parse("errorparity.s", src)
|
||||
if len(perr) > 0 {
|
||||
continue // the parser already rejects the spelling
|
||||
}
|
||||
if _, aerr := AssembleFileARM64(f); aerr == nil && !allowed[body] {
|
||||
t.Errorf("gasm accepts what the toolchain rejects: %s", body)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user