feat(asm): emit the loong64 stack-split guard for small and medium frames
This commit is contained in:
+9
-3
@@ -25,11 +25,16 @@ func (img *Image) GOObjectLOONG64(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return img.emitGOObject(pkgPath, srcPath, pre, 4, func(r Reloc) (uint16, uint8) {
|
return img.emitGOObject(pkgPath, srcPath, pre, 4, func(r Reloc) (uint16, uint8) {
|
||||||
// A pcalau12i+addi.d pair: the high part carries
|
// A pcalau12i+addi.d pair: the high part carries
|
||||||
// R_LOONG64_ADDR_HI, the low part R_LOONG64_ADDR_LO.
|
// R_LOONG64_ADDR_HI, the low part R_LOONG64_ADDR_LO; the guard's
|
||||||
if r.Kind == RelLoong64AddrLo {
|
// morestack call carries R_CALLLOONG64.
|
||||||
|
switch {
|
||||||
|
case r.Kind == RelLoong64AddrLo:
|
||||||
return relocLoong64AddrLo, 4
|
return relocLoong64AddrLo, 4
|
||||||
}
|
case r.Kind == RelLoong64Branch:
|
||||||
|
return relocCallLoong64, 4
|
||||||
|
default:
|
||||||
return relocLoong64AddrHi, 4
|
return relocLoong64AddrHi, 4
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +44,7 @@ func (img *Image) GOObjectLOONG64(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
const (
|
const (
|
||||||
relocLoong64AddrHi = 77 // R_LOONG64_ADDR_HI
|
relocLoong64AddrHi = 77 // R_LOONG64_ADDR_HI
|
||||||
relocLoong64AddrLo = 78 // R_LOONG64_ADDR_LO
|
relocLoong64AddrLo = 78 // R_LOONG64_ADDR_LO
|
||||||
|
relocCallLoong64 = 84 // R_CALLLOONG64
|
||||||
)
|
)
|
||||||
|
|
||||||
// toolchainObjectPreambleLOONG64 returns the "go object ...\n!\n" header
|
// toolchainObjectPreambleLOONG64 returns the "go object ...\n!\n" header
|
||||||
|
|||||||
@@ -185,3 +185,42 @@ func TestStackGuardBytesRISCV64(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The loong64 stack-split guard, pinned from `go tool asm` (Go 1.27,
|
||||||
|
// loong64) for the small and medium frame classes plus auto-NOSPLIT. The
|
||||||
|
// big class (>StackBig) and the huge-frame body prologue remain toolchain
|
||||||
|
// divergences tracked separately.
|
||||||
|
func TestStackGuardBytesLOONG64(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
src string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"leafsmall", "TEXT \u00b7leafsmall(SB), $16-0\n\tRET\n",
|
||||||
|
"61a0ff2963a0ff026100c0296360c0022000004c"},
|
||||||
|
{"leafmed", "TEXT \u00b7leafmed(SB), $256-0\n\tRET\n",
|
||||||
|
"d442c02878e0fd0294e21200801a004061e0fb2963e0fb026100c0296320c4022000004c3f00100000000000ffd7ff53"},
|
||||||
|
{"nosplit", "TEXT \u00b7nosplit(SB), NOSPLIT, $16-0\n\tRET\n",
|
||||||
|
"61a0ff2963a0ff026100c0296360c0022000004c"},
|
||||||
|
} {
|
||||||
|
f, errs := parser.Parse("g_loong64.s", tt.src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("%s: parse: %v", tt.name, errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileLOONG64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: assemble: %v", tt.name, err)
|
||||||
|
}
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
code := append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...)
|
||||||
|
for _, r := range fn.Relocs {
|
||||||
|
for j := r.Off; j < r.Off+4 && j < len(code); j++ {
|
||||||
|
code[j] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
got := hex.EncodeToString(code)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("%s:\n got %s\n want %s", tt.name, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ const (
|
|||||||
RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair)
|
RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair)
|
||||||
RelArm64Branch // R_CALLARM64 (BL instruction)
|
RelArm64Branch // R_CALLARM64 (BL instruction)
|
||||||
RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair)
|
RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair)
|
||||||
|
RelLoong64Branch // R_CALLLOONG64 (BL instruction)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reloc struct {
|
type Reloc struct {
|
||||||
|
|||||||
+28
-5
@@ -27,6 +27,7 @@ import (
|
|||||||
func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, error) {
|
func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, error) {
|
||||||
fi := loong64ComputeFrame(t)
|
fi := loong64ComputeFrame(t)
|
||||||
prologue := loong64Prologue(fi)
|
prologue := loong64Prologue(fi)
|
||||||
|
guardLen := loong64GuardLen(fi)
|
||||||
chain := loong64JumpChain(t)
|
chain := loong64JumpChain(t)
|
||||||
resolve := func(name string) string {
|
resolve := func(name string) string {
|
||||||
if r, ok := chain[name]; ok {
|
if r, ok := chain[name]; ok {
|
||||||
@@ -42,12 +43,12 @@ func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry,
|
|||||||
// delta by autosize; the boundary is reported at the third instruction's
|
// delta by autosize; the boundary is reported at the third instruction's
|
||||||
// pc, exactly as the toolchain's pctospadj does.
|
// pc, exactly as the toolchain's pctospadj does.
|
||||||
if fi.autosize != 0 {
|
if fi.autosize != 0 {
|
||||||
spadj = append(spadj, SpadjStep{PC: 8, Value: fi.autosize})
|
spadj = append(spadj, SpadjStep{PC: guardLen + 8, Value: fi.autosize})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 1: label offsets from the instruction sizes.
|
// Pass 1: label offsets from the instruction sizes.
|
||||||
offsets := map[string]int{}
|
offsets := map[string]int{}
|
||||||
pos := len(prologue)
|
pos := guardLen + len(prologue)
|
||||||
for _, stmt := range t.Body {
|
for _, stmt := range t.Body {
|
||||||
switch s := stmt.(type) {
|
switch s := stmt.(type) {
|
||||||
case *ast.Label:
|
case *ast.Label:
|
||||||
@@ -57,9 +58,25 @@ func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 2: encode. Relocation offsets are recorded function-relative.
|
// Pass 2: encode. The guard prefix precedes the prologue; its branches
|
||||||
out := append([]byte(nil), prologue...)
|
// target the morestack block at the end of the function, which the first
|
||||||
pc := len(prologue)
|
// pass has sized.
|
||||||
|
bodyLen := 0
|
||||||
|
{
|
||||||
|
p := guardLen + len(prologue)
|
||||||
|
for _, stmt := range t.Body {
|
||||||
|
if in, ok := stmt.(*ast.Instr); ok {
|
||||||
|
p += loong64InstrSize(in, fi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bodyLen = p - (guardLen + len(prologue))
|
||||||
|
}
|
||||||
|
var out []byte
|
||||||
|
if fi.needSplit {
|
||||||
|
out = append(out, loong64GuardBytes(fi, guardLen+len(prologue)+bodyLen)...)
|
||||||
|
}
|
||||||
|
out = append(out, prologue...)
|
||||||
|
pc := guardLen + len(prologue)
|
||||||
preCount := len(relocs)
|
preCount := len(relocs)
|
||||||
var lines []LineEntry
|
var lines []LineEntry
|
||||||
for _, stmt := range t.Body {
|
for _, stmt := range t.Body {
|
||||||
@@ -94,6 +111,12 @@ func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry,
|
|||||||
out = append(out, code...)
|
out = append(out, code...)
|
||||||
pc += len(code)
|
pc += len(code)
|
||||||
}
|
}
|
||||||
|
if fi.needSplit {
|
||||||
|
block, blReloc := loong64MoreStackBlock(pc)
|
||||||
|
out = append(out, block...)
|
||||||
|
relocs = append(relocs, blReloc)
|
||||||
|
pc += len(block)
|
||||||
|
}
|
||||||
return out, offsets, relocs, lines, spadj, nil
|
return out, offsets, relocs, lines, spadj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ type loong64FrameInfo struct {
|
|||||||
args int // the declared -argsize
|
args int // the declared -argsize
|
||||||
noSplit bool // the NOSPLIT flag
|
noSplit bool // the NOSPLIT flag
|
||||||
leaf bool // no call instructions in the body
|
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.
|
// 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.
|
// A zero-frame non-leaf function still opens an 8-byte frame for LR.
|
||||||
fi.autosize = 8
|
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
|
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
|
// loong64IsLeaf reports whether a function contains no call instructions
|
||||||
// (JAL/BL/CALL), matching the toolchain's LEAF mark, which drives the frame
|
// (JAL/BL/CALL), matching the toolchain's LEAF mark, which drives the frame
|
||||||
// and the epilogue shape.
|
// and the epilogue shape.
|
||||||
|
|||||||
Reference in New Issue
Block a user