diff --git a/asm/goobjloong64.go b/asm/goobjloong64.go index a3d7aa1..72d587d 100644 --- a/asm/goobjloong64.go +++ b/asm/goobjloong64.go @@ -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) { // A pcalau12i+addi.d pair: the high part carries - // R_LOONG64_ADDR_HI, the low part R_LOONG64_ADDR_LO. - if r.Kind == RelLoong64AddrLo { + // R_LOONG64_ADDR_HI, the low part R_LOONG64_ADDR_LO; the guard's + // morestack call carries R_CALLLOONG64. + switch { + case r.Kind == RelLoong64AddrLo: 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 ( relocLoong64AddrHi = 77 // R_LOONG64_ADDR_HI relocLoong64AddrLo = 78 // R_LOONG64_ADDR_LO + relocCallLoong64 = 84 // R_CALLLOONG64 ) // toolchainObjectPreambleLOONG64 returns the "go object ...\n!\n" header diff --git a/asm/guard_test.go b/asm/guard_test.go index e1fd463..ed4c6cb 100644 --- a/asm/guard_test.go +++ b/asm/guard_test.go @@ -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) + } + } +} diff --git a/asm/link.go b/asm/link.go index fd8be91..3a0565c 100644 --- a/asm/link.go +++ b/asm/link.go @@ -102,6 +102,7 @@ const ( RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair) RelArm64Branch // R_CALLARM64 (BL instruction) RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair) + RelLoong64Branch // R_CALLLOONG64 (BL instruction) ) type Reloc struct { diff --git a/asm/loong64_assemble.go b/asm/loong64_assemble.go index 6afe475..80deb9c 100644 --- a/asm/loong64_assemble.go +++ b/asm/loong64_assemble.go @@ -27,6 +27,7 @@ import ( func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, error) { fi := loong64ComputeFrame(t) prologue := loong64Prologue(fi) + guardLen := loong64GuardLen(fi) chain := loong64JumpChain(t) resolve := func(name string) string { 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 // pc, exactly as the toolchain's pctospadj does. 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. offsets := map[string]int{} - pos := len(prologue) + pos := guardLen + len(prologue) for _, stmt := range t.Body { switch s := stmt.(type) { 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. - out := append([]byte(nil), prologue...) - pc := len(prologue) + // Pass 2: encode. The guard prefix precedes the prologue; its branches + // target the morestack block at the end of the function, which the first + // 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) var lines []LineEntry for _, stmt := range t.Body { @@ -94,6 +111,12 @@ func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, out = append(out, 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 } diff --git a/asm/loong64_frame.go b/asm/loong64_frame.go index 5d0b80c..255db3a 100644 --- a/asm/loong64_frame.go +++ b/asm/loong64_frame.go @@ -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.