feat(asm): emit the loong64 stack-split guard for small and medium frames

This commit is contained in:
2026-09-14 21:21:58 +02:00
parent 2e2c0b82a0
commit db50b98179
5 changed files with 183 additions and 8 deletions
+106
View File
@@ -36,6 +36,11 @@ type loong64FrameInfo struct {
args int // the declared -argsize
noSplit bool // the NOSPLIT flag
leaf bool // no call instructions in the body
// Stack-split guard state: like amd64 and arm64, a leaf function with a
// small autosize is auto-marked NOSPLIT by the toolchain.
needSplit bool
splitClass int // 0: <=StackSmall, 1: <=StackBig, 2: >StackBig
}
// loong64ComputeFrame derives the frame layout for a TEXT function.
@@ -59,9 +64,110 @@ func loong64ComputeFrame(t *ast.Text) loong64FrameInfo {
// A zero-frame non-leaf function still opens an 8-byte frame for LR.
fi.autosize = 8
}
switch {
case fi.noSplit:
case fi.autosize < stackSmall && fi.leaf:
// Auto-NOSPLIT, as the toolchain's leaf mark concludes.
default:
fi.needSplit = true
switch {
case fi.autosize <= stackSmall:
fi.splitClass = 0
case fi.autosize <= stackBig:
fi.splitClass = 1
default:
fi.splitClass = 2
}
}
return fi
}
// loong64GuardLen returns the byte length of the stack-split guard prefix
// (zero when the function needs no guard). The big class materialises two
// constants through R30.
func loong64GuardLen(fi loong64FrameInfo) int {
if !fi.needSplit {
return 0
}
switch fi.splitClass {
case 0:
return 12
case 1:
return 16
default:
return 40 // MOVV + [LU12IW+ORI] + SGTU + BNE + [LU12IW+ORI] + ADDV + SGTU + BEQ
}
}
// loong64Lu12iOri materialises the 32-bit constant v in rd with the
// toolchain's LU12IW/ORI pair (the ORI reads and writes rd itself).
func loong64Lu12iOri(rd int, v int64) []uint32 {
hi := int32(v >> 12)
lo := int32(v & 0xFFF)
return []uint32{
0x0a<<25 | uint32(hi&0xFFFFF)<<5 | uint32(rd),
0x0e<<22 | uint32(lo)<<10 | uint32(rd)<<5 | uint32(rd),
}
}
// loong64GuardBytes emits the stack-split guard prefix. blockStart is the
// function-relative address of the morestack call at the end of the function;
// branch displacements are in instructions.
func loong64GuardBytes(fi loong64FrameInfo, blockStart int) []byte {
// MOVV 16(g), R20 (g.stackguard0), g = R22.
ws := []uint32{l64irr(l64loadStoreTable["MOVV"].ld, 16, 22, 20)}
switch fi.splitClass {
case 0:
// SGTU SP, R20, R20; BEQ R20, more
ws = append(ws, l64rrr(l64DualTable["SGTU"].rrr, 3, 20, 20))
ws = append(ws, loong64Beqz(20, int32((blockStart-8)>>2)))
case 1:
off := int32(fi.autosize - stackSmall)
ws = append(ws, l64irr(l64DualTable["ADDV"].imm, int(-off), 3, 24))
ws = append(ws, l64rrr(l64DualTable["SGTU"].rrr, 24, 20, 20))
ws = append(ws, loong64Beqz(20, int32((blockStart-12)>>2)))
default:
off := int64(fi.autosize - stackSmall)
movLen := 8 // LU12IW + ORI
ws = append(ws, loong64Lu12iOri(30, off)...)
ws = append(ws, l64rrr(l64DualTable["SGTU"].rrr, 30, 3, 24))
ws = append(ws, loong64Bnez(24, int32((blockStart-(8+movLen))>>2)))
ws = append(ws, loong64Lu12iOri(30, -off)...)
ws = append(ws, l64rrr(l64DualTable["ADDV"].rrr, 30, 3, 24))
ws = append(ws, l64rrr(l64DualTable["SGTU"].rrr, 24, 20, 20))
ws = append(ws, loong64Beqz(20, int32((blockStart-loong64GuardLen(fi)+12)>>2)))
}
return l64WordsLE(ws...)
}
// loong64Beqz/loong64Bnez build the 21-bit conditional branches against R0
// that the toolchain emits for its guard compares.
func loong64Beqz(rj int, dispInstr int32) uint32 {
return l64ir21(l64branch21Table["BEQZ"], int(dispInstr), rj)
}
func loong64Bnez(rj int, dispInstr int32) uint32 {
return l64ir21(l64branch21Table["BNEZ"], int(dispInstr), rj)
}
// loong64MoreStackBlock emits the trailing block: MOVV R1, R31 (save LR),
// BL runtime.morestack_noctxt, B back to the function entry.
func loong64MoreStackBlock(blockStart int) ([]byte, Reloc) {
ws := []uint32{
l64rrr(l64DualTable["ADD"].rrr, 0, 1, 31), // MOVV R1, R31 (ADD R1, R0, R31)
l64bbl(l64jumpTable["BL"], 0), // BL, patched by the linker
}
disp := (-(blockStart + 8)) >> 2
ws = append(ws, l64bbl(l64jumpTable["B"], int(disp)))
reloc := Reloc{
Off: blockStart + 4,
After: blockStart + 8,
Name: "runtime\u00b7morestack_noctxt",
Kind: RelLoong64Branch,
}
return l64WordsLE(ws...), reloc
}
// loong64IsLeaf reports whether a function contains no call instructions
// (JAL/BL/CALL), matching the toolchain's LEAF mark, which drives the frame
// and the epilogue shape.