fix(asm): tighten the arm64 acceptance toward the toolchain

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 02:36:24 +02:00
1 parent 82d741514a
commit 8ab99c9b0c
4 files changed
+250 -29

No files matched your search

+129 -6
View File
@@ -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
}