diff --git a/asm/arm64_assemble.go b/asm/arm64_assemble.go index e8f3556..08faa69 100644 --- a/asm/arm64_assemble.go +++ b/asm/arm64_assemble.go @@ -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": diff --git a/asm/arm64_frame.go b/asm/arm64_frame.go index e74953c..b92378e 100644 --- a/asm/arm64_frame.go +++ b/asm/arm64_frame.go @@ -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 {