feat(asm): lower the arm64 con(register) form to the ADD chain

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 02:36:24 +02:00
1 parent 9ef14bdb71
commit d786b90fa1
2 files changed
+193 -2

No files matched your search

+131 -2
View File
@@ -1602,6 +1602,24 @@ func encodeARM64Mov(instr *ast.Instr, mnem string, wb string, fi arm64FrameInfo,
}
return encodeARM64SBAddr(src.Imm.Sym, rd, relocs), nil
}
rd := arm64RegNum(operandRegName(dst))
// The con(register) form: MOVD $con(Rn), Rd adds the displacement to
// the base register (asm7.go case 4). The toolchain rejects every
// other width and the ZR destination outright (RSP is a real register
// here, the C_RSP row).
if rn, ok := arm64ImmWithBase(src); ok {
if mnem != "MOVD" && mnem != "MOV" {
return nil, fmt.Errorf("%s: illegal combination: the con(register) form exists for MOVD only", mnem)
}
if strings.EqualFold(operandRegName(dst), "ZR") {
return nil, fmt.Errorf("%s: illegal combination: the con(register) form needs a real destination register", mnem)
}
if rd < 0 {
return nil, fmt.Errorf("%s $con(Rn): invalid destination register", mnem)
}
con, _ := arm64ImmOperandValue(src)
return encodeARM64ConRn(rn, con, rd, pool, poolBase, pc)
}
// Immediate → memory: only storing zero is encodable (the ZR
// register); the toolchain rejects any other immediate-to-memory
// combination ("illegal combination").
@@ -1611,7 +1629,6 @@ func encodeARM64Mov(instr *ast.Instr, mnem string, wb string, fi arm64FrameInfo,
}
return encodeARM64MemOp(mnem, dst, 31, false, fi, "", pool, poolBase, pc)
}
rd := arm64RegNum(operandRegName(dst))
if rd < 0 {
return nil, fmt.Errorf("%s $imm: invalid destination register", mnem)
}
@@ -1712,6 +1729,13 @@ func arm64MovSize(mnem string, ops []*ast.Operand, fi arm64FrameInfo) int {
if src.Imm.Sym != nil && src.Imm.Sym.Pseudo == "SB" {
return 8 // ADRP + ADD
}
// The con(register) form lowers to the toolchain's ADD/SUB chain:
// one word in the addcon band, two in the 24-bit band, and the two
// pool words (LDR X plus the UXTX add) beyond it.
if _, ok := arm64ImmWithBase(src); ok {
con, _ := arm64ImmOperandValue(src)
return arm64ConRnSize(con)
}
if isMemOperand(dst) {
// Only the $0 (ZR store) immediate reaches memory, in one word.
return 4
@@ -2207,11 +2231,99 @@ func arm64OffsetSplitReach(off int64, lt a64LSType) bool {
// arm64AddImmWord encodes ADD $v, Rn, Rd the way the toolchain's oaddi
// does: a non-zero multiple of 0x1000 encodes shifted left by twelve.
func arm64AddImmWord(v int64, rn, rd uint32) uint32 {
return arm64AddSubImmWord(0, v, rn, rd)
}
// arm64AddSubImmWord encodes ADD (op 0) or SUB (op 1) $v, Rn, Rd the way the
// toolchain's oaddi does: a non-zero multiple of 0x1000 encodes shifted left
// by twelve.
func arm64AddSubImmWord(op uint32, v int64, rn, rd uint32) uint32 {
sh := arm64AddShift(v)
if sh == 1 {
v >>= 12
}
return a64AddSub(1, 0, 0, sh, uint32(v), rn, rd)
return a64AddSub(1, op, 0, sh, uint32(v), rn, rd)
}
// arm64ImmWithBase reports whether an immediate operand spells the
// con(register) form, $con(REG): the parser leaves it unstructured (an
// immediate whose raw spelling carries the parenthesised register), so the
// base register comes off the raw text while the constant rides the parsed
// immediate. ok is false for every other shape.
func arm64ImmWithBase(op *ast.Operand) (rn int, ok bool) {
if op.Kind != 0 || op.Imm.Sym != nil {
return 0, false
}
s := strings.Join(strings.Fields(op.Raw), " ")
if !strings.HasSuffix(s, ")") {
return 0, false
}
i := strings.LastIndex(s, "(")
if i < 0 {
return 0, false
}
reg := strings.TrimSpace(s[i+1 : len(s)-1])
rn = arm64RegNum(reg)
if rn < 0 {
return 0, false
}
return rn, true
}
// arm64ConRnSize sizes the con(register) lowering of encodeARM64ConRn
// without touching the pool: the bands mirror the encoder exactly.
func arm64ConRnSize(con int64) int {
a := con
if a < 0 {
a = -a
}
if a <= 0xfff || (a&0xfff == 0 && a <= 0xfff000) {
return 4
}
if con >= 0 && a <= 0xffffff {
return 8
}
return 8 // LDR X, pool + the UXTX add
}
// encodeARM64ConRn lowers MOVD $con(Rn), Rd the way the toolchain's case 4
// does: a single ADD/SUB immediate inside the addcon band (±4095 or a
// multiple of 4096 up to 0xfff<<12), the hi<<12 plus lo pair for a positive
// displacement inside the 24-bit band, and the literal pool plus a UXTX add
// beyond either (a negative displacement outside the addcon band pools
// straight away: isaddcon2 only takes non-negative values).
func encodeARM64ConRn(rn int, con int64, rd int, pool *arm64Pool, poolBase, pc int) ([]byte, error) {
op := uint32(0) // ADD
a := con
if a < 0 {
op = 1 // SUB
a = -a
}
if a <= 0xfff || (a&0xfff == 0 && a <= 0xfff000) {
return a64wordLE(arm64AddSubImmWord(op, a, uint32(rn), uint32(rd))), nil
}
if con >= 0 && a <= 0xffffff {
hi := a & 0xfff000
lo := a & 0xfff
return a64WordsLE(
arm64AddSubImmWord(op, hi, uint32(rn), uint32(rd)),
arm64AddSubImmWord(op, lo, uint32(rd), uint32(rd)),
), nil
}
// Beyond the bands the toolchain pools the displacement (a full 64-bit
// slot) and adds it back with a UXTX-extended register add (case 34).
if pool == nil {
return nil, fmt.Errorf("MOVD $%d(R%d): displacement out of range (literal pool not supported)", con, rn)
}
entryOff, w := pool.add64(con)
dist := (poolBase + entryOff - pc) >> 2
if dist < -(1<<18) || dist >= 1<<18 {
return nil, fmt.Errorf("MOVD $%d(R%d): literal pool %d out of 19-bit reach", con, rn, dist<<2)
}
return a64WordsLE(
w<<30|3<<27|uint32(dist)&0x7FFFF<<5|27, // LDR R27, pool
a64AddSubReg(1, 27, uint32(rn), uint32(rd)),
), nil
}
// ---- static symbol references (ADRP + offset) ----
@@ -4911,6 +5023,23 @@ func (p *arm64Pool) add(v int64) (int, uint32) {
return off, w
}
// add64 reserves an 8-byte slot for v loaded by a full LDR X: the lacon
// pool path always reads 64 bits, even when the value fits 32 (asm7.go case
// 34's omovlit(AMOVD)).
func (p *arm64Pool) add64(v int64) (int, uint32) {
if i, ok := p.seen[v]; ok && p.order[i].w == 1 {
return p.order[i].off, p.order[i].w
}
off := (p.size + 7) &^ 7
p.size = off + 8
if p.seen == nil {
p.seen = map[int64]int{}
}
p.seen[v] = len(p.order)
p.order = append(p.order, arm64PoolEntry{data: a64WordsLE(uint32(v), uint32(v>>32)), off: off, w: 1})
return off, 1
}
// AssembleFileARM64 assembles every TEXT function of a parsed arm64 file
// and lays out its static symbols (GLOBL/DATA) in a data section behind the
// code. SB references in the code are encoded as ADRP pairs with zero