fix(asm): enforce the arm64 TLBI, RPRFM, FCVT and integer-pair arities

Assisted-by: GLM 5.3
This commit is contained in:
petrbalvin committed 2026-10-07 13:49:49 +02:00
1 parent 7a69be8b59
commit 33e7fdac98
4 files changed
+140 -33

No files matched your search

+71 -7
View File
@@ -3448,6 +3448,34 @@ func arm64SimdNarrowPair(mnem, src, dst string, two bool) error {
return nil
}
// arm64SimdFCVTLongPair validates the FCVTL width pair: S to D alone, with
// the plain spelling reading S2 and the .2 spelling S4, the destination
// always D2.
func arm64SimdFCVTLongPair(mnem, src, dst string, two bool) error {
wantSrc := "S2"
if two {
wantSrc = "S4"
}
if src != wantSrc || dst != "D2" {
return fmt.Errorf("%s: operand mismatch for the %s spelling: want %s, %s", mnem, mnem, wantSrc, "D2")
}
return nil
}
// arm64SimdFCVTNarrowPair validates the FCVTN width pair: D to S alone, the
// source always D2, the destination S2 for the plain spelling and S4 for
// the .2 spelling.
func arm64SimdFCVTNarrowPair(mnem, src, dst string, two bool) error {
wantDst := "S2"
if two {
wantDst = "S4"
}
if src != "D2" || dst != wantDst {
return fmt.Errorf("%s: operand mismatch for the %s spelling: want %s, %s", mnem, mnem, "D2", wantDst)
}
return nil
}
// arm64SimdNLArrBits returns the arrangement bits a narrow/long/wide
// instruction contributes: the driving arrangement's size and Q bits, for
// the FCVT family only the Q bit (whose size field is fixed in the base),
@@ -3504,7 +3532,19 @@ func encodeARM64SimdNL(mnem string, spec a64SimdNLSpec, ops []*ast.Operand) ([]b
}
var drive string
var pairErr error
if spec.form == a64NLTwoNarrow {
if spec.qonly {
// The FCVT conversions are pinned to one width pair: S to D for
// the lengthening (FCVTL S2→D2, FCVTL2 S4→D2), D to S for the
// narrowing (FCVTN D2→S2, FCVTN2 D2→S4); the size field is
// fixed in the opcode and no other arrangement exists.
if spec.form == a64NLTwoNarrow {
drive = vd.arr
pairErr = arm64SimdFCVTNarrowPair(mnem, vn.arr, vd.arr, two)
} else {
drive = vn.arr
pairErr = arm64SimdFCVTLongPair(mnem, vn.arr, vd.arr, two)
}
} else if spec.form == a64NLTwoNarrow {
// XTN/FCVTN: wide source into a narrow destination; the
// arrangement bits follow the destination.
drive, pairErr = vd.arr, arm64SimdNarrowPair(mnem, vn.arr, vd.arr, two)
@@ -3863,7 +3903,9 @@ func encodeARM64Pair(mnem string, baseOp uint32, ops []*ast.Operand, pc int, fi
}
}
// The FP pairs take FP registers against a GP base (asm7.go: invalid
// register pair).
// register pair). The integer pairs are the mirror image: an F pair on
// LDP/STP is an invalid register pair too, the toolchain having no class
// for it.
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 {
@@ -3875,6 +3917,14 @@ func encodeARM64Pair(mnem string, baseOp uint32, ops []*ast.Operand, pc int, fi
if strings.HasPrefix(strings.TrimLeft(operandRegName(memOp), "( "), "F") {
return nil, fmt.Errorf("%s: invalid register pair: the base must be a general register", mnem)
}
} else {
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)
}
}
// Pair access against a static symbol: ADRP R27, sym; ADD R27, R27, #lo;
@@ -4192,19 +4242,28 @@ func encodeARM64Sys(mnem string, ops []*ast.Operand) ([]byte, error) {
w := 0xd5080000 | inst.op1<<16 | 7<<12 | inst.cm<<8 | inst.op2<<5
return a64wordLE(w | uint32(rn)&31), nil
case "TLBI":
// The register operand is optional: TLBI VMALLE1IS alone means ZR.
// The register operand's arity follows the operation (asm7.go's
// sysInstFields): the by-address spellings take the Xt and the
// whole-entry ones (VMALL*, ALL**) take none, an explicit register
// being extraneous.
if len(ops) != 1 && len(ops) != 2 {
return nil, fmt.Errorf("TLBI expects <op>[, Rn]")
}
inst, ok := a64TLBIOps[operandRegName(ops[0])]
name := operandRegName(ops[0])
inst, ok := a64TLBIOps[name]
if !ok {
return nil, fmt.Errorf("TLBI: unknown operation %q", operandRegName(ops[0]))
return nil, fmt.Errorf("TLBI: unknown operation %q", name)
}
rt := 31
if len(ops) == 2 {
if !arm64TLBITakesReg(name) {
return nil, fmt.Errorf("TLBI %s: extraneous register at operand 2", name)
}
if rt = arm64RegNum(operandRegName(ops[1])); rt < 0 {
return nil, fmt.Errorf("TLBI: invalid register operand")
}
} else if arm64TLBITakesReg(name) {
return nil, fmt.Errorf("TLBI %s: missing register at operand 2", name)
}
w := 0xd5080000 | inst.op1<<16 | 8<<12 | inst.cm<<8 | inst.op2<<5
return a64wordLE(w | uint32(rt)&31), nil
@@ -4328,6 +4387,11 @@ func encodeARM64Sys(mnem string, ops []*ast.Operand) ([]byte, error) {
if rm < 0 {
return nil, fmt.Errorf("RPRFM: invalid register operand")
}
// The toolchain's class ladder takes a plain R register alone: RSP
// and ZR are illegal combinations (asm7.go case 110).
if name := operandRegName(ops[1]); strings.EqualFold(name, "RSP") || strings.EqualFold(name, "SP") || strings.EqualFold(name, "ZR") {
return nil, fmt.Errorf("RPRFM: illegal combination: %s is not a general register", name)
}
var op uint64
if isImmOperand(ops[2]) {
op = uint64(arm64Imm64(ops[2]))
@@ -5015,8 +5079,8 @@ func encodeARM64VLDST(mnem string, post uint32, ops []*ast.Operand) ([]byte, err
}
// The toolchain's addressing contract for the structure forms (asm7.go's
// class ladder): an index register exists only as the post-index
// increment, so it is illegal without .P and takes neither an offset nor
// an extend nor a shift beside it.
// increment, so it is illegal without .P, and the immediate increment
// must be exactly the bytes the whole list transfers.
idx := ops[memIdx].Addr.Index
if idx != "" && post == 0 {
return nil, fmt.Errorf("%s: illegal combination: the register index is a post-index, it needs the .P spelling", mnem)