fix(asm): treat the arm64 $-8 frame as frameless and encode the RET forms

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 02:36:24 +02:00
1 parent d853432dba
commit f20aa156d0
2 files changed
+52 -11

No files matched your search

+1 -1
View File
@@ -396,7 +396,7 @@ func encodeARM64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi arm64
// Pseudo-instructions and special cases first.
switch mnem {
case "RET":
return arm64Return(fi), nil
return arm64RetInstr(fi, ops, relocs), nil
case "NOP", "NOOP":
return a64wordLE(a64NOP), nil
case "UNDEF":
+51 -10
View File
@@ -87,18 +87,28 @@ func arm64ComputeFrame(t *ast.Text) arm64FrameInfo {
if fi.frame != 0 || !fi.leaf {
fi.autosize = fi.frame + 8 // space for the saved LR
// The toolchain always adds an extrasize: 8 when the total leaves a
// 16-byte alignment gap, another 16 when already aligned.
switch fi.autosize % 16 {
case 8:
fi.autosize += 8
case 0:
fi.autosize += 16
default:
// The toolchain rejects unaligned frames; round up so such
// sources still assemble.
fi.autosize += 16 - (fi.autosize % 16)
// 16-byte alignment gap, another 16 when already aligned. An
// autosize of zero (the $-8 convention included) is frameless and
// takes neither.
if fi.autosize != 0 {
switch fi.autosize % 16 {
case 8:
fi.autosize += 8
case 0:
fi.autosize += 16
default:
// The toolchain rejects unaligned frames; round up so such
// sources still assemble.
fi.autosize += 16 - (fi.autosize % 16)
}
}
}
if fi.autosize == 0 {
// The NOFRAME shape: the toolchain forces the leaf mark on any
// autosize-zero function (calls included), so nothing saves LR and
// no stack-split guard runs.
fi.leaf = true
}
switch {
case fi.noSplit:
case fi.autosize < stackSmall && fi.leaf:
@@ -301,6 +311,37 @@ func arm64Return(fi arm64FrameInfo) []byte {
return a64WordsLE(ws...)
}
// arm64RetInstr encodes a RET. The plain form runs the frame epilogue and
// branches to LR; RET Rn runs the epilogue and branches to the register
// (asm7.go case 78); RET sym(SB) runs the epilogue and branches to the
// symbol with the call relocation, the toolchain's retJMP tail call.
func arm64RetInstr(fi arm64FrameInfo, ops []*ast.Operand, relocs *[]Reloc) []byte {
out := arm64Return(fi)
if len(ops) != 1 {
return out
}
op := ops[0]
switch {
case op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "" && op.Addr.Base == "" && op.Addr.Index == "":
if rn := arm64RegNum(op.Addr.Sym.Name); rn >= 0 {
// The operand form replaces the default BR LR word.
return append(out[:len(out)-4], a64wordLE(0xd65f0000|uint32(rn)<<5)...)
}
case op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "SB" && op.Addr.Base == "" && op.Addr.Index == "":
if relocs != nil {
*relocs = append(*relocs, Reloc{
Off: 0,
After: 4,
Name: op.Addr.Sym.Name,
Addend: op.Addr.Sym.Offset,
Kind: RelArm64Branch,
})
}
return append(out[:len(out)-4], a64wordLE(0x14000000)...) // B sym(SB)
}
return out
}
// arm64PrologueSpadjPC returns the function-relative byte offset where the
// prologue has finished decrementing SP (the delta becomes autosize).
func arm64PrologueSpadjPC(fi arm64FrameInfo) int {