fix(arm64): encode shifts, divides and multiplies and align sizes with emission
Assisted-by: GLM 5.3
This commit is contained in:
+209
-85
@@ -189,7 +189,7 @@ func arm64InstrSize(instr *ast.Instr, fi arm64FrameInfo) int {
|
||||
return arm64MovSize(mnem, ops, fi)
|
||||
case "ADD", "ADDW", "SUB", "SUBW", "AND", "ANDW", "ORR", "ORRW", "EOR", "EORW":
|
||||
if len(ops) >= 2 && isImmOperand(ops[0]) {
|
||||
v := immFromOperand(ops[0])
|
||||
v := arm64Imm64(ops[0])
|
||||
// Small immediate (0..4095 or -2048..-1) fits in one instruction.
|
||||
if v >= 0 && v <= 0xFFF {
|
||||
return 4
|
||||
@@ -221,7 +221,11 @@ func encodeARM64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi arm64
|
||||
if len(ops) != 1 {
|
||||
return nil, fmt.Errorf("WORD expects 1 operand, got %d", len(ops))
|
||||
}
|
||||
return a64wordLE(uint32(immFromOperand(ops[0]))), nil
|
||||
w := arm64Imm64(ops[0])
|
||||
if w < 0 || w > 0xFFFFFFFF {
|
||||
return nil, fmt.Errorf("WORD: immediate %d does not fit a 32-bit word", w)
|
||||
}
|
||||
return a64wordLE(uint32(w)), nil
|
||||
case "B", "JMP":
|
||||
return encodeARM64Branch(mnem, ops, pc, offsets, false, relocs, resolve)
|
||||
case "BL", "CALL":
|
||||
@@ -249,14 +253,19 @@ func encodeARM64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi arm64
|
||||
}
|
||||
}
|
||||
|
||||
// Shifts: immediate forms alias SBFM/UBFM/EXTR, register forms are the
|
||||
// two-source LSLV/LSRV/ASRV/RORV.
|
||||
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FShift {
|
||||
return encodeARM64Shift(mnem, enc.op, ops)
|
||||
}
|
||||
|
||||
// Multiply-accumulate: MADD/MSUB Rm, Ra, Rn, Rd.
|
||||
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FDPR4 {
|
||||
return encodeARM64MAddSub(mnem, enc.op, ops)
|
||||
}
|
||||
|
||||
// Register-register data processing.
|
||||
// ASR/LSL/LSR/ROR with immediate operands use bitfield encoding (SBFM/UBFM).
|
||||
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FDPSR {
|
||||
isShift := mnem == "ASR" || mnem == "ASRW" || mnem == "LSL" || mnem == "LSLW" ||
|
||||
mnem == "LSR" || mnem == "LSRW" || mnem == "ROR" || mnem == "RORW"
|
||||
if isShift && len(ops) >= 2 && isImmOperand(ops[0]) {
|
||||
return encodeARM64Bitfield(mnem, enc.op, ops)
|
||||
}
|
||||
return encodeARM64DPSR(mnem, enc.op, ops)
|
||||
}
|
||||
|
||||
@@ -478,6 +487,88 @@ func encodeARM64DPSR(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
|
||||
// encodeARM64Shift encodes LSL/LSR/ASR/ROR in both widths. The operand order
|
||||
// is source first, destination last: OP $sh|Rm, Rn, Rd or OP $sh|Rm, Rd.
|
||||
// With an immediate the shift is the SBFM/UBFM (ROR: EXTR) alias, with a
|
||||
// register it is the data-processing (2 source) LSLV/LSRV/ASRV/RORV; the
|
||||
// two-source opcode rides the same 0xd6<<21 field as SDIV/UDIV, with
|
||||
// LSLV=0b001000, LSRV=0b001001, ASRV=0b001010, RORV=0b001011 at bits 15:10.
|
||||
func encodeARM64Shift(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rn := arm64RegNum(operandRegName(ops[1]))
|
||||
rd := arm64RegNum(operandRegName(ops[len(ops)-1]))
|
||||
if rn < 0 || rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
|
||||
if isImmOperand(ops[0]) {
|
||||
width := uint32(64)
|
||||
if strings.HasSuffix(mnem, "W") {
|
||||
width = 32
|
||||
}
|
||||
sh := arm64Imm64(ops[0])
|
||||
if sh < 0 || uint32(sh) >= width {
|
||||
return nil, fmt.Errorf("%s: shift amount %d out of range for %d-bit form", mnem, sh, width)
|
||||
}
|
||||
switch mnem {
|
||||
case "LSL", "LSLW":
|
||||
// UBFM Rd, Rn, #(-sh) mod W, #(W-1)-sh
|
||||
immr := (width - uint32(sh)) % width
|
||||
return a64wordLE(baseOp | immr<<16 | (width-1-uint32(sh))<<10 | uint32(rn)<<5 | uint32(rd)), nil
|
||||
case "LSR", "LSRW":
|
||||
// UBFM Rd, Rn, #sh, #(W-1)
|
||||
return a64wordLE(baseOp | uint32(sh)<<16 | (width-1)<<10 | uint32(rn)<<5 | uint32(rd)), nil
|
||||
case "ASR", "ASRW":
|
||||
// SBFM Rd, Rn, #sh, #(W-1)
|
||||
return a64wordLE(baseOp | uint32(sh)<<16 | (width-1)<<10 | uint32(rn)<<5 | uint32(rd)), nil
|
||||
default:
|
||||
// ROR, RORW: EXTR Rd, Rn, Rn, #sh (Rm = Rn, imms = sh).
|
||||
return a64wordLE(baseOp | uint32(rn)<<16 | uint32(sh)<<10 | uint32(rn)<<5 | uint32(rd)), nil
|
||||
}
|
||||
}
|
||||
|
||||
rm := arm64RegNum(operandRegName(ops[0]))
|
||||
if rm < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
op2 := uint32(8) // LSLV
|
||||
switch mnem {
|
||||
case "LSR", "LSRW":
|
||||
op2 = 9 // LSRV
|
||||
case "ASR", "ASRW":
|
||||
op2 = 10 // ASRV
|
||||
case "ROR", "RORW":
|
||||
op2 = 11 // RORV
|
||||
}
|
||||
sf := uint32(1)
|
||||
if strings.HasSuffix(mnem, "W") {
|
||||
sf = 0
|
||||
}
|
||||
return a64wordLE(sf<<31 | 0xd6<<21 | op2<<10 | uint32(rm)<<16 | uint32(rn)<<5 | uint32(rd)), nil
|
||||
}
|
||||
|
||||
// encodeARM64MAddSub encodes MADD/MSUB/MADDW/MSUBW. The toolchain's operand
|
||||
// order is Rm, Ra, Rn, Rd (its optab case 15 comment says exactly that), so
|
||||
// the accumulate register is the SECOND operand: base | Rm<<16 | Ra<<10 |
|
||||
// Rn<<5 | Rd. The optab has no shorter row for these mnemonics, so all four
|
||||
// operands are mandatory; MUL's two-operand spelling (Ra = ZR) belongs to the
|
||||
// MUL mnemonic, not to these.
|
||||
func encodeARM64MAddSub(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||
if len(ops) != 4 {
|
||||
return nil, fmt.Errorf("%s expects 4 operands (Rm, Ra, Rn, Rd), got %d", mnem, len(ops))
|
||||
}
|
||||
rm := arm64RegNum(operandRegName(ops[0]))
|
||||
ra := arm64RegNum(operandRegName(ops[1]))
|
||||
rn := arm64RegNum(operandRegName(ops[2]))
|
||||
rd := arm64RegNum(operandRegName(ops[3]))
|
||||
if rm < 0 || rn < 0 || ra < 0 || rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(rm)<<16 | uint32(ra)<<10 | uint32(rn)<<5 | uint32(rd)), nil
|
||||
}
|
||||
|
||||
// ---- ADD/SUB immediate ----
|
||||
|
||||
// encodeARM64AddSubImm encodes an ADD/SUB immediate instruction.
|
||||
@@ -485,7 +576,7 @@ func encodeARM64AddSubImm(mnem string, ops []*ast.Operand) ([]byte, error) {
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
v := immFromOperand(ops[0])
|
||||
v := arm64Imm64(ops[0])
|
||||
rd := arm64RegNum(operandRegName(ops[len(ops)-1]))
|
||||
rn := rd
|
||||
if len(ops) == 3 {
|
||||
@@ -529,6 +620,8 @@ func encodeARM64AddSubImm(mnem string, ops []*ast.Operand) ([]byte, error) {
|
||||
if v >= 0 && v <= 0xFFF000 && v&0xFFF == 0 {
|
||||
return a64wordLE(a64AddSub(sf, op, S, 1, uint32(v>>12), uint32(rn), uint32(rd))), nil
|
||||
}
|
||||
// The imm12 field cannot carry the value; rejecting (rather than
|
||||
// truncating) matches the toolchain, which reports the same shape.
|
||||
return nil, fmt.Errorf("%s: immediate %d out of range for single instruction", mnem, v)
|
||||
}
|
||||
|
||||
@@ -615,14 +708,15 @@ func arm64MovSize(mnem string, ops []*ast.Operand, fi arm64FrameInfo) int {
|
||||
if src.Imm.Sym != nil && src.Imm.Sym.Pseudo == "SB" {
|
||||
return 8 // ADRP + ADD
|
||||
}
|
||||
v := arm64Imm64(src)
|
||||
if v == 0 {
|
||||
// Size the immediate exactly as the encoder will emit it: multi-chunk
|
||||
// values expand to up to four words and the W forms truncate first.
|
||||
// Anything else would desynchronise the label offsets of pass 1 from
|
||||
// the bytes pass 2 lays down, corrupting every later branch.
|
||||
b, err := encodeARM64LoadImm(31, arm64Imm64(src), mnem)
|
||||
if err != nil {
|
||||
return 4
|
||||
}
|
||||
if arm64Movcon(v) >= 0 || arm64Movcon(^v) >= 0 {
|
||||
return 4
|
||||
}
|
||||
return 8 // MOVZ + MOVK
|
||||
return len(b)
|
||||
case src.Addr.Sym != nil && src.Addr.Sym.Pseudo == "SB":
|
||||
return 8 // ADRP + LDR
|
||||
case dst.Addr.Sym != nil && dst.Addr.Sym.Pseudo == "SB":
|
||||
@@ -638,7 +732,7 @@ func arm64MovSize(mnem string, ops []*ast.Operand, fi arm64FrameInfo) int {
|
||||
if !ok {
|
||||
lt = a64LoadTable["MOVD"] // the MOV pseudo is a 64-bit access
|
||||
}
|
||||
scale := int32(1) << uint(lt.size)
|
||||
scale := int64(1) << uint(lt.size)
|
||||
if off >= 0 && off%scale == 0 && off/scale < 4096 {
|
||||
return 4
|
||||
}
|
||||
@@ -655,28 +749,28 @@ func arm64MovSize(mnem string, ops []*ast.Operand, fi arm64FrameInfo) int {
|
||||
}
|
||||
|
||||
// encodeARM64LoadImm loads an immediate into a register, matching the
|
||||
// toolchain's MOVZ/MOVN/MOVK sequence.
|
||||
// toolchain's MOVZ/MOVN/MOVK sequence. W forms truncate to 32 bits first and
|
||||
// every classification (movcon, complement, chunk count) runs on the truncated
|
||||
// value, so a 32-bit immediate never reaches the 64-bit halves: MOVW $-1
|
||||
// truncates to 0xFFFFFFFF, whose complement is a single zero chunk, and encodes
|
||||
// as MOVN W, #0.
|
||||
func encodeARM64LoadImm(rd int, v int64, mnem string) ([]byte, error) {
|
||||
d := v
|
||||
// For 32-bit MOVW, zero-extend.
|
||||
sf := uint32(1) // 64-bit
|
||||
if mnem == "MOVW" || mnem == "MOVWU" {
|
||||
d = int64(uint32(v))
|
||||
sf = 0
|
||||
}
|
||||
|
||||
if d == 0 {
|
||||
// ORR Rd, ZR, ZR (MOV $0, Rd)
|
||||
op := uint32(1<<31 | 1<<29 | 0x0a<<24) // ORR 64-bit
|
||||
if mnem == "MOVW" || mnem == "MOVWU" {
|
||||
if sf == 0 {
|
||||
op = 0<<31 | 1<<29 | 0x0a<<24 // ORR 32-bit
|
||||
}
|
||||
return a64wordLE(op | 31<<16 | 31<<5 | uint32(rd)), nil
|
||||
}
|
||||
|
||||
sf := uint32(1) // 64-bit
|
||||
if mnem == "MOVW" || mnem == "MOVWU" {
|
||||
sf = 0
|
||||
}
|
||||
|
||||
// The Go toolchain classifies immediates:
|
||||
// - C_ABCON0 (0 < v ≤ 4095): bitmask first for positive values
|
||||
// - Negative values: MOVN first, then bitmask
|
||||
@@ -691,15 +785,21 @@ func encodeARM64LoadImm(rd int, v int64, mnem string) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Try MOVZ (single non-zero 16-bit chunk).
|
||||
// Try MOVZ (single non-zero 16-bit chunk) and MOVN (single non-0xFFFF
|
||||
// chunk of the complement). The W forms must look inside the 32-bit
|
||||
// window only, so the complement is masked to the operand width; d is
|
||||
// already truncated and needs no mask.
|
||||
width := uint64(0xFFFFFFFF)
|
||||
if sf == 1 {
|
||||
width = 0xFFFFFFFFFFFFFFFF
|
||||
}
|
||||
s := arm64Movcon(d)
|
||||
if s >= 0 {
|
||||
return a64wordLE(a64MoveWide(sf, 2, uint32(s>>4), uint32((d>>uint(s))&0xFFFF), uint32(rd))), nil
|
||||
}
|
||||
// Try MOVN (single non-0xFFFF 16-bit chunk of ^d).
|
||||
sn := arm64Movcon(^d)
|
||||
sn := arm64Movcon(^d & int64(width))
|
||||
if sn >= 0 {
|
||||
return a64wordLE(a64MoveWide(sf, 0, uint32(sn>>4), uint32((^d>>uint(sn))&0xFFFF), uint32(rd))), nil
|
||||
return a64wordLE(a64MoveWide(sf, 0, uint32(sn>>4), uint32(((^d)>>uint(sn))&0xFFFF), uint32(rd))), nil
|
||||
}
|
||||
|
||||
// For values outside the bitmask-first range that are not movcon: try bitmask.
|
||||
@@ -710,7 +810,7 @@ func encodeARM64LoadImm(rd int, v int64, mnem string) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-instruction: MOVZ + MOVK for each non-zero16-bit chunk.
|
||||
// Multi-instruction: MOVZ + MOVK for each non-zero 16-bit chunk.
|
||||
var ws []uint32
|
||||
first := true
|
||||
for i := range 4 {
|
||||
@@ -805,7 +905,7 @@ func arm64Bitmask(v uint64, sf int) (N, immr, imms uint32, ok bool) {
|
||||
// Integer → integer: ORR Rd, ZR, Rs.
|
||||
// FP → FP: FMOV Fd, Fn (FP data processing).
|
||||
// FP ↔ GP: FMOV general (FPCVTI encoding).
|
||||
// Go Plan 9 syntax: MOV dst, src (first operand = destination).
|
||||
// Go Plan 9 syntax is source first, destination last: MOV src, dst.
|
||||
func encodeARM64RegMove(mnem string, src, dst *ast.Operand) ([]byte, error) {
|
||||
rs := arm64RegNum(operandRegName(src))
|
||||
rd := arm64RegNum(operandRegName(dst))
|
||||
@@ -826,8 +926,7 @@ func encodeARM64RegMove(mnem string, src, dst *ast.Operand) ([]byte, error) {
|
||||
}
|
||||
|
||||
// GP ↔ FP: FMOV general (FPCVTI encoding).
|
||||
// Go syntax: FMOV FPdst, GPsrc or FMOV GPdst, FPsrc.
|
||||
// First operand = destination, second = source.
|
||||
// Go syntax: FMOV GPsrc, FPdst or FMOV FPsrc, GPdst, source first.
|
||||
if sc == arm64ClsFP && dc == arm64ClsGR {
|
||||
// FP → GP: FMOV Wd/Xd, Sn/Dn. opcode bits[20:16]=6.
|
||||
sf, typ := uint32(0), uint32(0)
|
||||
@@ -867,7 +966,7 @@ func encodeARM64MemOp(mnem string, mem *ast.Operand, reg int, load bool, fi arm6
|
||||
lt = a64LoadTable["MOVD"]
|
||||
}
|
||||
|
||||
scale := int32(1) << uint(lt.size)
|
||||
scale := int64(1) << uint(lt.size)
|
||||
storeOpc := a64StoreOpc(lt)
|
||||
var opc int
|
||||
if load {
|
||||
@@ -880,30 +979,31 @@ func encodeARM64MemOp(mnem string, mem *ast.Operand, reg int, load bool, fi arm6
|
||||
return a64wordLE(a64LSU(uint32(lt.size), uint32(lt.V), uint32(opc), uint32(off/scale), uint32(rn), uint32(reg))), nil
|
||||
}
|
||||
if off >= -256 && off <= 255 {
|
||||
return a64wordLE(a64LSUnscaled(lt.size, lt.V, opc, off, rn, reg)), nil
|
||||
return a64wordLE(a64LSUnscaled(lt.size, lt.V, opc, int32(off), rn, reg)), nil
|
||||
}
|
||||
// Large offset: materialise the base in REGTMP (R27) the way the
|
||||
// toolchain does and access what remains.
|
||||
// toolchain does and access what remains. The ADD offsets from the
|
||||
// operand's own base register, [SP] and [Rn] alike.
|
||||
addImm, addShift, access, ok := arm64SplitOffset(off, scale)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s: offset %d out of range (literal pool not supported)", mnem, off)
|
||||
}
|
||||
return a64WordsLE(
|
||||
a64AddSub(1, 0, 0, addShift, uint32(addImm), 31, 27), // ADD $addImm<<shift, SP, R27
|
||||
a64AddSub(1, 0, 0, addShift, addImm, uint32(rn), 27), // ADD $addImm<<shift, Rn, R27
|
||||
a64LSU(uint32(lt.size), uint32(lt.V), uint32(opc), uint32(access/scale), 27, uint32(reg)),
|
||||
), nil
|
||||
}
|
||||
|
||||
// arm64SplitOffset decomposes an out-of-range frame offset for a REGTMP
|
||||
// base: an ADD (plain, or shifted left by 12) brings SP near the target and
|
||||
// the access covers what remains. ok is false when no decomposition exists
|
||||
// (offsets at or beyond 16 MiB, where the toolchain falls back to a literal
|
||||
// pool).
|
||||
func arm64SplitOffset(off int32, scale int32) (addImm, addShift uint32, access int32, ok bool) {
|
||||
// arm64SplitOffset decomposes an out-of-range offset for a REGTMP base: an
|
||||
// ADD (plain, or shifted left by 12) brings the base near the target and the
|
||||
// access covers what remains. ok is false when no decomposition exists
|
||||
// (negative offsets, or beyond 16 MiB, where the toolchain falls back to a
|
||||
// literal pool).
|
||||
func arm64SplitOffset(off int64, scale int64) (addImm, addShift uint32, access int64, ok bool) {
|
||||
if off < 0 {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
// Plain ADD: bring SP to within the largest scaled access.
|
||||
// Plain ADD: bring the base to within the largest scaled access.
|
||||
l := min(off, 4095*scale)
|
||||
l -= l % scale
|
||||
if a := off - l; a <= 4095 {
|
||||
@@ -987,12 +1087,15 @@ func arm64Imm64(op *ast.Operand) int64 {
|
||||
}
|
||||
|
||||
// arm64MemWithFrame resolves a memory operand, translating FP/SP pseudo-
|
||||
// registers via the frame mapping.
|
||||
func arm64MemWithFrame(op *ast.Operand, fi arm64FrameInfo) (rn int, off int32) {
|
||||
// registers via the frame mapping. The offset stays 64-bit: the AST carries
|
||||
// int64 displacements and truncating here would wrap offsets beyond 2^31
|
||||
// silently.
|
||||
func arm64MemWithFrame(op *ast.Operand, fi arm64FrameInfo) (rn int, off int64) {
|
||||
if op.Addr.Sym != nil && op.Addr.Sym.Pseudo != "" {
|
||||
return arm64ResolvePseudo(op.Addr.Sym, fi)
|
||||
base, pseudo := arm64ResolvePseudo(op.Addr.Sym, fi)
|
||||
return base, int64(pseudo)
|
||||
}
|
||||
return arm64RegNum(op.Addr.Base), int32(op.Addr.Offset)
|
||||
return arm64RegNum(op.Addr.Base), op.Addr.Offset
|
||||
}
|
||||
|
||||
// arm64Label returns the label name of an operand.
|
||||
@@ -1068,7 +1171,7 @@ func encodeARM64FPCmp(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, e
|
||||
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
// Check if first operand is #0 (compare with zero): FCMP $0.0, Fn.
|
||||
if isImmOperand(ops[0]) && immFromOperand(ops[0]) == 0 {
|
||||
if isImmOperand(ops[0]) && arm64Imm64(ops[0]) == 0 {
|
||||
rn := arm64RegNum(operandRegName(ops[1]))
|
||||
if rn < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
@@ -1105,8 +1208,11 @@ func encodeARM64FPCCmp(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte,
|
||||
if rm < 0 || rn < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
nzcv := uint32(immFromOperand(ops[3]))
|
||||
return a64wordLE(baseOp | uint32(rm)<<16 | cond<<12 | uint32(rn)<<5 | nzcv&0xF), nil
|
||||
nzcv := arm64Imm64(ops[3])
|
||||
if nzcv < 0 || nzcv > 0xF {
|
||||
return nil, fmt.Errorf("%s: nzcv %d out of range (0..15)", mnem, nzcv)
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(rm)<<16 | cond<<12 | uint32(rn)<<5 | uint32(nzcv)&0xF), nil
|
||||
}
|
||||
|
||||
// encodeARM64FPSel encodes a FP conditional select.
|
||||
@@ -1233,8 +1339,23 @@ func encodeARM64CRC32(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, e
|
||||
|
||||
// ---- Atomics encoding ----
|
||||
|
||||
// arm64ExclMem resolves the memory operand of an exclusive or atomic
|
||||
// instruction. These encodings have no immediate field: the toolchain
|
||||
// rejects `LDXR 8(R1), R2` as an illegal combination, so a non-zero offset is
|
||||
// reported rather than silently dropped (which would read the wrong address).
|
||||
func arm64ExclMem(mnem string, op *ast.Operand) (int, error) {
|
||||
rn, off := arm64MemWithFrame(op, arm64FrameInfo{})
|
||||
if rn < 0 {
|
||||
return 0, fmt.Errorf("invalid memory operand in %s", mnem)
|
||||
}
|
||||
if off != 0 {
|
||||
return 0, fmt.Errorf("%s: offset %d not supported, exclusive and atomic accesses take a plain (Rn) operand", mnem, off)
|
||||
}
|
||||
return rn, nil
|
||||
}
|
||||
|
||||
// encodeARM64Excl encodes an exclusive load/store instruction.
|
||||
// LDXR (Rn), Rt → LDXR Rt, [Rn] (2 operands: mem, reg or reg, mem)
|
||||
// LDXR (Rn), Rt → LDXR Rt, [Rn] (2 operands: mem, reg)
|
||||
// STXR Rs, (Rn), Rt → STXR Rs, Rt, [Rn] (3 operands: Rs, mem, Rt-status)
|
||||
func encodeARM64Excl(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||
// LDXR/STXR have different operand forms.
|
||||
@@ -1244,9 +1365,12 @@ func encodeARM64Excl(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
if len(ops) != 2 {
|
||||
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rn, _ := arm64MemWithFrame(ops[0], arm64FrameInfo{})
|
||||
rn, err := arm64ExclMem(mnem, ops[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rt := arm64RegNum(operandRegName(ops[1]))
|
||||
if rn < 0 || rt < 0 {
|
||||
if rt < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(rn)<<5 | uint32(rt)), nil
|
||||
@@ -1256,9 +1380,15 @@ func encodeARM64Excl(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
return nil, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rs := arm64RegNum(operandRegName(ops[0]))
|
||||
rn, _ := arm64MemWithFrame(ops[1], arm64FrameInfo{})
|
||||
if rs < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
rn, err := arm64ExclMem(mnem, ops[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rt := arm64RegNum(operandRegName(ops[2]))
|
||||
if rs < 0 || rn < 0 || rt < 0 {
|
||||
if rt < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(rs)<<16 | uint32(rn)<<5 | uint32(rt)), nil
|
||||
@@ -1272,9 +1402,15 @@ func encodeARM64LSEAtom(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte,
|
||||
return nil, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rs := arm64RegNum(operandRegName(ops[0]))
|
||||
rn, _ := arm64MemWithFrame(ops[1], arm64FrameInfo{})
|
||||
if rs < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
rn, err := arm64ExclMem(mnem, ops[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rt := arm64RegNum(operandRegName(ops[2]))
|
||||
if rs < 0 || rn < 0 || rt < 0 {
|
||||
if rt < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(rs)<<16 | uint32(rn)<<5 | uint32(rt)), nil
|
||||
@@ -1283,43 +1419,25 @@ func encodeARM64LSEAtom(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte,
|
||||
// ---- Bitfield/EXTR encoding ----
|
||||
|
||||
// encodeARM64Bitfield encodes a bitfield instruction.
|
||||
// ASR/LSL/LSR/ROR $shamt, Rn, Rd → 3 operands: $imm, Rn, Rd
|
||||
// BFI/BFXIL/SBFM/UBFM $immr, Rn, $imms, Rd → 4 operands
|
||||
func encodeARM64Bitfield(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||
isShift := mnem == "ASR" || mnem == "ASRW" || mnem == "LSL" || mnem == "LSLW" ||
|
||||
mnem == "LSR" || mnem == "LSRW" || mnem == "ROR" || mnem == "RORW"
|
||||
|
||||
if isShift {
|
||||
// ASR $shamt, Rn, Rd → SBFM with immr=shamt, imms=31/63
|
||||
if len(ops) != 3 {
|
||||
return nil, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
shamt := int(immFromOperand(ops[0]))
|
||||
rn := arm64RegNum(operandRegName(ops[1]))
|
||||
rd := arm64RegNum(operandRegName(ops[2]))
|
||||
if rn < 0 || rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
// ASR: SBFM with immr=shamt, imms=31(32-bit) or 63(64-bit)
|
||||
is64 := mnem == "ASR"
|
||||
imms := 31
|
||||
if is64 {
|
||||
imms = 63
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(shamt)<<16 | uint32(imms)<<10 | uint32(rn)<<5 | uint32(rd)), nil
|
||||
}
|
||||
|
||||
// BFI/BFXIL/SBFM/UBFM: 4 operands ($immr, Rn, $imms, Rd)
|
||||
if len(ops) != 4 {
|
||||
return nil, fmt.Errorf("%s expects 4 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
immr := int(immFromOperand(ops[0]))
|
||||
immr := arm64Imm64(ops[0])
|
||||
rn := arm64RegNum(operandRegName(ops[1]))
|
||||
imms := int(immFromOperand(ops[2]))
|
||||
imms := arm64Imm64(ops[2])
|
||||
rd := arm64RegNum(operandRegName(ops[3]))
|
||||
if rn < 0 || rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
// The toolchain rejects bit numbers at or above the operand width, which
|
||||
// sf (bit 31 of the base) selects: 64 when set, 32 otherwise.
|
||||
width := uint32(32) << (baseOp >> 31 & 1)
|
||||
if immr < 0 || uint32(immr) >= width || imms < 0 || uint32(imms) >= width {
|
||||
return nil, fmt.Errorf("%s: bit number out of range (immr=%d imms=%d, width=%d)", mnem, immr, imms, width)
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(immr)<<16 | uint32(imms)<<10 | uint32(rn)<<5 | uint32(rd)), nil
|
||||
}
|
||||
|
||||
@@ -1329,13 +1447,19 @@ func encodeARM64Extr(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
if len(ops) != 4 {
|
||||
return nil, fmt.Errorf("%s expects 4 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
lsb := int(immFromOperand(ops[0]))
|
||||
lsb := arm64Imm64(ops[0])
|
||||
rm := arm64RegNum(operandRegName(ops[1]))
|
||||
rn := arm64RegNum(operandRegName(ops[2]))
|
||||
rd := arm64RegNum(operandRegName(ops[3]))
|
||||
if rm < 0 || rn < 0 || rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
// The imms field is 6 bits and must stay below the operand width, which
|
||||
// sf (bit 31 of the base) selects: 64 when set, 32 otherwise.
|
||||
width := int64(32) << (baseOp >> 31 & 1)
|
||||
if lsb < 0 || lsb >= width {
|
||||
return nil, fmt.Errorf("%s: bit number %d out of range (width=%d)", mnem, lsb, width)
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(rm)<<16 | uint32(lsb)<<10 | uint32(rn)<<5 | uint32(rd)), nil
|
||||
}
|
||||
|
||||
@@ -1367,7 +1491,7 @@ func AssembleFileARM64(f *ast.File) (*Image, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
img := &Image{Symbols: map[string]int{}}
|
||||
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
||||
for _, d := range f.Decls {
|
||||
t, ok := d.(*ast.Text)
|
||||
if !ok {
|
||||
|
||||
Reference in New Issue
Block a user