fix(arm64): encode shifts, divides and multiplies and align sizes with emission
Assisted-by: GLM 5.3
This commit is contained in:
+1
-1
@@ -145,7 +145,7 @@ func arm64Curated() []Instr {
|
||||
for _, op := range []string{
|
||||
"LDAXR", "LDAXRB", "LDAXRH", "LDAXRW", "STXR", "STXRB", "STXRH", "STXRW",
|
||||
"LDAR", "LDARB", "LDARH", "LDARW", "STLR", "STLRB", "STLRH", "STLRW",
|
||||
"LDADD", "LDCLR", "LDEOR", "LDSET", "SWP", "CAS", "CASAL", "CASL", "CASAL",
|
||||
"LDADD", "LDCLR", "LDEOR", "LDSET", "SWP", "CAS", "CASAL", "CASL",
|
||||
} {
|
||||
t = append(t, i(op, "Atomic memory operation"))
|
||||
}
|
||||
|
||||
+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 {
|
||||
|
||||
+54
-68
@@ -27,6 +27,8 @@ package asm
|
||||
// Uncond-branch 0x6B<<25 | opc<<21 | Rn<<5 | Rd (BR/BLR/RET)
|
||||
// ADR/ADRP p<<31 | 0x10<<24 | immlo<<29 | immhi<<5 | Rd
|
||||
|
||||
import "maps"
|
||||
|
||||
// arm64RegNum returns the 5-bit register number for an AArch64 register name:
|
||||
// R0-R30 (integer), F0-F31 (floating point), and the ABI aliases the
|
||||
// runtime's assembly uses. Returns -1 for an unrecognised name.
|
||||
@@ -262,19 +264,15 @@ type a64Format uint8
|
||||
|
||||
const (
|
||||
a64FDPSR a64Format = iota // data-processing (shifted register): ADD, SUB, AND, ORR, EOR, etc.
|
||||
a64FDPIR // data-processing (immediate): ADD/SUB $imm
|
||||
a64FLogImm // logical (immediate): AND/ORR/EOR $imm
|
||||
a64FMovWide // move wide: MOVZ, MOVN, MOVK
|
||||
a64FLSU // load/store (unsigned immediate, scaled)
|
||||
a64FLSUnscaled // load/store (unscaled immediate)
|
||||
a64FLSPair // load/store pair
|
||||
a64FBranch // unconditional branch (B/BL)
|
||||
a64FBranchCond // conditional branch (B.cond)
|
||||
a64FUncondBranch // unconditional branch register (BR/BLR/RET)
|
||||
a64FADR // ADR/ADRP
|
||||
a64FEXTR // EXTR
|
||||
a64FBitfield // bitfield: BFI/BFXIL/SBFM/UBFM/BFM
|
||||
a64FSystem // system: NOP, BRK, etc.
|
||||
a64FShift // shifts: LSL/LSR/ASR alias SBFM/UBFM, ROR aliases EXTR; register forms are two-source
|
||||
a64FDPR4 // data-processing 4-register: MADD/MSUB, Ra in bits 14:10
|
||||
a64FFP3 // FP 3-operand (Rm, Rn, Rd): FADD, FSUB, FMUL, FDIV, etc.
|
||||
a64FFPUnary // FP unary (Rn, Rd): FMOV, FABS, FNEG, FSQRT, FCVT, FRINT*
|
||||
a64FFP4 // FP 4-operand FMA (Ra, Rm, Rn, Rd): FMADD, FMSUB, etc.
|
||||
@@ -282,7 +280,6 @@ const (
|
||||
a64FFPCCmp // FP conditional compare (Rm, Rn, nzcv, cond): FCCMP, FCCMPE
|
||||
a64FFPCvt // FP↔integer conversion: FCVTZS, SCVTF, etc.
|
||||
a64FFPSel // FP conditional select (Rm, Rn, Rd, cond): FCSEL
|
||||
a64FFMovGR // FMOV between GP and FP registers
|
||||
a64FCRC32 // CRC32
|
||||
a64FCSEL // conditional select: CSEL, CSINC, CSINV, CSNEG
|
||||
a64FExcl // exclusive load/store: LDXR, STXR, LDAXR, STLXR
|
||||
@@ -332,30 +329,14 @@ func init() {
|
||||
"ANDSW": 0<<31 | 3<<29 | 0x0a<<24,
|
||||
"BICS": 1<<31 | 3<<29 | 0x0a<<24 | 1<<21,
|
||||
"BICSW": 0<<31 | 3<<29 | 0x0a<<24 | 1<<21,
|
||||
// Shift
|
||||
"LSL": 1<<31 | 0<<29 | 0x0a<<24, // alias of UBFM
|
||||
"LSLW": 0<<31 | 0<<29 | 0x0a<<24,
|
||||
"LSR": 1<<31 | 0<<29 | 0x0a<<24,
|
||||
"LSRW": 0<<31 | 0<<29 | 0x0a<<24,
|
||||
"ASR": 1<<31 | 0<<29 | 0x0a<<24,
|
||||
"ASRW": 0<<31 | 0<<29 | 0x0a<<24,
|
||||
"ROR": 1<<31 | 0<<29 | 0x0a<<24,
|
||||
"RORW": 0<<31 | 0<<29 | 0x0a<<24,
|
||||
// Multiply
|
||||
"MADD": 1<<31 | 0<<29 | 0x1b<<24 | 0<<21,
|
||||
"MADDW": 0<<31 | 0<<29 | 0x1b<<24 | 0<<21,
|
||||
"MSUB": 1<<31 | 0<<29 | 0x1b<<24 | 1<<21,
|
||||
"MSUBW": 0<<31 | 0<<29 | 0x1b<<24 | 1<<21,
|
||||
// Divide
|
||||
"SDIV": 1<<31 | 0<<29 | 0x0d<<24,
|
||||
"SDIVW": 0<<31 | 0<<29 | 0x0d<<24,
|
||||
"UDIV": 1<<31 | 0<<29 | 0x0d<<24 | 1<<10,
|
||||
"UDIVW": 0<<31 | 0<<29 | 0x0d<<24 | 1<<10,
|
||||
// CRC
|
||||
"CRC32B": 0<<31 | 0<<29 | 0x1b<<24 | 4<<10,
|
||||
"CRC32H": 0<<31 | 0<<29 | 0x1b<<24 | 5<<10,
|
||||
"CRC32W": 0<<31 | 0<<29 | 0x1b<<24 | 6<<10,
|
||||
"CRC32X": 1<<31 | 0<<29 | 0x1b<<24 | 7<<10,
|
||||
// Divide (data-processing 2 source): the opcode occupies bits 15:10
|
||||
// of the 0xd6<<21 fixed field, UDIV=0b0010 and SDIV=0b0011 (ARM ARM
|
||||
// "Data-processing (2 source)"; the toolchain spells them OPDP2(2)
|
||||
// and OPDP2(3)). sf=1 selects the X forms.
|
||||
"SDIV": 1<<31 | 0xd6<<21 | 3<<10,
|
||||
"SDIVW": 0<<31 | 0xd6<<21 | 3<<10,
|
||||
"UDIV": 1<<31 | 0xd6<<21 | 2<<10,
|
||||
"UDIVW": 0<<31 | 0xd6<<21 | 2<<10,
|
||||
// Conditional select
|
||||
"CSEL": 1<<31 | 0<<29 | 0x1d<<24 | 0<<10,
|
||||
"CSELW": 0<<31 | 0<<29 | 0x1d<<24 | 0<<10,
|
||||
@@ -385,14 +366,37 @@ func init() {
|
||||
a64InstrTable["MOV"] = a64Enc{format: a64FDPSR, op: dpsr["ORR"]}
|
||||
a64InstrTable["MOVW"] = a64Enc{format: a64FDPSR, op: dpsr["ORRW"]}
|
||||
|
||||
// ---- data-processing (immediate) ----
|
||||
// ADD/SUB $imm, Rn, Rd
|
||||
a64InstrTable["ADDImm"] = a64Enc{format: a64FDPIR, op: 1<<31 | 0<<30 | 0<<29 | 0x11<<24}
|
||||
a64InstrTable["ADDWImm"] = a64Enc{format: a64FDPIR, op: 0<<31 | 0<<30 | 0<<29 | 0x11<<24}
|
||||
a64InstrTable["SUBImm"] = a64Enc{format: a64FDPIR, op: 1<<31 | 1<<30 | 0<<29 | 0x11<<24}
|
||||
a64InstrTable["SUBWImm"] = a64Enc{format: a64FDPIR, op: 0<<31 | 1<<30 | 0<<29 | 0x11<<24}
|
||||
a64InstrTable["ADDSImm"] = a64Enc{format: a64FDPIR, op: 1<<31 | 0<<30 | 1<<29 | 0x11<<24}
|
||||
a64InstrTable["SUBSImm"] = a64Enc{format: a64FDPIR, op: 1<<31 | 1<<30 | 1<<29 | 0x11<<24}
|
||||
// ---- shifts ----
|
||||
// The mnemonic serves both forms: with an immediate the aliases of the
|
||||
// data-processing (immediate) group apply (ARM ARM "Shifts"), with a
|
||||
// register the data-processing (2 source) LSLV/LSRV/ASRV/RORV. The op
|
||||
// field carries the immediate-alias base; encodeARM64Shift derives both
|
||||
// it and the two-source opcode. Identities, W = 64 (X) or 32 (W):
|
||||
//
|
||||
// LSL $sh, Rn, Rd = UBFM Rd, Rn, #(-sh) mod W, #(W-1)-sh
|
||||
// LSR $sh, Rn, Rd = UBFM Rd, Rn, #sh, #(W-1)
|
||||
// ASR $sh, Rn, Rd = SBFM Rd, Rn, #sh, #(W-1)
|
||||
// ROR $sh, Rn, Rd = EXTR Rd, Rn, Rn, #sh
|
||||
shifts := map[string]a64Enc{
|
||||
"LSL": {format: a64FShift, op: 1<<31 | 2<<29 | 0x26<<23 | 1<<22}, // UBFM X
|
||||
"LSLW": {format: a64FShift, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}, // UBFM W
|
||||
"LSR": {format: a64FShift, op: 1<<31 | 2<<29 | 0x26<<23 | 1<<22}, // UBFM X
|
||||
"LSRW": {format: a64FShift, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}, // UBFM W
|
||||
"ASR": {format: a64FShift, op: 1<<31 | 0<<29 | 0x26<<23 | 1<<22}, // SBFM X
|
||||
"ASRW": {format: a64FShift, op: 0<<31 | 0<<29 | 0x26<<23 | 0<<22}, // SBFM W
|
||||
"ROR": {format: a64FShift, op: 1<<31 | 0x27<<23 | 1<<22}, // EXTR X
|
||||
"RORW": {format: a64FShift, op: 0<<31 | 0x27<<23 | 0<<22}, // EXTR W
|
||||
}
|
||||
maps.Copy(a64InstrTable, shifts)
|
||||
|
||||
// ---- multiply accumulate ----
|
||||
// MADD/MSUB Rm, Ra, Rn, Rd: sf 00 11011 o0(15) Rm Ra Rn Rd. The
|
||||
// toolchain's optab has no shorter row, so all four operands are
|
||||
// mandatory, and Ra is the SECOND operand.
|
||||
a64InstrTable["MADD"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24}
|
||||
a64InstrTable["MADDW"] = a64Enc{format: a64FDPR4, op: 0<<31 | 0x1b<<24}
|
||||
a64InstrTable["MSUB"] = a64Enc{format: a64FDPR4, op: 1<<31 | 0x1b<<24 | 1<<15}
|
||||
a64InstrTable["MSUBW"] = a64Enc{format: a64FDPR4, op: 0<<31 | 0x1b<<24 | 1<<15}
|
||||
|
||||
// ---- move wide ----
|
||||
// MOVZ/MOVN/MOVK
|
||||
@@ -407,22 +411,9 @@ func init() {
|
||||
a64InstrTable["ADR"] = a64Enc{format: a64FADR, op: 0}
|
||||
a64InstrTable["ADRP"] = a64Enc{format: a64FADR, op: 1}
|
||||
|
||||
// ---- load/store (unsigned immediate) ----
|
||||
a64InstrTable["MOVD"] = a64Enc{format: a64FLSU, op: 3<<30 | 7<<27 | 1<<22} // LDR 64-bit
|
||||
a64InstrTable["MOVWU"] = a64Enc{format: a64FLSU, op: 2<<30 | 7<<27 | 1<<22} // LDR 32-bit unsigned
|
||||
a64InstrTable["MOVHU"] = a64Enc{format: a64FLSU, op: 1<<30 | 7<<27 | 1<<22} // LDRH unsigned
|
||||
a64InstrTable["MOVBU"] = a64Enc{format: a64FLSU, op: 0<<30 | 7<<27 | 1<<22} // LDRB unsigned
|
||||
a64InstrTable["MOVW"] = a64Enc{format: a64FLSU, op: 2<<30 | 7<<27 | 2<<22} // LDRSW (signed 32→64)
|
||||
a64InstrTable["MOVH"] = a64Enc{format: a64FLSU, op: 1<<30 | 7<<27 | 2<<22} // LDRSH (signed half)
|
||||
a64InstrTable["MOVB"] = a64Enc{format: a64FLSU, op: 0<<30 | 7<<27 | 2<<22} // LDRSB (signed byte)
|
||||
a64InstrTable["FMOVS"] = a64Enc{format: a64FLSU, op: 2<<30 | 7<<27 | 1<<26 | 1<<22} // FLDR 32-bit FP
|
||||
a64InstrTable["FMOVD"] = a64Enc{format: a64FLSU, op: 3<<30 | 7<<27 | 1<<26 | 1<<22} // FLDR 64-bit FP
|
||||
|
||||
// Store opcodes (load ^ (1<<22)):
|
||||
// STR 64-bit: size=3, V=0, opc=00 → 3<<30 | 7<<27 | 0<<22
|
||||
// STR 32-bit: size=2, V=0, opc=00 → 2<<30 | 7<<27 | 0<<22
|
||||
// STRH: size=1, V=0, opc=00 → 1<<30 | 7<<27 | 0<<22
|
||||
// STRB: size=0, V=0, opc=00 → 0<<30 | 7<<27 | 0<<22
|
||||
// Load/store mnemonics never enter this table: the MOV pseudo-instruction
|
||||
// dispatch handles them through a64LoadTable, which also carries the store
|
||||
// opcode (integer and FP stores both use opc=00, differing only in V).
|
||||
|
||||
// ---- branches ----
|
||||
a64InstrTable["B"] = a64Enc{format: a64FBranch, op: 0<<31 | 5<<26}
|
||||
@@ -445,10 +436,8 @@ func init() {
|
||||
a64InstrTable["RET"] = a64Enc{format: a64FUncondBranch, op: 0x6B<<25 | 2<<21}
|
||||
|
||||
// ---- system ----
|
||||
a64InstrTable["NOP"] = a64Enc{format: a64FSystem, op: a64NOP}
|
||||
a64InstrTable["NOOP"] = a64Enc{format: a64FSystem, op: a64NOP}
|
||||
a64InstrTable["BRK"] = a64Enc{format: a64FSystem, op: 0xd4200000}
|
||||
a64InstrTable["UNDEF"] = a64Enc{format: a64FSystem, op: a64BRK(0)}
|
||||
// NOP/NOOP/UNDEF are spelled out in encodeARM64Instr's pseudo switch,
|
||||
// so they carry no table entry; a64NOP and a64BRK are the encoders.
|
||||
|
||||
// ---- EXTR ----
|
||||
a64InstrTable["EXTR"] = a64Enc{format: a64FEXTR, op: 1<<31 | 0x27<<23 | 1<<22}
|
||||
@@ -549,8 +538,8 @@ func init() {
|
||||
a64InstrTable[m] = a64Enc{format: a64FFPCvt, op: op}
|
||||
}
|
||||
|
||||
// ---- FMOV between GP and FP registers ----
|
||||
a64InstrTable["FMOVGR"] = a64Enc{format: a64FFMovGR, op: 0x1e260000} // placeholder, actual encoding depends on direction
|
||||
// FMOV between GP and FP registers needs no table entry: the MOV
|
||||
// pseudo-instruction dispatches it by operand class (encodeARM64RegMove).
|
||||
|
||||
// ---- conditional select: CSEL, CSINC, CSINV, CSNEG ----
|
||||
csel := map[string]uint32{
|
||||
@@ -642,14 +631,11 @@ var a64LoadTable = map[string]a64LSType{
|
||||
"FMOVD": {3, 1, 1}, // LDR D (64-bit FP)
|
||||
}
|
||||
|
||||
// a64StoreOpc returns the store opc for a given load type.
|
||||
// For integer: store opc = 00 (the load opc bits cleared).
|
||||
// For FP: store opc = 00 (same pattern).
|
||||
// a64StoreOpc returns the store opc for a given load type: integer and FP
|
||||
// stores both encode opc=00 (the load's signedness bit sits in opc[1], which
|
||||
// the store form clears; FP registers are selected by V, not opc).
|
||||
func a64StoreOpc(t a64LSType) int {
|
||||
if t.V == 1 {
|
||||
return 0 // FP store
|
||||
}
|
||||
return 0 // integer store
|
||||
return 0
|
||||
}
|
||||
|
||||
// arm64RegClass discriminates integer (R), floating-point (F) registers for
|
||||
|
||||
@@ -611,3 +611,312 @@ TEXT ·f(SB), NOSPLIT, $0-0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// arm64Words assembles a single NOSPLIT leaf body and returns its words.
|
||||
func arm64Words(t *testing.T, body string) []uint32 {
|
||||
t.Helper()
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileARM64(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileARM64: %v", err)
|
||||
}
|
||||
return leWords(img.Code)
|
||||
}
|
||||
|
||||
// TestArm64ShiftEncodings pins the shift words against `go tool asm -S`
|
||||
// output (Go 1.27, arm64): immediate forms alias SBFM/UBFM with ROR as EXTR,
|
||||
// register forms are the two-source LSLV/LSRV/ASRV/RORV.
|
||||
func TestArm64ShiftEncodings(t *testing.T) {
|
||||
got := arm64Words(t, "\tLSL $4, R0, R1\n\tLSR $8, R0, R2\n\tASR $4, R0, R3\n\tROR $12, R0, R4\n"+
|
||||
"\tLSLW $4, R0, R5\n\tLSRW $8, R0, R6\n\tASRW $4, R0, R7\n\tRORW $12, R0, R8\n")
|
||||
want := []uint32{
|
||||
0xd37cec01, // LSL $4 = UBFM X1, X0, #60, #59
|
||||
0xd348fc02, // LSR $8 = UBFM X2, X0, #8, #63
|
||||
0x9344fc03, // ASR $4 = SBFM X3, X0, #4, #63
|
||||
0x93c03004, // ROR $12 = EXTR X4, X0, X0, #12
|
||||
0x531c6c05, // LSLW $4 = UBFM W5, W0, #28, #27
|
||||
0x53087c06, // LSRW $8 = UBFM W6, W0, #8, #31
|
||||
0x13047c07, // ASRW $4 = SBFM W7, W0, #4, #31
|
||||
0x13803008, // RORW $12 = EXTR W8, W0, W0, #12
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("imm shift word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
|
||||
got = arm64Words(t, "\tLSL R9, R0, R10\n\tLSR R9, R0, R11\n\tASR R9, R0, R12\n\tROR R9, R0, R13\n"+
|
||||
"\tLSLW R9, R0, R14\n\tLSRW R9, R0, R15\n\tASRW R9, R0, R16\n\tRORW R9, R0, R17\n")
|
||||
want = []uint32{
|
||||
0x9ac9200a, // LSLV X10, X0, X9
|
||||
0x9ac9240b, // LSRV X11, X0, X9
|
||||
0x9ac9280c, // ASRV X12, X0, X9
|
||||
0x9ac92c0d, // RORV X13, X0, X9
|
||||
0x1ac9200e, // LSLV W14, W0, W9
|
||||
0x1ac9240f, // LSRV W15, W0, W9
|
||||
0x1ac92810, // ASRV W16, W0, W9
|
||||
0x1ac92c11, // RORV W17, W0, W9
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("reg shift word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Two-operand spellings fold to Rn = Rd.
|
||||
got = arm64Words(t, "\tLSL $4, R1\n\tLSR R9, R1\n\tASR $4, R1\n\tROR R9, R1\n\tLSLW $4, R1\n\tRORW R9, R1\n")
|
||||
want = []uint32{
|
||||
0xd37cec21, // LSL $4, R1 = UBFM X1, X1, #60, #59
|
||||
0x9ac92421, // LSRV X1, X1, X9
|
||||
0x9344fc21, // ASR $4, R1 = SBFM X1, X1, #4, #63
|
||||
0x9ac92c21, // RORV X1, X1, X9
|
||||
0x531c6c21, // LSLW $4, R1 = UBFM W1, W1, #28, #27
|
||||
0x1ac92c21, // RORV W1, W1, W9
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("2op shift word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64ShiftRangeErrors: the toolchain reports "illegal bit number" for
|
||||
// shift amounts at or above the operand width.
|
||||
func TestArm64ShiftRangeErrors(t *testing.T) {
|
||||
for _, src := range []string{
|
||||
"\tLSL $64, R0, R1\n",
|
||||
"\tLSRW $32, R0, R1\n",
|
||||
"\tRORW $32, R0, R1\n",
|
||||
"\tASR $-1, R0, R1\n",
|
||||
} {
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+src+"\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Errorf("%s: expected an error, got none", src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64DivEncodings pins SDIV/UDIV in both widths: the 2-source opcode
|
||||
// field (bits 15:10 of the 0xd6<<21 fixed field) is UDIV=0b0010, SDIV=0b0011.
|
||||
func TestArm64DivEncodings(t *testing.T) {
|
||||
got := arm64Words(t, "\tSDIV R1, R2, R3\n\tUDIV R1, R2, R3\n\tSDIVW R1, R2, R3\n\tUDIVW R1, R2, R3\n")
|
||||
want := []uint32{
|
||||
0x9ac10c43, // SDIV X3, X2, X1
|
||||
0x9ac10843, // UDIV X3, X2, X1
|
||||
0x1ac10c43, // SDIV W3, W2, W1
|
||||
0x1ac10843, // UDIV W3, W2, W1
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("div word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64MAddSub pins the four-operand MADD/MSUB words (Rm, Ra, Rn, Rd,
|
||||
// with Ra in bits 14:10) and rejects the shorter spellings the toolchain
|
||||
// also rejects.
|
||||
func TestArm64MAddSub(t *testing.T) {
|
||||
got := arm64Words(t, "\tMADD R1, R2, R3, R4\n\tMSUB R1, R2, R3, R4\n\tMADDW R1, R2, R3, R5\n\tMSUBW R1, R2, R3, R5\n")
|
||||
want := []uint32{
|
||||
0x9b010864, // MADD X4, X3, X1, X2 (Rm=1, Ra=2, Rn=3)
|
||||
0x9b018864, // MSUB X4, X3, X1, X2
|
||||
0x1b010865, // MADD W5, W3, W1, W2
|
||||
0x1b018865, // MSUB W5, W3, W1, W2
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("madd word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
|
||||
// The accumulate operand is mandatory: 2- and 3-operand forms error
|
||||
// rather than silently reading R0 or ZR as the accumulator.
|
||||
for _, body := range []string{
|
||||
"\tMADD R1, R2\n",
|
||||
"\tMADD R1, R2, R3\n",
|
||||
"\tMSUBW R1, R2, R3\n",
|
||||
} {
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Errorf("%s: expected an error, got none", body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64MovImmWidth pins the immediate classifications whose size pass
|
||||
// once disagreed with the encoder: negative and 0xFFFFFFFF W values go
|
||||
// through MOVN after 32-bit truncation, and 3- to 4-chunk constants expand
|
||||
// to one word per non-zero chunk.
|
||||
func TestArm64MovImmWidth(t *testing.T) {
|
||||
got := arm64Words(t, "\tMOVW $-1, R0\n\tMOVW $0xFFFFFFFF, R3\n")
|
||||
want := []uint32{
|
||||
0x12800000, // MOVN W0, #0
|
||||
0x12800003, // MOVN W3, #0
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("movw word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
body string
|
||||
words int
|
||||
}{
|
||||
{"\tMOVD $0x0001000200030000, R2\n", 3}, // three chunks
|
||||
{"\tMOVD $0x0001000200030004, R1\n", 4}, // four chunks
|
||||
{"\tMOVW $-1, R0\n", 1}, // MOVN after truncation
|
||||
} {
|
||||
if got := arm64Words(t, tt.body); len(got) != tt.words+1 {
|
||||
t.Errorf("%s: %d words, want %d (including RET)", tt.body, len(got), tt.words+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64ExclOffsetErrors: exclusive and atomic encodings carry no
|
||||
// immediate field, so a non-zero offset is rejected the way the toolchain
|
||||
// reports "illegal combination" for it, never silently dropped.
|
||||
func TestArm64ExclOffsetErrors(t *testing.T) {
|
||||
for _, body := range []string{
|
||||
"\tLDXR 8(R1), R2\n",
|
||||
"\tLDAXR 8(R1), R2\n",
|
||||
"\tSTXR R3, 8(R1), R4\n",
|
||||
"\tSTLXR R3, 8(R1), R4\n",
|
||||
"\tCASD R3, 8(R1), R4\n",
|
||||
"\tLDADDD R3, 8(R1), R4\n",
|
||||
} {
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Errorf("%s: expected an error, got none", body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64ExclNoOffset pins the plain (Rn) forms. gasm parses the store
|
||||
// with the status register first (ARM ARM order); go tool asm parses the
|
||||
// same text with the data register first, so the two spellings differ and
|
||||
// the store word below is gasm's own.
|
||||
func TestArm64ExclNoOffset(t *testing.T) {
|
||||
got := arm64Words(t, "\tLDXR (R1), R2\n\tSTXR R3, (R1), R4\n")
|
||||
want := []uint32{
|
||||
0xc85f7c22, // LDXR X2, [X1]
|
||||
0xc8037c24, // STXR W3, X4, [X1] with Rs = R3, Rt = R4
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("excl word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64AddSubImmRange: immediates that cannot ride the imm12 field are
|
||||
// rejected instead of wrapping through int32.
|
||||
func TestArm64AddSubImmRange(t *testing.T) {
|
||||
for _, body := range []string{
|
||||
"\tADD $0x100000000, R0, R1\n",
|
||||
"\tSUB $-0x100000000, R0, R1\n",
|
||||
"\tCMP $0x100000000, R0\n",
|
||||
} {
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Errorf("%s: expected an error, got none", body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64LargeRegisterOffset pins the large-offset path for a register
|
||||
// base: the ADD offsets from the operand's own base, not from SP, matching
|
||||
// the toolchain's `ADD $(256<<12), R2, R27; MOVD (R27), R3`.
|
||||
func TestArm64LargeRegisterOffset(t *testing.T) {
|
||||
got := arm64Words(t, "\tMOVD 0x100000(R2), R3\n\tMOVD R3, 0x100000(R2)\n")
|
||||
want := []uint32{
|
||||
0x9144005b, // ADD $(256<<12), R2, R27
|
||||
0xf9400363, // MOVD (R27), R3
|
||||
0x9144005b, // ADD $(256<<12), R2, R27
|
||||
0xf9000363, // MOVD R3, (R27)
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("large offset word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64LargeFrameSpadj checks the stack-adjustment boundaries of a frame
|
||||
// whose autosize must be materialised into REGTMP: $5000 rounds the autosize
|
||||
// to 5024, so the prologue is [MOVD $5024, R27][SUB R27, RSP, R20][STP][ADD
|
||||
// R20, SP][SUB $8] and SP moves only at its fourth word, while the RET's
|
||||
// epilogue is [LDP][MOVD $5024, R27][ADD R27, RSP, RSP] before the final
|
||||
// RET. These PCs feed the DWARF CFA rules and the goobj stack maps.
|
||||
func TestArm64LargeFrameSpadj(t *testing.T) {
|
||||
f, errs := parser.Parse("frame_arm64.s", "#include \"textflag.h\"\n\nTEXT ·framed(SB), $5000-0\n\tCALL ·other(SB)\n\tRET\n\nTEXT ·other(SB), NOSPLIT, $0\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileARM64(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileARM64: %v", err)
|
||||
}
|
||||
fn := img.Funcs[0]
|
||||
// autosize 5024: class-2 guard of 6 words (24 bytes), a 5-word prologue
|
||||
// whose ADD R20, SP sits at byte 8 inside it, a one-instruction body,
|
||||
// then a 3-word epilogue before the final RET.
|
||||
wantSpadj := []SpadjStep{{PC: 24 + 12, Value: 5024}, {PC: 24 + 20 + 4 + 12, Value: 0}}
|
||||
if len(fn.Spadj) != len(wantSpadj) {
|
||||
t.Fatalf("spadj = %v, want %v", fn.Spadj, wantSpadj)
|
||||
}
|
||||
for i := range wantSpadj {
|
||||
if fn.Spadj[i] != wantSpadj[i] {
|
||||
t.Errorf("spadj[%d] = %v, want %v", i, fn.Spadj[i], wantSpadj[i])
|
||||
}
|
||||
}
|
||||
// The words those PCs point between: the prologue's ADD R20, SP at byte
|
||||
// 36, and the epilogue's materialised ADD R27, RSP, RSP right before the
|
||||
// final RET at byte 60.
|
||||
words := leWords(img.Code[fn.Offset : fn.Offset+fn.Size])
|
||||
if got := words[(24+12)/4]; got != 0x9100029f {
|
||||
t.Errorf("prologue word at byte 36 = %08x, want 9100029f (ADD R20, SP)", got)
|
||||
}
|
||||
if got := words[(24+20+4+8)/4]; got != 0x8b3b63ff {
|
||||
t.Errorf("epilogue word at byte 56 = %08x, want 8b3b63ff (ADD R27, RSP, RSP)", got)
|
||||
}
|
||||
if got := words[(24+20+4+12)/4]; got != 0xd65f03c0 {
|
||||
t.Errorf("final RET word at byte 60 = %08x, want d65f03c0", got)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-6
@@ -258,22 +258,30 @@ func arm64PrologueSpadjPC(fi arm64FrameInfo) int {
|
||||
if fi.autosize <= 0xf0 {
|
||||
return 4 // MOVD.W instruction decrements SP
|
||||
}
|
||||
return 8 // SUB + STP + MOVD (3 instructions, SP updated at the MOVD)
|
||||
// Large frame: [SUB words][STP][ADD R20, SP]; SP moves at the ADD, whose
|
||||
// position depends on how many words the SUB itself took (immediate,
|
||||
// shifted immediate, or a materialised REGTMP sequence).
|
||||
return 4 * (len(arm64SubImmWords(uint32(fi.autosize), 20)) + 1)
|
||||
}
|
||||
|
||||
// arm64ReturnEpilogueLen returns the byte length of the RET's epilogue up to
|
||||
// (but not including) the final RET instruction.
|
||||
// (but not including) the final RET instruction. The ADD sequences share the
|
||||
// prologue's immediate ladder, so their length is read from the same helper
|
||||
// rather than assumed: a materialised autosize costs its MOV words plus the
|
||||
// ADD itself.
|
||||
func arm64ReturnEpilogueLen(fi arm64FrameInfo) int {
|
||||
if fi.autosize == 0 {
|
||||
return 0
|
||||
}
|
||||
if fi.leaf {
|
||||
return 8 // ADD + ADD
|
||||
return 4 * (len(arm64AddImmWords(uint32(fi.autosize-8), 29)) +
|
||||
len(arm64AddImmWords(uint32(fi.autosize), 31)))
|
||||
}
|
||||
if fi.autosize <= 0xf0 {
|
||||
return 8 // LDR + LDR.P
|
||||
}
|
||||
return 8 // LDP + ADD
|
||||
// LDP + the ADD ladder that deallocates the frame.
|
||||
return 4 + 4*len(arm64AddImmWords(uint32(fi.autosize), 31))
|
||||
}
|
||||
|
||||
// arm64ResolvePseudo translates a pseudo-register memory reference into a
|
||||
@@ -382,9 +390,12 @@ func arm64GuardBytes(fi arm64FrameInfo, blockStart int) []byte {
|
||||
ws = append(ws, wordsOf(mov)...)
|
||||
ml := len(mov) / 4
|
||||
ws = append(ws, arm64DPExtWords(arm64OpSubs, 27, 31, 17)) // SUBS R17, RSP, R27
|
||||
ws = append(ws, br(8+ml, a64CondLO))
|
||||
// The branches sit at fixed byte offsets in the guard prefix: after
|
||||
// the LDR (4), the ml MOV words (4*ml) and the SUBS (4) for B.LO,
|
||||
// then a further B.LO word and the CMP for B.LS.
|
||||
ws = append(ws, br(8+4*ml, a64CondLO))
|
||||
ws = append(ws, arm64DPSRWords(arm64OpSubs, 16, 17, 31)) // CMP R16, R17
|
||||
ws = append(ws, br(8+ml+8, a64CondLS))
|
||||
ws = append(ws, br(16+4*ml, a64CondLS))
|
||||
}
|
||||
return a64WordsLE(ws...)
|
||||
}
|
||||
|
||||
@@ -152,6 +152,45 @@ func TestStackGuardBytesARM64(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestStackGuardBranchTargetsARM64 checks the class-2 guard's branch
|
||||
// positions for a frame whose guard constant needs two MOV words: the
|
||||
// displacements must be computed from byte offsets (8+4*ml and 16+4*ml), so
|
||||
// both branches land on the morestack block rather than inside the body.
|
||||
// The frame size makes the toolchain switch its own prologue decomposition,
|
||||
// so the assertion is on the branch targets, not pinned bytes.
|
||||
func TestStackGuardBranchTargetsARM64(t *testing.T) {
|
||||
f, errs := parser.Parse("g_arm64.s", "TEXT \u00b7f(SB), $65664-0\n\tCALL \u00b7other(SB)\n\tRET\nTEXT \u00b7other(SB), NOSPLIT, $0\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileARM64(f)
|
||||
if err != nil {
|
||||
t.Fatalf("assemble: %v", err)
|
||||
}
|
||||
fn := img.Funcs[0]
|
||||
code := img.Code[fn.Offset : fn.Offset+fn.Size]
|
||||
if len(code)%4 != 0 {
|
||||
t.Fatalf("function size %d is not a word multiple", len(code))
|
||||
}
|
||||
// autosize = 65680, so the guard materialises 65552 = MOVZ+MOVK: ml = 2
|
||||
// and the branches sit at bytes 16 and 24 of the guard prefix.
|
||||
const morestackBlock = 12 // MOVD R30, R3; BL; B back
|
||||
blockStart := len(code) - morestackBlock
|
||||
check := func(name string, off int) {
|
||||
t.Helper()
|
||||
w := leWord(code[off:])
|
||||
imm19 := int32(w>>5) & 0x7FFFF
|
||||
if imm19&(1<<18) != 0 {
|
||||
imm19 -= 1 << 19
|
||||
}
|
||||
if target := off + int(imm19)*4; target != blockStart {
|
||||
t.Errorf("%s at byte %d targets byte %d, want the morestack block at %d", name, off, target, blockStart)
|
||||
}
|
||||
}
|
||||
check("B.LO", 16)
|
||||
check("B.LS", 24)
|
||||
}
|
||||
|
||||
// The riscv64 stack-split guard, pinned from `go tool asm` (Go 1.27,
|
||||
// riscv64): the morestack call sits between the guard and the body, and the
|
||||
// guard branches forward over it. Relocation fields are masked.
|
||||
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Wide MOV immediates that expand past one instruction, each followed by a
|
||||
// branch to a later label: the displacement is what the two passes must agree
|
||||
// on, so a size pass that disagrees with the encoder corrupts the branch.
|
||||
// Byte-parity-checked against go tool asm.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT ·movimm2(SB), NOSPLIT, $0-24
|
||||
MOVW $-1, R0
|
||||
B after1
|
||||
MOVD $0x0001000200030004, R1
|
||||
B after2
|
||||
MOVD $0x0001000200030000, R2
|
||||
B after3
|
||||
MOVW $0xFFFFFFFF, R3
|
||||
|
||||
after1:
|
||||
MOVD R0, r0+0(FP)
|
||||
|
||||
after2:
|
||||
MOVD R1, r1+8(FP)
|
||||
|
||||
after3:
|
||||
MOVD R2, r2+16(FP)
|
||||
RET
|
||||
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Shifts, division and multiply-accumulate, byte-parity-checked against
|
||||
// go tool asm: the immediate shift aliases (SBFM/UBFM, ROR via EXTR), the
|
||||
// register two-source forms (LSLV/LSRV/ASRV/RORV), SDIV/UDIV and the
|
||||
// four-operand MADD/MSUB. Every function body is padded to a whole number
|
||||
// of 16 bytes so the toolchain's object adds no trailing alignment.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// shiftimm exercises the immediate shift aliases in both widths.
|
||||
TEXT ·shiftimm(SB), NOSPLIT, $0-48
|
||||
MOVD x+0(FP), R0
|
||||
LSL $4, R0, R1
|
||||
LSR $8, R0, R2
|
||||
ASR $4, R0, R3
|
||||
ROR $12, R0, R4
|
||||
LSLW $4, R0, R5
|
||||
LSRW $8, R0, R6
|
||||
ASRW $4, R0, R7
|
||||
RORW $12, R0, R8
|
||||
MOVD R1, r1+0(FP)
|
||||
MOVD R2, r2+8(FP)
|
||||
MOVD R3, r3+16(FP)
|
||||
MOVD R4, r4+24(FP)
|
||||
MOVW R5, r5+32(FP)
|
||||
MOVW R6, r6+40(FP)
|
||||
RET
|
||||
|
||||
// shiftreg exercises the register shift forms in both widths: the count
|
||||
// comes from a register, encoding as LSLV/LSRV/ASRV/RORV.
|
||||
TEXT ·shiftreg(SB), NOSPLIT, $0-40
|
||||
MOVD x+0(FP), R0
|
||||
MOVD c+8(FP), R20
|
||||
LSL R20, R0, R1
|
||||
LSR R20, R0, R2
|
||||
ASR R20, R0, R3
|
||||
ROR R20, R0, R4
|
||||
LSLW R20, R0, R5
|
||||
LSRW R20, R0, R6
|
||||
ASRW R20, R0, R7
|
||||
RORW R20, R0, R8
|
||||
MOVD R1, r1+0(FP)
|
||||
MOVD R2, r2+8(FP)
|
||||
MOVD R3, r3+16(FP)
|
||||
MOVD R4, r4+24(FP)
|
||||
MOVW R5, r5+32(FP)
|
||||
RET
|
||||
|
||||
// shift2op exercises the two-operand spellings, which fold to Rn = Rd.
|
||||
TEXT ·shift2op(SB), NOSPLIT, $0-24
|
||||
MOVD x+0(FP), R1
|
||||
MOVD c+8(FP), R20
|
||||
LSL $4, R1
|
||||
LSR R20, R1
|
||||
LSL $12, R2
|
||||
ROR $8, R2
|
||||
LSLW $4, R3
|
||||
RORW $8, R3
|
||||
MOVD R1, r1+0(FP)
|
||||
MOVD R2, r2+8(FP)
|
||||
MOVD R3, r3+16(FP)
|
||||
RET
|
||||
|
||||
// divmul exercises SDIV/UDIV in both widths and the four-operand
|
||||
// MADD/MSUB, whose accumulate register is the second operand (Rm, Ra, Rn,
|
||||
// Rd) and rides bits 14:10.
|
||||
TEXT ·divmul(SB), NOSPLIT, $0-40
|
||||
MOVD x+0(FP), R0
|
||||
MOVD y+8(FP), R1
|
||||
SDIV R1, R0, R2
|
||||
UDIV R1, R0, R3
|
||||
SDIVW R1, R0, R4
|
||||
UDIVW R1, R0, R5
|
||||
MADD R1, R0, R2, R6
|
||||
MSUB R1, R0, R2, R7
|
||||
MADDW R1, R0, R4, R8
|
||||
MSUBW R1, R0, R4, R9
|
||||
MOVD R2, r2+0(FP)
|
||||
MOVD R3, r3+8(FP)
|
||||
MOVD R6, r6+16(FP)
|
||||
MOVD R7, r7+24(FP)
|
||||
MOVD R8, r8+32(FP)
|
||||
RET
|
||||
Reference in New Issue
Block a user