feat(riscv64,loong64): encode AMO atomics, vector slices and bit ops
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -321,6 +321,16 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
||||
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
|
||||
// The LSX/LASX vector slice and the VMOVQ/XVMOVQ move family, before
|
||||
// the integer/FP table (their mnemonics overlap the table's 2R format
|
||||
// but resolve vector-bank registers).
|
||||
if code, handled, err := encodeLOONG64Vector(instr, mnem, fi); handled {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return code, nil
|
||||
}
|
||||
|
||||
enc, ok := l64InstrTable[mnem]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported loong64 instruction %q", mnem)
|
||||
@@ -1490,3 +1500,415 @@ func l64Label(op *ast.Operand) string {
|
||||
}
|
||||
return op.Raw
|
||||
}
|
||||
|
||||
// ---- LSX/LASX (V*/XV*) vector dispatch ----
|
||||
|
||||
// l64VecOperand describes a vector register operand: the 5-bit register
|
||||
// number, its bank and an optional width or element suffix (V0.B16,
|
||||
// V1.V[0], X3.WU[2]). The parser hands suffixed operands over verbatim
|
||||
// (the element index survives only in the raw text), so the suffix is
|
||||
// scanned from op.Raw.
|
||||
type l64VecOperand struct {
|
||||
num int // 5-bit register number
|
||||
lasx bool // X bank (LASX) rather than V (LSX)
|
||||
width byte // suffix width letter (B/H/W/V), 0 on a bare register
|
||||
lanes int // lane count of a width suffix (B16 → 16)
|
||||
elem int // element index of a .T[i] suffix
|
||||
hasEl bool // the suffix names an element (.T[i])
|
||||
unsig bool // the suffix carries the U marker (.BU[0])
|
||||
hasSuf bool // any suffix present
|
||||
}
|
||||
|
||||
// l64ParseVecOperand parses a vector register operand with an optional
|
||||
// width or element suffix. ok reports whether the operand names a vector
|
||||
// register at all (V or X bank, with or without a suffix).
|
||||
func l64ParseVecOperand(op *ast.Operand) (v l64VecOperand, ok bool) {
|
||||
if op.Kind == ast.OpImmediate {
|
||||
return v, false
|
||||
}
|
||||
name := strings.ReplaceAll(op.Raw, " ", "")
|
||||
if name == "" || (name[0] != 'V' && name[0] != 'X') {
|
||||
return v, false
|
||||
}
|
||||
i := 1
|
||||
num := 0
|
||||
for i < len(name) && name[i] >= '0' && name[i] <= '9' {
|
||||
num = num*10 + int(name[i]-'0')
|
||||
if num > 31 {
|
||||
return v, false
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i == 1 {
|
||||
return v, false // no register digits
|
||||
}
|
||||
v.num, v.lasx = num, name[0] == 'X'
|
||||
if i == len(name) {
|
||||
return v, true
|
||||
}
|
||||
if name[i] != '.' || i+2 > len(name) {
|
||||
return v, false
|
||||
}
|
||||
i++
|
||||
w := name[i]
|
||||
if w != 'B' && w != 'H' && w != 'W' && w != 'V' {
|
||||
return v, false
|
||||
}
|
||||
v.width, v.hasSuf = w, true
|
||||
i++
|
||||
if i < len(name) && name[i] == 'U' {
|
||||
v.unsig = true
|
||||
i++
|
||||
}
|
||||
if i < len(name) && name[i] == '[' {
|
||||
// Element form .T[i]: the closing bracket ends the operand.
|
||||
if name[len(name)-1] != ']' || i+2 > len(name)-1 {
|
||||
return v, false
|
||||
}
|
||||
idx := 0
|
||||
for _, c := range name[i+1 : len(name)-1] {
|
||||
if c < '0' || c > '9' {
|
||||
return v, false
|
||||
}
|
||||
idx = idx*10 + int(c-'0')
|
||||
if idx > 31 {
|
||||
return v, false
|
||||
}
|
||||
}
|
||||
v.elem, v.hasEl = idx, true
|
||||
return v, true
|
||||
}
|
||||
// Width form .T<lanes>: the trailing digits give the lane count.
|
||||
lanes := 0
|
||||
if i >= len(name) {
|
||||
return v, false
|
||||
}
|
||||
for ; i < len(name); i++ {
|
||||
if name[i] < '0' || name[i] > '9' {
|
||||
return v, false
|
||||
}
|
||||
lanes = lanes*10 + int(name[i]-'0')
|
||||
if lanes > 64 {
|
||||
return v, false
|
||||
}
|
||||
}
|
||||
v.lanes = lanes
|
||||
return v, true
|
||||
}
|
||||
|
||||
// l64VecSuffixWidth validates a width suffix against the bank (LSX:
|
||||
// B16/H8/W4/V2, LASX: B32/H16/W8/V4) and returns the encoded 2-bit width
|
||||
// selector of vreplgr2vr and vldrepl.
|
||||
func l64VecSuffixWidth(lasx bool, v l64VecOperand) (int, bool) {
|
||||
want := map[byte]int{'B': 16, 'H': 8, 'W': 4, 'V': 2}
|
||||
if lasx {
|
||||
want = map[byte]int{'B': 32, 'H': 16, 'W': 8, 'V': 4}
|
||||
}
|
||||
lanes, ok := want[v.width]
|
||||
if !ok || lanes != v.lanes {
|
||||
return 0, false
|
||||
}
|
||||
switch v.width {
|
||||
case 'B':
|
||||
return 0, true
|
||||
case 'H':
|
||||
return 1, true
|
||||
case 'W':
|
||||
return 2, true
|
||||
default:
|
||||
return 3, true
|
||||
}
|
||||
}
|
||||
|
||||
// l64VecElementBase validates an element suffix against the bank and
|
||||
// returns the encoded index field: the index rides in the rk field above a
|
||||
// per-width base (vpickve2gr/vinsgr2vr give ui4 to .b, ui3 to .h, ui2 to .w
|
||||
// and ui1 to .d). The LASX bank has no .b/.h element forms: the toolchain
|
||||
// rejects `XVMOVQ R4, X2.B[0]` and `XVMOVQ X3.B[31], R5`.
|
||||
func l64VecElementBase(lasx bool, v l64VecOperand) (int, bool) {
|
||||
limit, base := 0, 0
|
||||
switch v.width {
|
||||
case 'B':
|
||||
if lasx {
|
||||
return 0, false
|
||||
}
|
||||
limit, base = 15, 0
|
||||
case 'H':
|
||||
if lasx {
|
||||
return 0, false
|
||||
}
|
||||
limit, base = 7, 16
|
||||
case 'W':
|
||||
limit, base = 3, 24
|
||||
if lasx {
|
||||
limit, base = 7, 16
|
||||
}
|
||||
case 'V':
|
||||
limit, base = 1, 28
|
||||
if lasx {
|
||||
limit, base = 3, 24
|
||||
}
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
if v.elem > limit {
|
||||
return 0, false
|
||||
}
|
||||
return base + v.elem, true
|
||||
}
|
||||
|
||||
// encodeLOONG64Vector encodes the LSX/LASX mnemonics the table marks as
|
||||
// vector plus the VMOVQ/XVMOVQ move family. handled reports whether the
|
||||
// mnemonic belongs to the vector slice; the operand shapes and opcode
|
||||
// constants reproduce GOARCH=loong64 `go tool asm` exactly.
|
||||
func encodeLOONG64Vector(instr *ast.Instr, mnem string, fi loong64FrameInfo) ([]byte, bool, error) {
|
||||
if mnem == "VMOVQ" || mnem == "XVMOVQ" {
|
||||
code, err := encodeLOONG64Vmovq(mnem == "XVMOVQ", instr.Operands, fi)
|
||||
return code, true, err
|
||||
}
|
||||
lasx, ok := l64VecBank[mnem]
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
ops := instr.Operands
|
||||
bank := "V"
|
||||
if lasx {
|
||||
bank = "X"
|
||||
}
|
||||
vec := func(op *ast.Operand) (int, error) {
|
||||
v, isVec := l64ParseVecOperand(op)
|
||||
if !isVec || v.lasx != lasx || v.hasSuf {
|
||||
return -1, fmt.Errorf("%s: expected a bare %s0-%s31 vector register, got %q", mnem, bank, bank, op.Raw)
|
||||
}
|
||||
return v.num, nil
|
||||
}
|
||||
|
||||
// Two-operand forms (vpcnt.v): INSTR vj, vd.
|
||||
if l64Vec2R[mnem] {
|
||||
if len(ops) != 2 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
vj, err := vec(ops[0])
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
vd, err := vec(ops[1])
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
return l64wordLE(l64rr(l64InstrTable[mnem].op, vj, vd)), true, nil
|
||||
}
|
||||
|
||||
// Immediate forms: INSTR $imm, vd or INSTR $imm, vj, vd.
|
||||
if e, imm := l64VecImmInfo[mnem]; imm && len(ops) >= 2 && isImmOperand(ops[0]) {
|
||||
if len(ops) > 3 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
imm := int(immFromOperand(ops[0]))
|
||||
if imm < e.min || imm > e.max {
|
||||
return nil, true, fmt.Errorf("%s: immediate out of range [%d, %d]", mnem, e.min, e.max)
|
||||
}
|
||||
vd, err := vec(ops[len(ops)-1])
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
vj := vd
|
||||
if len(ops) == 3 {
|
||||
if vj, err = vec(ops[1]); err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
}
|
||||
return l64wordLE(l64irr(e.op, (imm+e.bias)&e.mask, vj, vd)), true, nil
|
||||
}
|
||||
|
||||
// Vector-to-condition forms: INSTR vj, FCCn.
|
||||
if l64InstrTable[mnem].format == l64Fvcf {
|
||||
if len(ops) != 2 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
vj, err := vec(ops[0])
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
if loong64RegClass(operandRegName(ops[1])) != l64ClsFCC {
|
||||
return nil, true, fmt.Errorf("%s: expected an FCC condition flag, got %q", mnem, ops[1].Raw)
|
||||
}
|
||||
fcc := loong64RegNum(operandRegName(ops[1]))
|
||||
return l64wordLE(l64rr(l64InstrTable[mnem].op, vj, fcc)), true, nil
|
||||
}
|
||||
|
||||
// Three-register forms: INSTR vk, vj, vd or INSTR vk, vd (vj = vd).
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
vk, err := vec(ops[0])
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
vd, err := vec(ops[len(ops)-1])
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
vj := vd
|
||||
if len(ops) == 3 {
|
||||
if vj, err = vec(ops[1]); err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
}
|
||||
return l64wordLE(l64rrr(l64InstrTable[mnem].op, vk, vj, vd)), true, nil
|
||||
}
|
||||
|
||||
// encodeLOONG64Vmovq encodes the VMOVQ/XVMOVQ move family. One mnemonic
|
||||
// covers the whole LSX/LASX transfer surface, dispatched by operand shape
|
||||
// exactly as the toolchain's table does:
|
||||
//
|
||||
// VMOVQ vd, off(rj) vst VMOVQ off(rj), vd vld
|
||||
// VMOVQ vd, (rj)(rk) vstx VMOVQ (rj)(rk), vd vldx
|
||||
// VMOVQ off(rj), vd.T vldrepl (load and replicate one element)
|
||||
// VMOVQ vj, vd vori.b $0 (a register move)
|
||||
// VMOVQ rj, vd.T vreplgr2vr (duplicate a general register)
|
||||
// VMOVQ vj.T[i], rd vpickve2gr (extract one element)
|
||||
// VMOVQ rj, vd.T[i] vinsgr2vr (insert one element)
|
||||
func encodeLOONG64Vmovq(lasx bool, ops []*ast.Operand, fi loong64FrameInfo) ([]byte, error) {
|
||||
enc := l64VmovqTable[lasx]
|
||||
bank := "V"
|
||||
if lasx {
|
||||
bank = "X"
|
||||
}
|
||||
if len(ops) != 2 {
|
||||
return nil, fmt.Errorf("VMOVQ expects 2 operands, got %d", len(ops))
|
||||
}
|
||||
src, srcVec := l64ParseVecOperand(ops[0])
|
||||
dst, dstVec := l64ParseVecOperand(ops[1])
|
||||
srcMem := isMemOperand(ops[0])
|
||||
dstMem := isMemOperand(ops[1])
|
||||
srcIdx := srcMem && ops[0].Addr.Index != ""
|
||||
dstIdx := dstMem && ops[1].Addr.Index != ""
|
||||
intReg := func(op *ast.Operand) (int, error) {
|
||||
if isMemOperand(op) {
|
||||
return -1, fmt.Errorf("VMOVQ: expected a general register, got %q", op.Raw)
|
||||
}
|
||||
name := operandRegName(op)
|
||||
if loong64RegClass(name) != l64ClsGR {
|
||||
return -1, fmt.Errorf("VMOVQ: expected a general register, got %q", op.Raw)
|
||||
}
|
||||
return loong64RegNum(name), nil
|
||||
}
|
||||
|
||||
// Register move: VMOVQ vj, vd (vori.b/xvori.b with the zero constant),
|
||||
// both operands bare registers of the same bank.
|
||||
if srcVec && dstVec {
|
||||
if src.hasSuf || dst.hasSuf {
|
||||
return nil, fmt.Errorf("VMOVQ: a register move takes bare %s registers", bank)
|
||||
}
|
||||
if src.lasx != lasx || dst.lasx != lasx {
|
||||
return nil, fmt.Errorf("VMOVQ: expected %s-bank vector registers", bank)
|
||||
}
|
||||
return l64wordLE(l64rr(enc.move, src.num, dst.num)), nil
|
||||
}
|
||||
|
||||
// Store: VMOVQ vd, off(rj) or VMOVQ vd, (rj)(rk).
|
||||
if srcVec && dstMem {
|
||||
if src.hasSuf || src.lasx != lasx {
|
||||
return nil, fmt.Errorf("VMOVQ: expected a bare %s0-%s31 register as the stored value", bank, bank)
|
||||
}
|
||||
if dstIdx {
|
||||
rj, rk := loong64RegNum(ops[1].Addr.Base), loong64RegNum(ops[1].Addr.Index)
|
||||
if rj < 0 || rk < 0 {
|
||||
return nil, fmt.Errorf("VMOVQ: invalid register operand")
|
||||
}
|
||||
return l64wordLE(l64rrr(enc.stx, rk, rj, src.num)), nil
|
||||
}
|
||||
rj, off := l64MemWithFrame(ops[1], fi)
|
||||
if rj < 0 || off < -2048 || off > 2047 {
|
||||
return nil, fmt.Errorf("VMOVQ: store offset out of range [-2048, 2047]")
|
||||
}
|
||||
return l64wordLE(l64irr(enc.st, int(off), rj, src.num)), nil
|
||||
}
|
||||
|
||||
// Load: VMOVQ off(rj), vd, the indexed VMOVQ (rj)(rk), vd, and the
|
||||
// load-and-replicate form VMOVQ off(rj), vd.T.
|
||||
if srcMem && dstVec {
|
||||
if dst.lasx != lasx {
|
||||
return nil, fmt.Errorf("VMOVQ: expected %s-bank vector registers", bank)
|
||||
}
|
||||
if srcIdx {
|
||||
if dst.hasSuf {
|
||||
return nil, fmt.Errorf("VMOVQ: an indexed load takes a bare %s register", bank)
|
||||
}
|
||||
rj, rk := loong64RegNum(ops[0].Addr.Base), loong64RegNum(ops[0].Addr.Index)
|
||||
if rj < 0 || rk < 0 {
|
||||
return nil, fmt.Errorf("VMOVQ: invalid register operand")
|
||||
}
|
||||
return l64wordLE(l64rrr(enc.ldx, rk, rj, dst.num)), nil
|
||||
}
|
||||
rj, off := l64MemWithFrame(ops[0], fi)
|
||||
if rj < 0 || off < -2048 || off > 2047 {
|
||||
return nil, fmt.Errorf("VMOVQ: load offset out of range [-2048, 2047]")
|
||||
}
|
||||
op := enc.ld
|
||||
if dst.hasSuf {
|
||||
w, ok := l64VecSuffixWidth(lasx, dst)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("VMOVQ: invalid replicate width suffix %q", ops[1].Raw)
|
||||
}
|
||||
switch w {
|
||||
case 0:
|
||||
op = enc.replB
|
||||
case 1:
|
||||
op = enc.replH
|
||||
case 2:
|
||||
op = enc.replW
|
||||
default:
|
||||
op = enc.replD
|
||||
}
|
||||
}
|
||||
return l64wordLE(l64irr(op, int(off), rj, dst.num)), nil
|
||||
}
|
||||
|
||||
// Element extract: VMOVQ vj.T[i], rd (vpickve2gr, signed or unsigned).
|
||||
if srcVec && src.hasEl && !dstVec && !dstMem {
|
||||
if src.lasx != lasx {
|
||||
return nil, fmt.Errorf("VMOVQ: expected %s-bank vector registers", bank)
|
||||
}
|
||||
idx, ok := l64VecElementBase(lasx, src)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("VMOVQ: invalid element suffix %q", ops[0].Raw)
|
||||
}
|
||||
rd, err := intReg(ops[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
op := enc.pickS
|
||||
if src.unsig {
|
||||
op = enc.pickU
|
||||
}
|
||||
return l64wordLE(l64irr(op, idx, src.num, rd)), nil
|
||||
}
|
||||
|
||||
// Insert and duplicate: VMOVQ rj, vd.T[i] (vinsgr2vr) and
|
||||
// VMOVQ rj, vd.T (vreplgr2vr).
|
||||
if !srcVec && !srcMem && dstVec && dst.hasSuf {
|
||||
if dst.lasx != lasx {
|
||||
return nil, fmt.Errorf("VMOVQ: expected %s-bank vector registers", bank)
|
||||
}
|
||||
rs, err := intReg(ops[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dst.hasEl {
|
||||
idx, ok := l64VecElementBase(lasx, dst)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("VMOVQ: invalid element suffix %q", ops[1].Raw)
|
||||
}
|
||||
return l64wordLE(l64irr(enc.ins, idx, rs, dst.num)), nil
|
||||
}
|
||||
w, ok := l64VecSuffixWidth(lasx, dst)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("VMOVQ: invalid width suffix %q", ops[1].Raw)
|
||||
}
|
||||
return l64wordLE(l64irr(enc.dup, w, rs, dst.num)), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("VMOVQ: unsupported operand combination %q, %q", ops[0].Raw, ops[1].Raw)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user