feat(asm): give riscv64 the END and GETCALLERPC the toolchain accepts
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
0211d6672d
commit
76f8ba6403
3 files changed
+195
-6
No files matched your search
+99
-2
@@ -380,10 +380,14 @@ func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo, tlsSyms map[string]bool
|
||||
mnem := instr.Mnemonic.Text
|
||||
ops := instr.Operands
|
||||
mnem = riscvNormalisePseudo(mnem)
|
||||
if mnem == "FUNCDATA" || mnem == "PCDATA" {
|
||||
// The bookkeeping statements contribute no bytes.
|
||||
if mnem == "FUNCDATA" || mnem == "PCDATA" || mnem == "END" {
|
||||
// The bookkeeping statements and the function-end marker contribute
|
||||
// no bytes.
|
||||
return 0
|
||||
}
|
||||
if mnem == "GETCALLERPC" {
|
||||
return riscvGetCallerPCSize(ops, fi)
|
||||
}
|
||||
var immNeg bool
|
||||
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
||||
if mnem == "RET" {
|
||||
@@ -1136,6 +1140,12 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
return nil, fmt.Errorf("PCDATA expects $n, $m")
|
||||
}
|
||||
return nil, nil
|
||||
case "END":
|
||||
// The function-end marker: the toolchain accepts it anywhere in a
|
||||
// body, ignores whatever operands follow it, and emits nothing.
|
||||
return nil, nil
|
||||
case "GETCALLERPC":
|
||||
return encodeRISCVGetCallerPC(ops, fi, relocs)
|
||||
case "WORD":
|
||||
// WORD $w lays down a raw 32-bit little-endian word, in the range
|
||||
// [0, 0xffffffff] exactly as the toolchain's validation bounds it.
|
||||
@@ -2429,6 +2439,65 @@ func riscvFrameMemSize(op *ast.Operand, fi riscvFrameInfo) int {
|
||||
return len(riscvAddressInX31WithBase(off, rs1)) + 4
|
||||
}
|
||||
|
||||
// encodeRISCVGetCallerPC encodes the toolchain's GETCALLERPC rewrite: on
|
||||
// entry the caller's address sits in the link register, so a leaf reads it
|
||||
// straight from X1 (MOV X1, rd, which the compress pass turns into C.MV);
|
||||
// a body that calls out has clobbered X1, and reads the prologue's save at
|
||||
// 0(SP) instead (LD rd, 0(SP), compressed to C.LDSP). A memory destination
|
||||
// stores the address there through the MOV store paths, which the leaf form
|
||||
// shares with every width and offset; the framed form is the toolchain's
|
||||
// own rejection, its rewrite reading (SP) into a MOV with two memory ends.
|
||||
func encodeRISCVGetCallerPC(ops []*ast.Operand, fi riscvFrameInfo, relocs *[]Reloc) ([]byte, error) {
|
||||
if len(ops) == 0 {
|
||||
return nil, fmt.Errorf("GETCALLERPC: unsupported MOV")
|
||||
}
|
||||
dst := ops[len(ops)-1]
|
||||
if isMemOperand(dst) {
|
||||
if !fi.leaf {
|
||||
return nil, fmt.Errorf("GETCALLERPC: unsupported MOV")
|
||||
}
|
||||
if dst.Addr.Sym != nil && dst.Addr.Sym.Pseudo == "SB" {
|
||||
// A static destination rides the external store path, exactly
|
||||
// as MOV X1, sym(SB) does.
|
||||
return encodeRISCVSBStore(dst.Addr.Sym, 1, relocs), nil
|
||||
}
|
||||
if err := riscvWantMemBase("GETCALLERPC", "rs1", dst); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs1, off, err := riscvAccessMem("GETCALLERPC", dst, fi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return riscvFrameMemOp(riscvEnc{0x23, 0x3, 0x00}, true, 1, rs1, off), nil
|
||||
}
|
||||
n, bank := riscvBankedRegNum(operandRegName(dst))
|
||||
if bank != riscvBankInt {
|
||||
return nil, fmt.Errorf("GETCALLERPC: expected integer register in rd position but got non-integer register %s", operandRegName(dst))
|
||||
}
|
||||
if fi.leaf {
|
||||
return wordLE(riscvIType(riscvInstrTable["ADDI"], n, 1, 0)), nil
|
||||
}
|
||||
return wordLE(riscvIType(riscvInstrTable["LD"], n, 2, 0)), nil
|
||||
}
|
||||
|
||||
// riscvGetCallerPCSize returns the encoded size of a GETCALLERPC for the
|
||||
// layout pass: one word for the register reads, the store's own size
|
||||
// (expanded where the offset leaves imm12) for a memory destination.
|
||||
func riscvGetCallerPCSize(ops []*ast.Operand, fi riscvFrameInfo) int {
|
||||
if len(ops) == 0 {
|
||||
return 4
|
||||
}
|
||||
dst := ops[len(ops)-1]
|
||||
if isMemOperand(dst) && (dst.Addr.Sym == nil || dst.Addr.Sym.Pseudo != "SB") {
|
||||
if err := riscvWantMemOffset("GETCALLERPC", dst, fi); err == nil {
|
||||
if rs1, off := memFromOperandWithFrame(dst, fi); rs1 >= 0 && !fits12(off) {
|
||||
return len(riscvAddressInX31WithBase(off, rs1)) + 4
|
||||
}
|
||||
}
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
// riscvMemInstrSize returns the encoded size of a plain load or store
|
||||
// (integer and FP widths, one register end and one memory end) for the layout
|
||||
// pass: 4 bytes when the offset fits the signed 12-bit span, otherwise the
|
||||
@@ -3124,6 +3193,34 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) {
|
||||
return rvcCS(0x7, rvcReg3(rs2), rvcReg3(rs1), uint32(imm)), true
|
||||
}
|
||||
|
||||
case "GETCALLERPC":
|
||||
// The rewrite lands on MOV X1, rd (leaf), LD rd, 0(SP) (framed) or
|
||||
// SD X1, off(base) (leaf, memory destination); each compresses the
|
||||
// way its own shape does, the toolchain's compress() rules verbatim.
|
||||
if len(ops) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
dst := ops[len(ops)-1]
|
||||
if !isMemOperand(dst) {
|
||||
rd := regFromOperand(dst)
|
||||
if fi.leaf {
|
||||
if rd > 0 {
|
||||
return rvcCR(0x8, uint32(rd), 1), true // C.MV rd, X1
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
// LD rd, 0(SP): the SP form takes every destination but X0.
|
||||
if rd > 0 {
|
||||
return rvcLSP(0x3, uint32(rd), 0), true // C.LDSP rd, 0(SP)
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
if fi.leaf {
|
||||
if rs1, off := memFromOperandWithFrame(dst, fi); rs1 == 2 && off >= 0 && off < 512 && off%8 == 0 {
|
||||
return rvcSSP(0x7, 1, uint32(off)), true // C.SDSP X1, off
|
||||
}
|
||||
}
|
||||
|
||||
case "LW":
|
||||
rd, rs1, imm := extractLDParams(instr, fi)
|
||||
if rs1 == 2 && rd != 0 && rd != -1 && imm >= 0 && imm < 256 && imm%4 == 0 {
|
||||
|
||||
+11
-4
@@ -34,6 +34,11 @@ import (
|
||||
type riscvFrameInfo struct {
|
||||
autosize int // the real SP adjustment (locals + saved LR)
|
||||
|
||||
// leaf mirrors the toolchain's cursym.Leaf: the body makes no call, so
|
||||
// the link register still holds the caller's address. GETCALLERPC reads
|
||||
// it there; a framed body reads the prologue's save at 0(SP) instead.
|
||||
leaf bool
|
||||
|
||||
// Stack-split guard state: the toolchain emits the check for every
|
||||
// non-NOSPLIT function whose autosize is nonzero (a zero autosize is
|
||||
// "effectively NOSPLIT"); unlike amd64 and arm64 there is no leaf
|
||||
@@ -44,12 +49,12 @@ type riscvFrameInfo struct {
|
||||
|
||||
// riscvComputeFrame derives the frame layout for a TEXT function.
|
||||
func riscvComputeFrame(t *ast.Text) riscvFrameInfo {
|
||||
frame := frameSize(t)
|
||||
if frame != 0 || !riscvIsLeaf(t) {
|
||||
leaf := riscvIsLeaf(t)
|
||||
if frame := frameSize(t); frame != 0 || !leaf {
|
||||
// FixedFrameSize = 8: space for the saved link register. A
|
||||
// zero-frame non-leaf function still opens an 8-byte frame for LR.
|
||||
autosize := frame + 8
|
||||
fi := riscvFrameInfo{autosize: autosize}
|
||||
fi := riscvFrameInfo{autosize: autosize, leaf: leaf}
|
||||
if !hasNoSplitFlag(t) {
|
||||
fi.needSplit = true
|
||||
switch {
|
||||
@@ -63,7 +68,9 @@ func riscvComputeFrame(t *ast.Text) riscvFrameInfo {
|
||||
}
|
||||
return fi
|
||||
}
|
||||
return riscvFrameInfo{}
|
||||
// A zero-frame leaf: no prologue, no guard, and the link register still
|
||||
// holding the caller's address.
|
||||
return riscvFrameInfo{leaf: leaf}
|
||||
}
|
||||
|
||||
// hasNoSplitFlag reports whether the TEXT directive carries NOSPLIT.
|
||||
|
||||
@@ -241,6 +241,91 @@ func TestRISCVToolchainRejections(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRISCVGetCallerPC_Differential proves the toolchain's GETCALLERPC
|
||||
// rewrite: a leaf reads the caller's address straight from the link register
|
||||
// (MOV X1, rd, compressed to C.MV; X0 stays the discarded ADDI), a body that
|
||||
// calls out reads the prologue's save at 0(SP) (LD rd, 0(SP), compressed to
|
||||
// C.LDSP, the reserved SP destination included; X0 stays uncompressed), and a
|
||||
// memory destination stores it there, through the frame mapping and the
|
||||
// static-data path alike.
|
||||
func TestRISCVGetCallerPC_Differential(t *testing.T) {
|
||||
leaf := `#include "textflag.h"
|
||||
|
||||
TEXT ·leafgcp(SB), NOSPLIT, $0
|
||||
GETCALLERPC X5 // c.mv X5, X1
|
||||
GETCALLERPC X0 // addi X0, X1, 0, discarded
|
||||
GETCALLERPC 8(X6) // sd X1, 8(X6)
|
||||
GETCALLERPC x+8(FP) // c.sdsp X1, 16
|
||||
END
|
||||
RET
|
||||
`
|
||||
path := writeRISCVSrc(t, "gcp_leaf_riscv64.s", leaf)
|
||||
assertRISCVDifferential(t, path, leaf, "leafgcp")
|
||||
|
||||
framed := `#include "textflag.h"
|
||||
|
||||
TEXT ·framegcp(SB), $8
|
||||
CALL extcall(SB)
|
||||
GETCALLERPC X5 // c.ldsp X5, 0(SP)
|
||||
GETCALLERPC X2 // c.ldsp X2, 0(SP)
|
||||
GETCALLERPC X0 // ld X0, 0(SP), uncompressed
|
||||
END
|
||||
RET
|
||||
`
|
||||
path = writeRISCVSrc(t, "gcp_frame_riscv64.s", framed)
|
||||
assertRISCVDifferential(t, path, framed, "framegcp")
|
||||
}
|
||||
|
||||
// TestRISCVGetCallerPCRejections pins the rewrite's rejections: a framed
|
||||
// body with a memory destination would read (SP) into a MOV with two memory
|
||||
// ends, a non-integer register is the rd bank rejection, and no destination
|
||||
// at all is the toolchain's unsupported MOV.
|
||||
func TestRISCVGetCallerPCRejections(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
src string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "memory destination in a framed body",
|
||||
src: "CALL extcall(SB)\n\tGETCALLERPC 8(X6)\n",
|
||||
want: "GETCALLERPC: unsupported MOV",
|
||||
},
|
||||
{
|
||||
name: "float destination",
|
||||
src: "\tGETCALLERPC F5\n",
|
||||
want: "GETCALLERPC: expected integer register in rd position but got non-integer register F5",
|
||||
},
|
||||
{
|
||||
name: "vector destination",
|
||||
src: "\tGETCALLERPC V1\n",
|
||||
want: "GETCALLERPC: expected integer register in rd position but got non-integer register V1",
|
||||
},
|
||||
{
|
||||
name: "immediate destination",
|
||||
src: "\tGETCALLERPC $5\n",
|
||||
want: "GETCALLERPC: expected integer register in rd position",
|
||||
},
|
||||
{
|
||||
name: "no destination",
|
||||
src: "\tGETCALLERPC\n",
|
||||
want: "GETCALLERPC: unsupported MOV",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\n\nTEXT ·r(SB), NOSPLIT, $0\n"+tc.src+"\tRET\n")
|
||||
_, _, _, _, _, _, err := assembleRISCV(fn, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("source assembled, want rejection %q", tc.want)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.want) {
|
||||
t.Errorf("error %q does not carry %q", err.Error(), tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRISCVTLSRelocKind pins the relocation the TLS sequence records: one
|
||||
// R_RISCV_TLS_LE field spanning the LUI + ADDIW pair, the symbol named and
|
||||
// no addend for a plain tls(SB) reference.
|
||||
|
||||
Reference in new issue
Block a user