feat(asm): emit the loong64 stack-split guard for big frames
This commit is contained in:
+11
-4
@@ -182,7 +182,7 @@ func arm64Prologue(fi arm64FrameInfo) []byte {
|
||||
|
||||
// arm64SubImmWords emits SUB $imm, SP, Rd: the immediate form when the value
|
||||
// fits the imm12 field, otherwise the toolchain materialises it into REGTMP
|
||||
// (R27) and subtracts the register.
|
||||
// (R27) and subtracts the register in the extended-register form.
|
||||
func arm64SubImmWords(imm uint32, rd uint32) []uint32 {
|
||||
if imm <= 0xFFF {
|
||||
return []uint32{a64AddSub(1, 1, 0, 0, imm, 31, rd)}
|
||||
@@ -191,7 +191,7 @@ func arm64SubImmWords(imm uint32, rd uint32) []uint32 {
|
||||
if err != nil {
|
||||
mov = nil
|
||||
}
|
||||
return append(wordsOf(mov), arm64DPSRWords(arm64OpSub, 27, 31, rd))
|
||||
return append(wordsOf(mov), arm64DPExtWords(arm64OpSub, 27, 31, rd))
|
||||
}
|
||||
|
||||
// arm64AddImmWords emits ADD $imm, SP, Rd with the same REGTMP fallback.
|
||||
@@ -203,7 +203,7 @@ func arm64AddImmWords(imm uint32, rd uint32) []uint32 {
|
||||
if err != nil {
|
||||
mov = nil
|
||||
}
|
||||
return append(wordsOf(mov), arm64DPSRWords(arm64OpAdd, 27, 31, rd))
|
||||
return append(wordsOf(mov), arm64DPExtWords(arm64OpAdd, 27, 31, rd))
|
||||
}
|
||||
|
||||
// arm64Return returns the bytes for a RET: the epilogue (restore FP/LR and
|
||||
@@ -326,6 +326,13 @@ func arm64DPSRWords(base uint32, rm, rn, rd uint32) uint32 {
|
||||
return base | rm<<16 | rn<<5 | rd
|
||||
}
|
||||
|
||||
// arm64DPExtWords builds one data-processing (extended register) word, the
|
||||
// form the toolchain picks when a large immediate was materialised into
|
||||
// REGTMP before the operation: base | 1<<21 | Rm<<16 | UXTX<<13 | Rn<<5 | Rd.
|
||||
func arm64DPExtWords(base, rm, rn, rd uint32) uint32 {
|
||||
return base | 1<<21 | rm<<16 | 3<<13 | rn<<5 | rd
|
||||
}
|
||||
|
||||
// wordsOf converts little-endian instruction bytes back to words.
|
||||
func wordsOf(b []byte) []uint32 {
|
||||
ws := make([]uint32, 0, len(b)/4)
|
||||
@@ -359,7 +366,7 @@ func arm64GuardBytes(fi arm64FrameInfo, blockStart int) []byte {
|
||||
}
|
||||
ws = append(ws, wordsOf(mov)...)
|
||||
ml := len(mov) / 4
|
||||
ws = append(ws, arm64DPSRWords(arm64OpSubs, 27, 31, 17)) // SUBS R27, RSP, R17
|
||||
ws = append(ws, arm64DPExtWords(arm64OpSubs, 27, 31, 17)) // SUBS R17, RSP, R27
|
||||
ws = append(ws, br(8+ml, a64CondLO))
|
||||
ws = append(ws, arm64DPSRWords(arm64OpSubs, 16, 17, 31)) // CMP R16, R17
|
||||
ws = append(ws, br(8+ml+8, a64CondLS))
|
||||
|
||||
+5
-1
@@ -16,6 +16,7 @@ const (
|
||||
// LoongArch relocation types (the ELF psABI).
|
||||
rLarchPCALAHI20 = 71 // R_LARCH_PCALA_HI20 (pcalau12i)
|
||||
rLarchPCALALO12 = 72 // R_LARCH_PCALA_LO12 (addi.d/ld/st)
|
||||
rLarchB26 = 66 // R_LARCH_B26 (b/bl, matches the Go linker's mapping)
|
||||
)
|
||||
|
||||
// ELFLOONG64Object returns the image as an ELF64 relocatable object file for
|
||||
@@ -95,8 +96,11 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) {
|
||||
return nil, fmt.Errorf("relocation references unknown symbol %q", r.Name)
|
||||
}
|
||||
typ := uint32(rLarchPCALAHI20)
|
||||
if r.Kind == RelLoong64AddrLo {
|
||||
switch r.Kind {
|
||||
case RelLoong64AddrLo:
|
||||
typ = rLarchPCALALO12
|
||||
case RelLoong64Branch:
|
||||
typ = rLarchB26
|
||||
}
|
||||
relas = append(relas, elfRela{
|
||||
off: uint64(fn.Offset + r.Off),
|
||||
|
||||
@@ -198,3 +198,45 @@ TEXT ·nop(SB), NOSPLIT, $0
|
||||
t.Error("function symbol nop not found")
|
||||
}
|
||||
}
|
||||
|
||||
// TestELFLOONG64BranchRelocation checks that the morestack call and an
|
||||
// internal CALL both carry R_LARCH_B26 in the emitted object, matching the
|
||||
// Go linker's mapping of its call relocation.
|
||||
func TestELFLOONG64BranchRelocation(t *testing.T) {
|
||||
f, errs := parser.Parse("k_loong64.s", "TEXT \u00b7callbig(SB), $8192-0\n\tCALL \u00b7other(SB)\n\tRET\nTEXT \u00b7other(SB), NOSPLIT, $0\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileLOONG64(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileLOONG64: %v", err)
|
||||
}
|
||||
obj, err := img.ELFLOONG64Object()
|
||||
if err != nil {
|
||||
t.Fatalf("ELFLOONG64Object: %v", err)
|
||||
}
|
||||
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||
if err != nil {
|
||||
t.Fatalf("parse emitted object: %v", err)
|
||||
}
|
||||
defer ef.Close()
|
||||
relaSec := ef.Section(".rela.text")
|
||||
if relaSec == nil {
|
||||
t.Fatal("missing .rela.text")
|
||||
}
|
||||
raw, err := relaSec.Data()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The guard's morestack call plus the body's CALL to other.
|
||||
if len(raw)%24 != 0 || len(raw)/24 != 2 {
|
||||
t.Fatalf(".rela.text has %d bytes, want two 24-byte entries", len(raw))
|
||||
}
|
||||
le := binary.LittleEndian
|
||||
for i := range 2 {
|
||||
info := le.Uint64(raw[i*24+8:])
|
||||
if elf.R_LARCH(info&0xffffffff) != elf.R_LARCH_B26 {
|
||||
t.Errorf("relocation %d type = %v, want R_LARCH_B26", i, elf.R_LARCH(info&0xffffffff))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -156,6 +156,16 @@ const goobjBuiltinMorestackNoctxt = 246
|
||||
// stack-guard call.
|
||||
var goobjBuiltinMorestack = "runtime\u00b7morestack_noctxt"
|
||||
|
||||
// isCallReloc reports whether k is one of the per-arch call relocations a
|
||||
// direct branch to a TEXT symbol carries.
|
||||
func isCallReloc(k RelocKind) bool {
|
||||
switch k {
|
||||
case RelCall, RelRISCVJal, RelArm64Branch, RelLoong64Branch:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const goobjMagic = "\x00go120ld"
|
||||
|
||||
// goSym is one symbol definition under construction.
|
||||
@@ -436,7 +446,7 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
|
||||
// A call to a TEXT function of the same file references the
|
||||
// non-package definition table.
|
||||
ni, isText := textNpIdx[r.Name]
|
||||
if !isText || r.Kind != RelCall {
|
||||
if !isText || !isCallReloc(r.Kind) {
|
||||
return nil, fmt.Errorf("GOOBJ emission: reference to unknown symbol %q", r.Name)
|
||||
}
|
||||
pkg = pkgIdxNone
|
||||
|
||||
+62
-5
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
)
|
||||
|
||||
@@ -116,7 +117,7 @@ func TestStackGuardBytesARM64(t *testing.T) {
|
||||
{"leafmed", "TEXT \u00b7leafmed(SB), $256-0\n\tRET\n",
|
||||
"900b40f9f14302d13f0210eb09010054f44304d19dfa3fa99f020091fd2300d1fd230491ff430491c0035fd6e3031eaa00000000f3ffff17"},
|
||||
{"leafbig", "TEXT \u00b7leafbig(SB), $8192-0\n\tRET\n",
|
||||
"900b40f91bf283d2f1031beba30100543f0210eb690100541b0284d2f4031bcb9dfa3fa99f020091fd2300d11b0184d2fd031b8b1b0284d2ff031b8bc0035fd6e3031eaa00000000eeffff17"},
|
||||
"900b40f91bf283d2f1633beba30100543f0210eb690100541b0284d2f4633bcb9dfa3fa99f020091fd2300d11b0184d2fd633b8b1b0284d2ff633b8bc0035fd6e3031eaa00000000eeffff17"},
|
||||
{"callsmall", "TEXT \u00b7callsmall(SB), $16-0\n\tCALL \u00b7other(SB)\n\tRET\nTEXT \u00b7other(SB), NOSPLIT, $0\n\tRET\n",
|
||||
"900b40f9ff6330eb09010054fe0f1ef8fd831ff8fd2300d100000000fd835ff8fe0742f8c0035fd6e3031eaa00000000f4ffff17"},
|
||||
{"nosplit", "TEXT \u00b7nosplit(SB), NOSPLIT, $16-0\n\tRET\n",
|
||||
@@ -187,9 +188,10 @@ 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.
|
||||
// loong64): every guard class (including the medium class with the
|
||||
// materialised constant and the big class with the ORI-less constants), the
|
||||
// auto-NOSPLIT leaf behaviour, the large-frame R30 prologue/epilogue forms
|
||||
// and the morestack block. Relocation fields are masked.
|
||||
func TestStackGuardBytesLOONG64(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
@@ -199,9 +201,37 @@ func TestStackGuardBytesLOONG64(t *testing.T) {
|
||||
{"leafsmall", "TEXT \u00b7leafsmall(SB), $16-0\n\tRET\n",
|
||||
"61a0ff2963a0ff026100c0296360c0022000004c"},
|
||||
{"leafmed", "TEXT \u00b7leafmed(SB), $256-0\n\tRET\n",
|
||||
"d442c02878e0fd0294e21200801a004061e0fb2963e0fb026100c0296320c4022000004c3f00100000000000ffd7ff53"},
|
||||
"d442c02878e0fd0294e21200801a004061e0fb2963e0fb026100c0296320c4022000004c3f00150000000000ffd7ff53"},
|
||||
{"nosplit", "TEXT \u00b7nosplit(SB), NOSPLIT, $16-0\n\tRET\n",
|
||||
"61a0ff2963a0ff026100c0296360c0022000004c"},
|
||||
// The LR store leaves the 12-bit store-offset range while the SP
|
||||
// adjust immediate still fits, and the epilogue adjusts through a
|
||||
// single ORI.
|
||||
{"fit2048", "TEXT \u00b7fit2048(SB), $2040-0\n\tRET\n",
|
||||
"d442c0287800e20294e21200802600401e000014de8f1000c103e0296300e0026100c0291e00a00363f810002000004c3f00150000000000ffcbff53"},
|
||||
// Medium class at the materialisation boundary (off = 2048 still
|
||||
// immediate, 2049+ goes through R30).
|
||||
{"med2048off", "TEXT \u00b7med2048off(SB), $2168-0\n\tRET\n",
|
||||
"d442c0287800e00294e21200802e0040feffff15de8f1000c103de29feffff15de039e0363f810006100c0291e00a20363f810002000004c3f00150000000000ffc3ff53"},
|
||||
{"medmat", "TEXT \u00b7medmat(SB), $2176-0\n\tRET\n",
|
||||
"d442c028feffff15dee39f0378f8100094e21200802e0040feffff15de8f1000c1e3dd29feffff15dee39d0363f810006100c0291e20a20363f810002000004c3f00150000000000ffbbff53"},
|
||||
// Big class with the rounding-split store and the floor-split adjust.
|
||||
{"leafbig", "TEXT \u00b7leafbig(SB), $8192-0\n\tRET\n",
|
||||
"d442c0283e000014de23be0378f8120000470044deffff15dee3810378f8100094e2120080320040deffff15de8f1000c1e3ff29beffff15dee3bf0363f810006100c0295e000014de23800363f810002000004c3f00150000000000ffa7ff53"},
|
||||
// Zero low 12 bits drop the ORI from the store, the adjust and the
|
||||
// epilogue materialisation.
|
||||
{"bigzero", "TEXT \u00b7bigzero(SB), $4088-0\n\tRET\n",
|
||||
"d442c028feffff15de03820378f8100094e21200802a0040feffff15de8f1000c103c029feffff1563f810006100c0293e00001463f810002000004c3f00150000000000ffbfff53"},
|
||||
// Big class whose first constant has a zero high part: a single ORI.
|
||||
{"big3976", "TEXT \u00b7big3976(SB), $4096-0\n\tRET\n",
|
||||
"d442c0281e20be0378f8120000470044feffff15dee3810378f8100094e2120080320040feffff15de8f1000c1e3ff29deffff15dee3bf0363f810006100c0293e000014de23800363f810002000004c3f00150000000000ffabff53"},
|
||||
// Big class at a multiple of 4096: both guard constants lose their
|
||||
// ORI word.
|
||||
{"giantlo0", "TEXT \u00b7giantlo0(SB), $4216-0\n\tRET\n",
|
||||
"d442c0283e00001478f8120000430044feffff1578f8100094e2120080320040feffff15de8f1000c103fe29deffff15de03be0363f810006100c0293e000014de03820363f810002000004c3f00150000000000ffafff53"},
|
||||
// Non-leaf big frame: the body call plus the LR restore epilogue.
|
||||
{"callbig", "TEXT \u00b7callbig(SB), $8192-0\n\tCALL \u00b7other(SB)\n\tRET\nTEXT \u00b7other(SB), NOSPLIT, $0\n\tRET\n",
|
||||
"d442c0283e000014de23be0378f81200004f0044deffff15dee3810378f8100094e21200803a0040deffff15de8f1000c1e3ff29beffff15dee3bf0363f810006100c029000000006100c0285e000014de23800363f810002000004c3f00150000000000ff9fff53"},
|
||||
} {
|
||||
f, errs := parser.Parse("g_loong64.s", tt.src)
|
||||
if len(errs) > 0 {
|
||||
@@ -224,3 +254,30 @@ func TestStackGuardBytesLOONG64(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestStackGuardGOObjInternalCall checks that GOOBJ emission succeeds when a
|
||||
// guarded function calls a TEXT symbol of the same file, for every arch's
|
||||
// call relocation kind.
|
||||
func TestStackGuardGOObjInternalCall(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
src string
|
||||
assemble func(*ast.File) (*Image, error)
|
||||
}{
|
||||
{"g_amd64.s", AssembleFile},
|
||||
{"g_arm64.s", AssembleFileARM64},
|
||||
{"g_riscv64.s", AssembleFileRISCV},
|
||||
{"g_loong64.s", AssembleFileLOONG64},
|
||||
} {
|
||||
f, errs := parser.Parse(tt.src, "TEXT \u00b7callsmall(SB), $16-0\n\tCALL \u00b7other(SB)\n\tRET\nTEXT \u00b7other(SB), NOSPLIT, $0\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("%s: parse: %v", tt.src, errs)
|
||||
}
|
||||
img, err := tt.assemble(f)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: assemble: %v", tt.src, err)
|
||||
}
|
||||
if _, err := img.GOObject("testpkg", tt.src); err != nil {
|
||||
t.Errorf("%s: GOObject: %v", tt.src, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-20
@@ -18,12 +18,10 @@ import (
|
||||
// then encoding with resolved branch targets).
|
||||
//
|
||||
// The emitted bytes match the Go toolchain's loong64 assembler, which is the
|
||||
// ground-truth oracle: prologue/epilogue, FP/SP frame mapping, branch
|
||||
// encodings and the MOV immediate expansions all follow cmd/internal/obj/
|
||||
// loong64's asmout cases. One deliberate difference: the stack-growth guard
|
||||
// (the morestack check in the prologue and the call back into the runtime in
|
||||
// the epilogue) is not emitted, so the bytes match only for NOSPLIT functions
|
||||
// or zero-frame leaves, where the toolchain emits no guard either.
|
||||
// ground-truth oracle: prologue/epilogue (including the large-frame R30
|
||||
// materialisations), FP/SP frame mapping, the stack-split guard classes, and
|
||||
// branch encodings all follow cmd/internal/obj/loong64. The morestack block
|
||||
// at the end of split functions carries the runtime.morestack_noctxt call.
|
||||
func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, error) {
|
||||
fi := loong64ComputeFrame(t)
|
||||
prologue := loong64Prologue(fi)
|
||||
@@ -39,11 +37,12 @@ func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry,
|
||||
var relocs []Reloc
|
||||
var spadj []SpadjStep
|
||||
|
||||
// The prologue (3 instructions when a frame is present) raises the SP
|
||||
// delta by autosize; the boundary is reported at the third instruction's
|
||||
// pc, exactly as the toolchain's pctospadj does.
|
||||
// The prologue raises the SP delta by autosize; the boundary is reported
|
||||
// after the SP adjust instruction, exactly as the toolchain's pctospadj
|
||||
// does. The prologue (3 instructions when a frame is present) may
|
||||
// materialise its store or adjust through R30, which widens it.
|
||||
if fi.autosize != 0 {
|
||||
spadj = append(spadj, SpadjStep{PC: guardLen + 8, Value: fi.autosize})
|
||||
spadj = append(spadj, SpadjStep{PC: guardLen + (loong64StoreWords(fi.autosize)+loong64AdjustWords(-int64(fi.autosize)))*4, Value: fi.autosize})
|
||||
}
|
||||
|
||||
// Pass 1: label offsets from the instruction sizes.
|
||||
@@ -99,14 +98,9 @@ func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry,
|
||||
preCount = len(relocs)
|
||||
lines = append(lines, LineEntry{Offset: pc, Line: in.Pos().Line})
|
||||
// The RET's epilogue closes the frame: the SP delta returns to zero
|
||||
// after the addi.d (one instruction for a leaf, two for a non-leaf
|
||||
// with the LR restore).
|
||||
// after the frame-deallocating ADDV.
|
||||
if strings.ToUpper(in.Mnemonic.Text) == "RET" && fi.autosize != 0 {
|
||||
epi := 4
|
||||
if !fi.leaf {
|
||||
epi = 8
|
||||
}
|
||||
spadj = append(spadj, SpadjStep{PC: pc + epi, Value: 0})
|
||||
spadj = append(spadj, SpadjStep{PC: pc + loong64EpilogueWords(fi)*4, Value: 0})
|
||||
}
|
||||
out = append(out, code...)
|
||||
pc += len(code)
|
||||
@@ -255,9 +249,9 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
||||
}
|
||||
return l64wordLE(uint32(immFromOperand(ops[0]))), nil
|
||||
case "JMP", "B":
|
||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, false, resolve)
|
||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, false, resolve, relocs)
|
||||
case "JAL", "CALL", "BL":
|
||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, true, resolve)
|
||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, true, resolve, relocs)
|
||||
case "MOV", "MOVB", "MOVH", "MOVW", "MOVV", "MOVBU", "MOVHU", "MOVWU", "MOVF", "MOVD":
|
||||
return encodeLOONG64Mov(instr, mnem, fi, relocs)
|
||||
}
|
||||
@@ -516,7 +510,7 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
||||
//
|
||||
// JMP/B label → b label JMP/B (rj) → jirl r0, rj, 0
|
||||
// JAL/CALL/BL label → bl label JAL/CALL/BL (rj) → jirl r1, rj, 0
|
||||
func encodeLOONG64Branch(instr *ast.Instr, mnem string, pc int, offsets map[string]int, link bool, resolve func(string) string) ([]byte, error) {
|
||||
func encodeLOONG64Branch(instr *ast.Instr, mnem string, pc int, offsets map[string]int, link bool, resolve func(string) string, relocs *[]Reloc) ([]byte, error) {
|
||||
if len(instr.Operands) != 1 {
|
||||
return nil, fmt.Errorf("%s expects 1 operand, got %d", mnem, len(instr.Operands))
|
||||
}
|
||||
@@ -533,6 +527,14 @@ func encodeLOONG64Branch(instr *ast.Instr, mnem string, pc int, offsets map[stri
|
||||
}
|
||||
return l64wordLE(l64irr16(l64branchTable["JIRL"], 0, rj, rd)), nil
|
||||
}
|
||||
// Direct symbol: sym+off(SB) → bl with an R_CALLLOONG64 relocation (the
|
||||
// linker fills the offset), as the toolchain does for CALL/BL/JAL.
|
||||
if op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "SB" && link {
|
||||
if relocs != nil {
|
||||
*relocs = append(*relocs, Reloc{Off: 0, After: 4, Name: op.Addr.Sym.Name, Kind: RelLoong64Branch, Addend: op.Addr.Sym.Offset})
|
||||
}
|
||||
return l64wordLE(l64bbl(l64jumpTable["BL"], 0)), nil
|
||||
}
|
||||
// Direct: label → b/bl.
|
||||
target := resolve(l64Label(op))
|
||||
targetOff, ok := offsets[target]
|
||||
|
||||
+141
-36
@@ -20,14 +20,20 @@ import (
|
||||
// (the toolchain aligns frames with `if autosize&4 != 0 { autosize += 4 }`).
|
||||
// A leaf function (no calls) with a zero frame gets no prologue at all.
|
||||
//
|
||||
// Prologue (autosize > 0), byte-identical to the toolchain:
|
||||
// Prologue (autosize > 0, small), byte-identical to the toolchain:
|
||||
//
|
||||
// MOVV R1, -autosize(R3) // save LR below the new SP (traceback-safe)
|
||||
// ADDV $-autosize, R3 // open the frame
|
||||
// MOVV R1, 0(R3) // save LR again at SP (signal-safety)
|
||||
//
|
||||
// Large frames (autosize past the 12-bit offset or immediate ranges) expand
|
||||
// the store and the adjust through REGTMP (R30) exactly as the toolchain's
|
||||
// assembler does: the store via the rounding LU12IW split, the adjust via
|
||||
// the floor LU12IW/ORI split.
|
||||
//
|
||||
// Epilogue: MOVV 0(R3), R1; ADDV $autosize, R3 (non-leaf only for the LR
|
||||
// restore); the RET's jirl r0, r1, 0 follows.
|
||||
// restore; the adjust materialised when the immediate does not fit); the
|
||||
// RET's jirl r0, r1, 0 follows.
|
||||
|
||||
// loong64FrameInfo holds the frame layout derived from a TEXT directive.
|
||||
type loong64FrameInfo struct {
|
||||
@@ -84,62 +90,108 @@ func loong64ComputeFrame(t *ast.Text) loong64FrameInfo {
|
||||
|
||||
// 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.
|
||||
// constants through R30; each materialisation shrinks by one word when the
|
||||
// constant's low 12 bits are zero.
|
||||
func loong64GuardLen(fi loong64FrameInfo) int {
|
||||
if !fi.needSplit {
|
||||
return 0
|
||||
}
|
||||
off := int64(fi.autosize - stackSmall)
|
||||
switch fi.splitClass {
|
||||
case 0:
|
||||
return 12
|
||||
case 1:
|
||||
return 16
|
||||
if off <= 2048 {
|
||||
return 16 // ADDV $-off fits the signed 12-bit immediate
|
||||
}
|
||||
return 24 // MOVV + LU12IW + ORI + ADDV + SGTU + BEQ
|
||||
default:
|
||||
return 40 // MOVV + [LU12IW+ORI] + SGTU + BNE + [LU12IW+ORI] + ADDV + SGTU + BEQ
|
||||
// MOVV + [mat] + SGTU + BNE + [mat] + ADDV + SGTU + BEQ
|
||||
return (6 + loong64MatLen(off) + loong64MatLen(-off)) * 4
|
||||
}
|
||||
}
|
||||
|
||||
// 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),
|
||||
// loong64MatLen reports the word count of materialising v in R30: a value
|
||||
// with a zero high part needs only the ORI (the toolchain's MOVW $v, R30),
|
||||
// one with a zero low part only the LU12IW.
|
||||
func loong64MatLen(v int64) int {
|
||||
if v>>12 == 0 || v&0xFFF == 0 {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
// loong64MatWords appends the words that materialise v in R30, splitting it
|
||||
// as v>>12 plus the zero-extended low 12 bits.
|
||||
func loong64MatWords(ws []uint32, v int64) []uint32 {
|
||||
hi := v >> 12
|
||||
lo := v & 0xFFF
|
||||
if hi == 0 {
|
||||
return append(ws, l64irr(l64OriOp, int(v), 0, 30))
|
||||
}
|
||||
ws = append(ws, l64ir(l64Lu12iwOp, int(hi), 30))
|
||||
if lo != 0 {
|
||||
ws = append(ws, l64irr(l64OriOp, int(lo), 30, 30))
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
// The LU12IW and ORI opcode bases (2RI20 and 2RI12 formats); the ORI reads
|
||||
// and writes rd itself.
|
||||
const (
|
||||
l64Lu12iwOp = 0x0a << 25
|
||||
l64OriOp = 0x0e << 22
|
||||
)
|
||||
|
||||
// loong64Imm12 reports whether v fits a signed 12-bit immediate.
|
||||
func loong64Imm12(v int64) bool { return v >= -2048 && v <= 2047 }
|
||||
|
||||
// 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.
|
||||
// branch displacements are in instructions and are computed from each
|
||||
// branch's own position.
|
||||
func loong64GuardBytes(fi loong64FrameInfo, blockStart int) []byte {
|
||||
// MOVV 16(g), R20 (g.stackguard0), g = R22.
|
||||
ws := []uint32{l64irr(l64loadStoreTable["MOVV"].ld, 16, 22, 20)}
|
||||
off := int64(fi.autosize - stackSmall)
|
||||
// beq appends BEQ R20, blockStart from the branch's own position.
|
||||
beq := func() {
|
||||
ws = append(ws, loong64Beqz(20, int32((blockStart-len(ws)*4)>>2)))
|
||||
}
|
||||
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)))
|
||||
beq()
|
||||
case 1:
|
||||
off := int32(fi.autosize - stackSmall)
|
||||
ws = append(ws, l64irr(l64DualTable["ADDV"].imm, int(-off), 3, 24))
|
||||
ws = append(ws, loong64MediumWords(off)...)
|
||||
ws = append(ws, l64rrr(l64DualTable["SGTU"].rrr, 24, 20, 20))
|
||||
ws = append(ws, loong64Beqz(20, int32((blockStart-12)>>2)))
|
||||
beq()
|
||||
default:
|
||||
off := int64(fi.autosize - stackSmall)
|
||||
movLen := 8 // LU12IW + ORI
|
||||
ws = append(ws, loong64Lu12iOri(30, off)...)
|
||||
// SGTU $off, SP, R24 catches the SP underflow a huge frame would
|
||||
// cause; BNE jumps to morestack in that case.
|
||||
ws = append(ws, loong64MatWords(nil, 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, loong64Bnez(24, int32((blockStart-len(ws)*4)>>2)))
|
||||
ws = append(ws, loong64MatWords(nil, -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)))
|
||||
beq()
|
||||
}
|
||||
return l64WordsLE(ws...)
|
||||
}
|
||||
|
||||
// loong64MediumWords emits the medium-class stack check for offset off: the
|
||||
// ADDV immediate when it fits, otherwise the same sequence with the constant
|
||||
// materialised in R30.
|
||||
func loong64MediumWords(off int64) []uint32 {
|
||||
if off <= 2048 {
|
||||
return []uint32{l64irr(l64DualTable["ADDV"].imm, int(-off), 3, 24)}
|
||||
}
|
||||
ws := loong64MatWords(nil, -off)
|
||||
return append(ws, l64rrr(l64DualTable["ADDV"].rrr, 30, 3, 24))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -150,12 +202,13 @@ 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.
|
||||
// loong64MoreStackBlock emits the trailing block: MOVV R1, R31 (save LR, the
|
||||
// toolchain's OR R1, R0, R31 expansion), 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
|
||||
l64rrr(l64DualTable["OR"].rrr, 0, 1, 31), // MOVV R1, R31 (OR R1, R0, R31)
|
||||
l64bbl(l64jumpTable["BL"], 0), // BL, patched by the linker
|
||||
}
|
||||
disp := (-(blockStart + 8)) >> 2
|
||||
ws = append(ws, l64bbl(l64jumpTable["B"], int(disp)))
|
||||
@@ -185,17 +238,37 @@ func loong64IsLeaf(t *ast.Text) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// loong64Prologue returns the prologue bytes for a loong64 function.
|
||||
// loong64Prologue returns the prologue bytes for a loong64 function. When
|
||||
// the LR store offset leaves the toolchain's 12-bit store range ([-2046,
|
||||
// 2045], BIG_12 = 2046) or the SP adjust immediate its 12-bit immediate
|
||||
// range, each switches to the R30 materialisation the assembler expands it
|
||||
// to: the store uses the rounding %hi/%lo split (LU12IW of (v+2048)>>12,
|
||||
// REGTMP += SP, store at the raw offset), the adjust the floor split
|
||||
// (LU12IW, ORI when the low part is non-zero, REGTMP += SP).
|
||||
func loong64Prologue(fi loong64FrameInfo) []byte {
|
||||
if fi.autosize == 0 {
|
||||
return nil
|
||||
}
|
||||
addiD := l64DualTable["ADDV"].imm
|
||||
return l64WordsLE(
|
||||
l64irr(l64loadStoreTable["MOVV"].st, -fi.autosize, 3, 1), // MOVV R1, -autosize(R3)
|
||||
l64irr(addiD, -fi.autosize, 3, 3), // ADDV $-autosize, R3
|
||||
l64irr(l64loadStoreTable["MOVV"].st, 0, 3, 1), // MOVV R1, 0(R3)
|
||||
)
|
||||
var ws []uint32
|
||||
storeBase := 3
|
||||
if fi.autosize > 2046 {
|
||||
// The store goes through REGTMP: LU12IW of the rounding split,
|
||||
// REGTMP += SP, then the store at REGTMP with the truncated offset.
|
||||
v := -int64(fi.autosize)
|
||||
ws = append(ws, l64ir(l64Lu12iwOp, int((v+2048)>>12), 30))
|
||||
ws = append(ws, l64rrr(l64DualTable["ADDV"].rrr, 3, 30, 30))
|
||||
storeBase = 30
|
||||
}
|
||||
ws = append(ws, l64irr(l64loadStoreTable["MOVV"].st, -fi.autosize, storeBase, 1)) // MOVV R1, -autosize(base)
|
||||
if loong64Imm12(-int64(fi.autosize)) {
|
||||
ws = append(ws, l64irr(addiD, -fi.autosize, 3, 3)) // ADDV $-autosize, R3
|
||||
} else {
|
||||
ws = append(ws, loong64MatWords(nil, -int64(fi.autosize))...)
|
||||
ws = append(ws, l64rrr(l64DualTable["ADDV"].rrr, 30, 3, 3))
|
||||
}
|
||||
ws = append(ws, l64irr(l64loadStoreTable["MOVV"].st, 0, 3, 1)) // MOVV R1, 0(R3)
|
||||
return l64WordsLE(ws...)
|
||||
}
|
||||
|
||||
// loong64Return returns the bytes for a RET: the epilogue (restore LR and
|
||||
@@ -207,14 +280,46 @@ func loong64Return(fi loong64FrameInfo) []byte {
|
||||
// MOVV 0(R3), R1, restore the link register.
|
||||
ws = append(ws, l64irr(l64loadStoreTable["MOVV"].ld, 0, 3, 1))
|
||||
}
|
||||
// ADDV $autosize, R3, close the frame.
|
||||
ws = append(ws, l64irr(l64DualTable["ADDV"].imm, fi.autosize, 3, 3))
|
||||
// ADDV $autosize, R3, close the frame (materialised when the
|
||||
// immediate does not fit).
|
||||
if loong64Imm12(int64(fi.autosize)) {
|
||||
ws = append(ws, l64irr(l64DualTable["ADDV"].imm, fi.autosize, 3, 3))
|
||||
} else {
|
||||
ws = append(ws, loong64MatWords(nil, int64(fi.autosize))...)
|
||||
ws = append(ws, l64rrr(l64DualTable["ADDV"].rrr, 30, 3, 3))
|
||||
}
|
||||
}
|
||||
// jirl r0, r1, 0, return.
|
||||
ws = append(ws, l64irr16(l64branchTable["JIRL"], 0, 1, 0))
|
||||
return l64WordsLE(ws...)
|
||||
}
|
||||
|
||||
// loong64StoreWords reports the prologue word count of the LR store, and
|
||||
// loong64AdjustWords the word count of an SP adjust of v: the immediate
|
||||
// forms when they fit, otherwise the R30 materialisation sequences.
|
||||
func loong64StoreWords(autosize int) int {
|
||||
if autosize > 2046 {
|
||||
return 3
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func loong64AdjustWords(v int64) int {
|
||||
if loong64Imm12(v) {
|
||||
return 1
|
||||
}
|
||||
return loong64MatLen(v) + 1
|
||||
}
|
||||
|
||||
// loong64EpilogueWords reports the epilogue word count the RET expands to.
|
||||
func loong64EpilogueWords(fi loong64FrameInfo) int {
|
||||
n := loong64AdjustWords(int64(fi.autosize))
|
||||
if !fi.leaf {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// loong64ResolvePseudo translates a pseudo-register memory reference into a
|
||||
// hardware base register and offset. x+N(FP) → (N + autosize + 8)(SP);
|
||||
// x-N(SP) → (autosize - N)(SP). Returns base = -1 for an unresolvable
|
||||
|
||||
Reference in New Issue
Block a user