feat(asm): encode the explicit riscv64 compressed instructions

The C extension's own spellings were names the table carried and the
encoder refused: CLWSP stopped the corpus audit's riscv64 file first.
Thirty-eight mnemonics now encode directly to their halfword, with the
toolchain's operand spellings and validation: the stack loads and stores
pin their base to SP, the register-based loads, stores and arithmetic
carry prime registers, CLUI refuses zero and SP, CADDI4SPN scales by
four, CADDI16SP by sixteen, and CJ, CBEQZ and CBNEZ resolve their N(PC)
targets against the final layout, taking a two-byte placeholder in the
early passes so the offsets stay honest.  CAND with an immediate is the
toolchain's C.ANDI spelling.  The toolchain's whole C extension testdata
block is a differential test, halfword for halfword, beside a range test
at the toolchain's own boundaries.

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 00:47:27 +02:00
1 parent ddfa33ccc1
commit e43fa39dc9
2 files changed
+716 -8

No files matched your search

+574 -8
View File
@@ -69,10 +69,10 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
}
// Pass 2: encode each instruction using Pass-1 offsets. A branch or
// jump the offsets prove overlong encodes to a 4-byte placeholder: the
// relaxation pass rewrites it before the final encoding. pcRelPcs is
// unavailable this early, so the N(PC) forms take the same placeholder
// path.
// jump the offsets prove overlong encodes to a placeholder of the
// instruction's own size: the relaxation pass rewrites it before the
// final encoding. pcRelPcs is unavailable this early, so the N(PC)
// forms take the same placeholder path.
pc := len(prologue)
for i := range recs {
branchLike := isBranchLike(recs[i].instr.Mnemonic.Text) || riscvIsCondBranch(recs[i].instr.Mnemonic.Text)
@@ -81,7 +81,7 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%s: %w", recs[i].instr.Mnemonic.Text, err)
}
if err != nil {
code = make([]byte, 4)
code = make([]byte, riscvInstrSize(recs[i].instr, fi))
}
recs[i].code = code
pc += len(code)
@@ -460,6 +460,9 @@ func riscvExtendedSize(mnem string, ops []*ast.Operand) int {
case "RORIW":
return 12
}
if isRVCInstr(mnem) {
return 2
}
return 4
}
@@ -511,7 +514,8 @@ func riscvRevShiftSize(mnem string, ops []*ast.Operand) int {
// recalculated offsets after compression.
func isBranchLike(mnem string) bool {
switch mnem {
case "BEQ", "BNE", "BLT", "BGE", "BLTU", "BGEU", "JMP", "JAL":
case "BEQ", "BNE", "BLT", "BGE", "BLTU", "BGEU", "JMP", "JAL",
"CJ", "CBEQZ", "CBNEZ":
return true
}
return false
@@ -895,6 +899,14 @@ func riscvPCRelOffset(instr *ast.Instr) (int, bool) {
if len(instr.Operands) < 2 {
return 0, false
}
case "CJ":
if len(instr.Operands) != 1 {
return 0, false
}
case "CBEQZ", "CBNEZ":
if len(instr.Operands) != 2 {
return 0, false
}
default:
return 0, false
}
@@ -1426,7 +1438,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
// The toolchain's synthesised instructions and the RVV slice: expanded
// encodings the main table does not carry. FSGNJD is a plain table
// entry and stays with the FP arithmetic path.
if code, handled, err := encodeRISCVExtended(mnem, instr, pc, offsets); handled {
if code, handled, err := encodeRISCVExtended(mnem, instr, pc, offsets, pcRelPcs); handled {
if err != nil {
return nil, err
}
@@ -2693,8 +2705,13 @@ func extractITypeParams(instr *ast.Instr) (rd, rs1 int, imm int32) {
// this group; err carries the diagnostic when it does but cannot be encoded.
// Each expansion reproduces the toolchain's instruction-for-instruction
// sequence, including its use of X31 (TMP) and its RVC compression.
func encodeRISCVExtended(mnem string, instr *ast.Instr, pc int, offsets map[string]int) ([]byte, bool, error) {
func encodeRISCVExtended(mnem string, instr *ast.Instr, pc int, offsets map[string]int, pcRelPcs map[*ast.Instr]int) ([]byte, bool, error) {
ops := instr.Operands
switch {
case isRVCInstr(mnem):
code, err := encodeRISCVCompressed(mnem, instr, pc, offsets, pcRelPcs)
return code, true, err
}
switch mnem {
case "NOP":
if len(ops) != 0 {
@@ -2987,6 +3004,555 @@ func riscvFoldedMove(rd, rs int) []byte {
}
}
// isRVCInstr reports whether m is one of the explicit compressed-instruction
// mnemonics: the toolchain's own spellings, encoded directly rather than
// reached by compressing a 32-bit form.
func isRVCInstr(m string) bool {
switch m {
case "CLWSP", "CLDSP", "CFLDSP", "CSWSP", "CSDSP", "CFSDSP",
"CLW", "CLD", "CFLD", "CSW", "CSD", "CFSD",
"CJ", "CJR", "CJALR", "CBEQZ", "CBNEZ",
"CLI", "CLUI", "CADD", "CADDI", "CADDW", "CADDIW",
"CADDI16SP", "CADDI4SPN",
"CSLLI", "CSRLI", "CSRAI", "CANDI",
"CMV", "CAND", "COR", "CXOR", "CSUB", "CSUBW",
"CNOP", "CEBREAK":
return true
}
return false
}
// encodeRISCVCompressed encodes one explicit RVC mnemonic to its 16-bit
// halfword, with the toolchain's operand spellings and its validation:
// stack-relative loads and stores pin their base to SP, the register-based
// ones and the CA arithmetic to the prime registers x8-x15, and every
// immediate carries its instruction's own range and scale.
func encodeRISCVCompressed(mnem string, instr *ast.Instr, pc int, offsets map[string]int, pcRelPcs map[*ast.Instr]int) ([]byte, error) {
ops := instr.Operands
immOf := func(op *ast.Operand) (int64, error) {
v, ok := riscvRawImm(op)
if !ok {
return 0, fmt.Errorf("%s expects an immediate", mnem)
}
return v, nil
}
// stackMem accepts a bare offset(SP) reference: the explicit compressed
// stack instructions pin their base to the hardware SP, so a frame
// reference (name+off(SP)) is not one.
stackMem := func(op *ast.Operand) (int64, bool) {
if op.Addr.Sym != nil || op.Addr.Base != "SP" {
return 0, false
}
return op.Addr.Offset, true
}
// regMem accepts a bare offset(rs) reference with a prime base register.
regMem := func(op *ast.Operand) (int, int64, bool) {
if op.Addr.Sym != nil || op.Addr.Base == "" {
return 0, 0, false
}
rs1 := riscvRegNum(op.Addr.Base)
return rs1, op.Addr.Offset, true
}
prime := func(r int) bool { return r >= 8 && r <= 15 }
branchTarget := func(op *ast.Operand) (int, error) {
if op.Addr.Sym == nil && op.Addr.Base == "PC" {
n := int(op.Addr.Offset)
// The target lands in the final layout; pass 2 encodes ahead of
// it with a placeholder, so the missing map is a range error like
// any unresolved branch.
if pcRelPcs == nil {
return 0, &riscvRangeError{fmt.Sprintf("%s: PC-relative target %d out of range", mnem, n)}
}
target, ok := pcRelPcs[instr]
if !ok {
return 0, &riscvRangeError{fmt.Sprintf("%s: PC-relative target %d out of range", mnem, n)}
}
return target - pc, nil
}
target := labelFromOperand(op)
off, ok := offsets[target]
if !ok {
return 0, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
}
return off - pc, nil
}
switch {
// Compressed stack-pointer-based loads and stores: offset(SP), rd.
case mnem == "CLWSP" || mnem == "CLDSP" || mnem == "CFLDSP":
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
off, ok := stackMem(ops[0])
if !ok {
return nil, fmt.Errorf("%s: rs2 must be SP/X2", mnem)
}
rd := regFromOperand(ops[1])
if rd < 0 {
return nil, fmt.Errorf("%s: expected integer register in rd position", mnem)
}
scale, hi := int64(4), int64(255)
funct3 := uint32(0x2)
if mnem != "CLWSP" {
scale, hi, funct3 = 8, 511, 0x3
}
if mnem == "CFLDSP" {
funct3 = 0x1
}
if off < 0 || off > hi {
return nil, fmt.Errorf("%s: offset %d must be in range [0, %d]", mnem, off, hi)
}
if off%scale != 0 {
return nil, fmt.Errorf("%s: offset %d must be a multiple of %d", mnem, off, scale)
}
return word16(rvcLSP(funct3, uint32(rd), uint32(off))), nil
case mnem == "CSWSP" || mnem == "CSDSP" || mnem == "CFSDSP":
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rs2 := regFromOperand(ops[0])
off, ok := stackMem(ops[1])
if !ok {
return nil, fmt.Errorf("%s: rd must be SP/X2", mnem)
}
if rs2 < 0 {
return nil, fmt.Errorf("%s: expected integer register in rs2 position", mnem)
}
scale, hi, funct3 := int64(4), int64(255), uint32(0x6)
if mnem != "CSWSP" {
scale, hi, funct3 = 8, 511, 0x7
}
if mnem == "CFSDSP" {
funct3 = 0x5
}
if off < 0 || off > hi {
return nil, fmt.Errorf("%s: offset %d must be in range [0, %d]", mnem, off, hi)
}
if off%scale != 0 {
return nil, fmt.Errorf("%s: offset %d must be a multiple of %d", mnem, off, scale)
}
return word16(rvcSSP(funct3, uint32(rs2), uint32(off))), nil
// Compressed register-based loads and stores: offset(rs), rd, all prime.
case mnem == "CLW" || mnem == "CLD" || mnem == "CFLD":
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rs1, off, ok := regMem(ops[0])
if !ok || !prime(rs1) {
return nil, fmt.Errorf("%s: expected integer prime register in rs1 position", mnem)
}
rd := regFromOperand(ops[1])
if rd < 0 || !prime(rd) {
return nil, fmt.Errorf("%s: expected integer prime register in rd position", mnem)
}
scale, hi, funct3 := int64(4), int64(127), uint32(0x2)
if mnem != "CLW" {
scale, hi, funct3 = 8, 255, 0x3
}
if mnem == "CFLD" {
funct3 = 0x1
}
if off < 0 || off > hi {
return nil, fmt.Errorf("%s: offset %d must be in range [0, %d]", mnem, off, hi)
}
if off%scale != 0 {
return nil, fmt.Errorf("%s: offset %d must be a multiple of %d", mnem, off, scale)
}
return word16(rvcCL(funct3, uint32(rvcReg3(rd)), uint32(rvcReg3(rs1)), uint32(off))), nil
case mnem == "CSW" || mnem == "CSD" || mnem == "CFSD":
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rs2 := regFromOperand(ops[0])
rs1, off, ok := regMem(ops[1])
if !ok || !prime(rs1) {
return nil, fmt.Errorf("%s: expected integer prime register in rs1 position", mnem)
}
if rs2 < 0 || !prime(rs2) {
return nil, fmt.Errorf("%s: expected integer prime register in rs2 position", mnem)
}
scale, hi, funct3 := int64(4), int64(127), uint32(0x6)
if mnem != "CSW" {
scale, hi, funct3 = 8, 255, 0x7
}
if mnem == "CFSD" {
funct3 = 0x5
}
if off < 0 || off > hi {
return nil, fmt.Errorf("%s: offset %d must be in range [0, %d]", mnem, off, hi)
}
if off%scale != 0 {
return nil, fmt.Errorf("%s: offset %d must be a multiple of %d", mnem, off, scale)
}
return word16(rvcCS(funct3, uint32(rvcReg3(rs2)), uint32(rvcReg3(rs1)), uint32(off))), nil
// Compressed control transfer.
case mnem == "CJ" || mnem == "CBEQZ" || mnem == "CBNEZ":
if mnem == "CJ" && len(ops) != 1 {
return nil, fmt.Errorf("%s expects 1 operand, got %d", mnem, len(ops))
}
if mnem != "CJ" && len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rs1 := 0
if mnem != "CJ" {
rs1 = regFromOperand(ops[0])
if rs1 < 0 || !prime(rs1) {
return nil, fmt.Errorf("%s: expected integer prime register in rs1", mnem)
}
}
off, err := branchTarget(ops[len(ops)-1])
if err != nil {
return nil, err
}
hi, lo := 2046, -2048
if mnem != "CJ" {
hi, lo = 254, -256
}
if off > hi || off < lo || off%2 != 0 {
return nil, fmt.Errorf("%s: branch target %d out of range [%d, %d]", mnem, off, lo, hi)
}
if mnem == "CJ" {
return word16(rvcCJ(int32(off))), nil
}
funct3 := uint32(0x6)
if mnem == "CBNEZ" {
funct3 = 0x7
}
return word16(rvcCB(funct3, uint32(rvcReg3(rs1)), int32(off))), nil
case mnem == "CJR" || mnem == "CJALR":
if len(ops) != 1 {
return nil, fmt.Errorf("%s expects 1 operand, got %d", mnem, len(ops))
}
rs1 := regFromOperand(ops[0])
if rs1 < 0 {
return nil, fmt.Errorf("%s: expected integer register in rs1", mnem)
}
if rs1 == 0 {
return nil, fmt.Errorf("%s: cannot use register X0 in rs1", mnem)
}
funct4 := uint32(0x8)
if mnem == "CJALR" {
funct4 = 0x9
}
return word16(rvcCR(funct4, uint32(rs1), 0)), nil
// Compressed constant generation.
case mnem == "CLI":
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
imm, err := immOf(ops[0])
if err != nil {
return nil, err
}
if imm < -32 || imm > 31 {
return nil, fmt.Errorf("%s: immediate %d must be in range [-32, 31]", mnem, imm)
}
rd := regFromOperand(ops[1])
if rd < 0 {
return nil, fmt.Errorf("%s: expected integer register in rd position", mnem)
}
if rd == 0 {
return nil, fmt.Errorf("%s: cannot use register X0 in rd", mnem)
}
return word16(rvcCI(0x2, uint32(rd), uint32(imm)&0x3F)), nil
case mnem == "CLUI":
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
imm, err := immOf(ops[0])
if err != nil {
return nil, err
}
if imm == 0 {
return nil, fmt.Errorf("%s: immediate cannot be zero", mnem)
}
if imm < -32 || imm > 31 {
return nil, fmt.Errorf("%s: immediate %d must be in range [-32, 31]", mnem, imm)
}
rd := regFromOperand(ops[1])
if rd < 0 {
return nil, fmt.Errorf("%s: expected integer register in rd position", mnem)
}
if rd == 0 {
return nil, fmt.Errorf("%s: cannot use register X0 in rd", mnem)
}
if rd == 2 {
return nil, fmt.Errorf("%s: cannot use register SP/X2 in rd", mnem)
}
return word16(rvcCI(0x3, uint32(rd), uint32(imm)&0x3F)), nil
// Compressed integer register-immediate operations.
case (mnem == "CADD" || mnem == "CADDI") && len(ops) >= 1 && isImmOperand(ops[0]),
(mnem == "CADDW" || mnem == "CADDIW") && len(ops) >= 1 && isImmOperand(ops[0]):
if len(ops) != 2 && len(ops) != 3 {
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
imm, err := immOf(ops[0])
if err != nil {
return nil, err
}
if imm < -32 || imm > 31 {
return nil, fmt.Errorf("%s: immediate %d must be in range [-32, 31]", mnem, imm)
}
if (mnem == "CADD" || mnem == "CADDI") && imm == 0 {
return nil, fmt.Errorf("%s: immediate cannot be zero", mnem)
}
rd := regFromOperand(ops[1])
if len(ops) == 3 {
if rd != regFromOperand(ops[2]) {
return nil, fmt.Errorf("%s: rd must be the same as rs1", mnem)
}
}
if rd < 0 {
return nil, fmt.Errorf("%s: expected integer register in rd position", mnem)
}
funct3 := uint32(0x0)
if mnem == "CADDW" || mnem == "CADDIW" {
funct3 = 0x1
}
return word16(rvcCI(funct3, uint32(rd), uint32(imm)&0x3F)), nil
case mnem == "CADDI16SP":
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
imm, err := immOf(ops[0])
if err != nil {
return nil, err
}
if imm == 0 {
return nil, fmt.Errorf("%s: immediate cannot be zero", mnem)
}
if imm < -512 || imm > 511 {
return nil, fmt.Errorf("%s: immediate %d must be in range [-512, 511]", mnem, imm)
}
if imm%16 != 0 {
return nil, fmt.Errorf("%s: immediate %d must be a multiple of 16", mnem, imm)
}
rd := regFromOperand(ops[1])
if rd != 2 {
return nil, fmt.Errorf("%s: rd must be SP/X2", mnem)
}
return word16(rvcADDI16SP(2, int32(imm))), nil
case mnem == "CADDI4SPN":
if len(ops) != 3 {
return nil, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
}
imm, err := immOf(ops[0])
if err != nil {
return nil, err
}
if imm == 0 {
return nil, fmt.Errorf("%s: immediate cannot be zero", mnem)
}
if imm < 0 || imm > 1023 {
return nil, fmt.Errorf("%s: immediate %d must be in range [0, 1023]", mnem, imm)
}
if imm%4 != 0 {
return nil, fmt.Errorf("%s: immediate %d must be a multiple of 4", mnem, imm)
}
if regFromOperand(ops[1]) != 2 {
return nil, fmt.Errorf("%s: SP/X2 must be in rs1", mnem)
}
rd := regFromOperand(ops[2])
if rd < 0 || !prime(rd) {
return nil, fmt.Errorf("%s: expected integer prime register in rd", mnem)
}
return word16(rvcCIW(0x0, uint32(rvcReg3(rd)), uint32(imm))), nil
// Compressed shifts and the immediate C.ANDI: rd is the source too.
// CAND with an immediate first operand is the toolchain's C.ANDI
// spelling (CANDI $imm and CAND $imm encode identically).
case mnem == "CSLLI" || mnem == "CSRLI" || mnem == "CSRAI" || mnem == "CANDI",
mnem == "CAND" && len(ops) >= 1 && isImmOperand(ops[0]):
if mnem == "CAND" {
mnem = "CANDI"
}
if len(ops) != 2 && len(ops) != 3 {
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
imm, err := immOf(ops[0])
if err != nil {
return nil, err
}
if imm == 0 && mnem != "CANDI" {
return nil, fmt.Errorf("%s: immediate cannot be zero", mnem)
}
if mnem == "CANDI" {
if imm < -32 || imm > 31 {
return nil, fmt.Errorf("%s: immediate %d must be in range [-32, 31]", mnem, imm)
}
} else {
if imm < 0 || imm > 63 {
return nil, fmt.Errorf("%s: immediate %d must be in range [0, 63]", mnem, imm)
}
}
rd := regFromOperand(ops[1])
if len(ops) == 3 {
if rd != regFromOperand(ops[2]) {
return nil, fmt.Errorf("%s: rd must be the same as rs1", mnem)
}
}
if rd < 0 {
return nil, fmt.Errorf("%s: expected integer register in rd position", mnem)
}
if mnem == "CSLLI" {
if rd == 0 {
return nil, fmt.Errorf("%s: cannot use register X0 in rd", mnem)
}
return word16(rvcSLLI(uint32(rd), uint32(imm)&0x3F)), nil
}
if !prime(rd) {
return nil, fmt.Errorf("%s: expected integer prime register in rd", mnem)
}
funct2 := uint32(0x0)
switch mnem {
case "CSRAI":
funct2 = 0x1
case "CANDI":
funct2 = 0x2
}
return word16(rvcCBShift(funct2, uint32(rvcReg3(rd)), uint32(imm)&0x3F)), nil
// Compressed integer register-register operations: destination last.
case mnem == "CMV":
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rs2 := regFromOperand(ops[0])
rd := regFromOperand(ops[1])
if rs2 < 0 {
return nil, fmt.Errorf("%s: expected integer register in rs2", mnem)
}
if rd < 0 {
return nil, fmt.Errorf("%s: expected integer register in rd", mnem)
}
if rs2 == 0 {
return nil, fmt.Errorf("%s: cannot use register X0 in rs2", mnem)
}
if rd == 0 {
return nil, fmt.Errorf("%s: cannot use register X0 in rd", mnem)
}
return word16(rvcCR(0x8, uint32(rd), uint32(rs2))), nil
case mnem == "CADD" || mnem == "CAND" || mnem == "COR" || mnem == "CXOR" || mnem == "CSUB" || mnem == "CSUBW":
if len(ops) != 2 && len(ops) != 3 {
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
rs2 := regFromOperand(ops[0])
rd := regFromOperand(ops[1])
if len(ops) == 3 {
if rd != regFromOperand(ops[2]) {
return nil, fmt.Errorf("%s: rd must be the same as rs1", mnem)
}
}
if rs2 < 0 {
return nil, fmt.Errorf("%s: expected integer register in rs2", mnem)
}
if rd < 0 {
return nil, fmt.Errorf("%s: expected integer register in rd", mnem)
}
if rs2 == 0 {
return nil, fmt.Errorf("%s: cannot use register X0 in rs2", mnem)
}
if rd == 0 {
return nil, fmt.Errorf("%s: cannot use register X0 in rd", mnem)
}
if mnem == "CADD" {
return word16(rvcCR(0x9, uint32(rd), uint32(rs2))), nil
}
if !prime(rd) || !prime(rs2) {
return nil, fmt.Errorf("%s: expected integer prime registers", mnem)
}
funct6 := uint32(0x23)
funct2 := uint32(0x0)
switch mnem {
case "CAND":
funct2 = 0x3
case "COR":
funct2 = 0x2
case "CXOR":
funct2 = 0x1
case "CSUBW":
funct6 = 0x27
}
return word16(rvcCA(funct6, funct2, uint32(rvcReg3(rd)), uint32(rvcReg3(rs2)))), nil
case mnem == "CADDW":
if len(ops) < 1 {
return nil, fmt.Errorf("%s expects operands", mnem)
}
if isImmOperand(ops[0]) {
if len(ops) != 2 && len(ops) != 3 {
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
imm, err := immOf(ops[0])
if err != nil {
return nil, err
}
if imm < -32 || imm > 31 {
return nil, fmt.Errorf("%s: immediate %d must be in range [-32, 31]", mnem, imm)
}
rd := regFromOperand(ops[1])
if len(ops) == 3 && rd != regFromOperand(ops[2]) {
return nil, fmt.Errorf("%s: rd must be the same as rs1", mnem)
}
if rd < 0 {
return nil, fmt.Errorf("%s: expected integer register in rd position", mnem)
}
return word16(rvcCI(0x1, uint32(rd), uint32(imm)&0x3F)), nil
}
if len(ops) != 2 && len(ops) != 3 {
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
rs2 := regFromOperand(ops[0])
rd := regFromOperand(ops[1])
if len(ops) == 3 && rd != regFromOperand(ops[2]) {
return nil, fmt.Errorf("%s: rd must be the same as rs1", mnem)
}
if rs2 < 0 || rd < 0 || !prime(rs2) || !prime(rd) {
return nil, fmt.Errorf("%s: expected integer prime registers", mnem)
}
return word16(rvcCA(0x27, 0x1, uint32(rvcReg3(rd)), uint32(rvcReg3(rs2)))), nil
case mnem == "CNOP":
if len(ops) != 0 {
return nil, fmt.Errorf("%s expects no operands", mnem)
}
return word16(0x0001), nil
case mnem == "CEBREAK":
if len(ops) != 0 {
return nil, fmt.Errorf("%s expects no operands", mnem)
}
return word16(0x9002), nil
}
return nil, fmt.Errorf("unsupported RISC-V instruction %q", mnem)
}
// rvcCJ encodes a CJ-type compressed jump: the 11-bit displacement in the
// order [11|4|9:8|10|6|7|3:1|5], funct3 5, op 01.
func rvcCJ(off int32) uint16 {
packed := encodeRVCPattern(uint32(off), []int{11, 4, 9, 8, 10, 6, 7, 3, 2, 1, 5})
return uint16((0x5 << 13) | packed<<2 | 0x1)
}
// rvcCB encodes a CB-type compressed branch: the 8-bit displacement in the
// order [8|4:3|7:6|2:1|5], funct3 6 (C.BEQZ) or 7 (C.BNEZ), op 01.
func rvcCB(funct3, rs1 uint32, off int32) uint16 {
packed := encodeRVCPattern(uint32(off), []int{8, 4, 3, 7, 6, 2, 1, 5})
return uint16((funct3 << 13) | ((packed>>5)&0x7)<<10 | rs1<<7 | (packed&0x1F)<<2 | 0x1)
}
// encodeRISCVVector encodes the RVV slice GOROOT's kernels use. Registers
// are accepted in either spelling: the vector V registers and the integer
// registers share their 5-bit numbers, and the superset keeps hand-written