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
|
mnem := instr.Mnemonic.Text
|
||||||
ops := instr.Operands
|
ops := instr.Operands
|
||||||
mnem = riscvNormalisePseudo(mnem)
|
mnem = riscvNormalisePseudo(mnem)
|
||||||
if mnem == "FUNCDATA" || mnem == "PCDATA" {
|
if mnem == "FUNCDATA" || mnem == "PCDATA" || mnem == "END" {
|
||||||
// The bookkeeping statements contribute no bytes.
|
// The bookkeeping statements and the function-end marker contribute
|
||||||
|
// no bytes.
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
if mnem == "GETCALLERPC" {
|
||||||
|
return riscvGetCallerPCSize(ops, fi)
|
||||||
|
}
|
||||||
var immNeg bool
|
var immNeg bool
|
||||||
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
||||||
if mnem == "RET" {
|
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, fmt.Errorf("PCDATA expects $n, $m")
|
||||||
}
|
}
|
||||||
return nil, nil
|
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":
|
case "WORD":
|
||||||
// WORD $w lays down a raw 32-bit little-endian word, in the range
|
// WORD $w lays down a raw 32-bit little-endian word, in the range
|
||||||
// [0, 0xffffffff] exactly as the toolchain's validation bounds it.
|
// [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
|
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
|
// 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
|
// (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
|
// 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
|
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":
|
case "LW":
|
||||||
rd, rs1, imm := extractLDParams(instr, fi)
|
rd, rs1, imm := extractLDParams(instr, fi)
|
||||||
if rs1 == 2 && rd != 0 && rd != -1 && imm >= 0 && imm < 256 && imm%4 == 0 {
|
if rs1 == 2 && rd != 0 && rd != -1 && imm >= 0 && imm < 256 && imm%4 == 0 {
|
||||||
|
|||||||
+11
-4
@@ -34,6 +34,11 @@ import (
|
|||||||
type riscvFrameInfo struct {
|
type riscvFrameInfo struct {
|
||||||
autosize int // the real SP adjustment (locals + saved LR)
|
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
|
// Stack-split guard state: the toolchain emits the check for every
|
||||||
// non-NOSPLIT function whose autosize is nonzero (a zero autosize is
|
// non-NOSPLIT function whose autosize is nonzero (a zero autosize is
|
||||||
// "effectively NOSPLIT"); unlike amd64 and arm64 there is no leaf
|
// "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.
|
// riscvComputeFrame derives the frame layout for a TEXT function.
|
||||||
func riscvComputeFrame(t *ast.Text) riscvFrameInfo {
|
func riscvComputeFrame(t *ast.Text) riscvFrameInfo {
|
||||||
frame := frameSize(t)
|
leaf := riscvIsLeaf(t)
|
||||||
if frame != 0 || !riscvIsLeaf(t) {
|
if frame := frameSize(t); frame != 0 || !leaf {
|
||||||
// FixedFrameSize = 8: space for the saved link register. A
|
// FixedFrameSize = 8: space for the saved link register. A
|
||||||
// zero-frame non-leaf function still opens an 8-byte frame for LR.
|
// zero-frame non-leaf function still opens an 8-byte frame for LR.
|
||||||
autosize := frame + 8
|
autosize := frame + 8
|
||||||
fi := riscvFrameInfo{autosize: autosize}
|
fi := riscvFrameInfo{autosize: autosize, leaf: leaf}
|
||||||
if !hasNoSplitFlag(t) {
|
if !hasNoSplitFlag(t) {
|
||||||
fi.needSplit = true
|
fi.needSplit = true
|
||||||
switch {
|
switch {
|
||||||
@@ -63,7 +68,9 @@ func riscvComputeFrame(t *ast.Text) riscvFrameInfo {
|
|||||||
}
|
}
|
||||||
return fi
|
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.
|
// 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
|
// TestRISCVTLSRelocKind pins the relocation the TLS sequence records: one
|
||||||
// R_RISCV_TLS_LE field spanning the LUI + ADDIW pair, the symbol named and
|
// R_RISCV_TLS_LE field spanning the LUI + ADDIW pair, the symbol named and
|
||||||
// no addend for a plain tls(SB) reference.
|
// no addend for a plain tls(SB) reference.
|
||||||
|
|||||||
Reference in new issue
Block a user