feat(asm): lower the arm64 con(register) form to the ADD chain
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
9ef14bdb71
commit
d786b90fa1
2 files changed
+193
-2
No files matched your search
+131
-2
@@ -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
|
||||
|
||||
Reference in new issue
Block a user