feat(asm): encode the riscv64 bit-manipulation families
The Zba address generation, Zbb unary bit operations, Zbc carry-less multiplication and Zbs single-bit families were names the table carried and the encoder refused: thirty spellings plus RORI and XNOR fell over. The register and immediate forms now encode as the toolchain does, the unary operations carry their fixed rs2 constant, RORI lowers to ROR's expansion (its reverse shift compressing like ROR's), XNOR XORs and inverts in place, and ROL/ROLW rotate left through the same temporary the toolchain uses, taking a register amount only as its own expansion requires. The toolchain's whole testdata block for these families is now a differential test: every word must agree byte for byte. Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
d82ef33fa9
commit
ddfa33ccc1
3 files changed
+266
-19
No files matched your search
+123
-19
@@ -437,7 +437,7 @@ func riscvExtendedSize(mnem string, ops []*ast.Operand) int {
|
||||
case "NOP":
|
||||
// The toolchain drops a bare NOP entirely.
|
||||
return 0
|
||||
case "ANDN", "ORN":
|
||||
case "ANDN", "ORN", "XNOR":
|
||||
return 8
|
||||
case "MAX", "MAXU", "MIN", "MINU":
|
||||
if riscvIdenticalMinMax(mnem, ops) {
|
||||
@@ -451,7 +451,7 @@ func riscvExtendedSize(mnem string, ops []*ast.Operand) int {
|
||||
return 4
|
||||
}
|
||||
return 20
|
||||
case "ROR", "RORW":
|
||||
case "ROL", "ROLW", "ROR", "RORI", "RORW":
|
||||
if len(ops) >= 1 && isImmOperand(ops[0]) {
|
||||
// SRL + [compressed] SLL of the reverse shift + OR.
|
||||
return 4 + riscvRevShiftSize(mnem, ops) + 4
|
||||
@@ -487,11 +487,11 @@ func riscvIdenticalMinMax(mnem string, ops []*ast.Operand) bool {
|
||||
}
|
||||
|
||||
// riscvRevShiftSize returns the size of the reverse-shift instruction inside
|
||||
// a ROR/RORW immediate expansion: the SLLI of the complementary amount, which
|
||||
// a ROR/RORI immediate expansion: the SLLI of the complementary amount, which
|
||||
// compresses to C.SLLI only in the 64-bit form when rd == rs1, both non-zero,
|
||||
// and the amount lands in 1-63. The W forms have no compressed shift.
|
||||
func riscvRevShiftSize(mnem string, ops []*ast.Operand) int {
|
||||
if mnem != "ROR" {
|
||||
if mnem != "ROR" && mnem != "RORI" {
|
||||
return 4 // SLLIW has no compressed form
|
||||
}
|
||||
imm := int(immFromOperand(ops[0]))
|
||||
@@ -1501,6 +1501,18 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
}
|
||||
word = riscvAMOType(enc, rd, rs1, rs2)
|
||||
|
||||
// Zbb unary bit operations: INSTR rs, rd, exactly two operands as the
|
||||
// toolchain spells them. The rs2 field is fixed, not zero: the table
|
||||
// below carries the constant each operation reads (CLZ counts leading
|
||||
// zeros with an empty field, REV8 works on bytes at position 24).
|
||||
case len(ops) == 2 && isZbUnaryInstr(mnem):
|
||||
rs1 := regFromOperand(ops[0])
|
||||
rd := regFromOperand(ops[1])
|
||||
if rd < 0 || rs1 < 0 {
|
||||
return nil, fmt.Errorf("invalid register in %s", mnem)
|
||||
}
|
||||
word = riscvRType(enc, rd, rs1, riscvZbUnaryRS2[mnem])
|
||||
|
||||
// FP arithmetic: Go reverses the ISA order, writing rs2, rs1, rd.
|
||||
case len(ops) == 3 && isFPArithInstr(mnem):
|
||||
rs2 := regFromOperand(ops[0])
|
||||
@@ -2749,6 +2761,24 @@ func encodeRISCVExtended(mnem string, instr *ast.Instr, pc int, offsets map[stri
|
||||
}
|
||||
return append(out, wordLE(riscvRType(op, rd, rs1, notReg))...), true, nil
|
||||
|
||||
case "XNOR":
|
||||
// ~(rs1 ^ rs2): the toolchain XORs into the destination and inverts
|
||||
// it in place, no temporary.
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rs2 := regFromOperand(ops[0])
|
||||
rs1 := regFromOperand(ops[1])
|
||||
rd := rs1
|
||||
if len(ops) == 3 {
|
||||
rd = regFromOperand(ops[2])
|
||||
}
|
||||
if rs1 < 0 || rs2 < 0 || rd < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid register", mnem)
|
||||
}
|
||||
out := wordLE(riscvRType(riscvInstrTable["XOR"], rd, rs1, rs2))
|
||||
return append(out, wordLE(riscvIType(riscvInstrTable["XORI"], rd, rd, -1))...), true, nil
|
||||
|
||||
case "MAX", "MAXU", "MIN", "MINU":
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
@@ -2788,17 +2818,47 @@ func encodeRISCVExtended(mnem string, instr *ast.Instr, pc int, offsets map[stri
|
||||
out = append(out, wordLE(riscvRType(riscvInstrTable["XOR"], rd, rs1, rd))...)
|
||||
return out, true, nil
|
||||
|
||||
case "ROR", "RORW", "RORIW":
|
||||
case "BCLR", "BEXT", "BINV", "BSET":
|
||||
// The immediate spelling lowers to the shift-immediate entry, as the
|
||||
// toolchain does: BCLR $63, X24 is BCLRI $63, X24, X24. The register
|
||||
// spelling falls through to the main table's R-type path.
|
||||
if len(ops) == 0 || !isImmOperand(ops[0]) {
|
||||
return nil, false, nil
|
||||
}
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
shamt, ok := riscvRawImm(ops[0])
|
||||
if !ok || shamt < 0 || shamt > 63 {
|
||||
return nil, true, fmt.Errorf("%s: immediate out of range 0 to 63", mnem)
|
||||
}
|
||||
rs1 := regFromOperand(ops[1])
|
||||
rd := rs1
|
||||
if len(ops) == 3 {
|
||||
rd = regFromOperand(ops[2])
|
||||
}
|
||||
if rs1 < 0 || rd < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid register", mnem)
|
||||
}
|
||||
immForm := map[string]string{"BCLR": "BCLRI", "BEXT": "BEXTI", "BINV": "BINVI", "BSET": "BSETI"}[mnem]
|
||||
return wordLE(riscvRType(riscvInstrTable[immForm], rd, rs1, int(shamt))), true, nil
|
||||
|
||||
case "ROL", "ROLW", "ROR", "RORI", "RORW", "RORIW":
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
if isImmOperand(ops[0]) {
|
||||
// Immediate rotate: SRLI the amount, SLLI the complement, OR.
|
||||
// The immediate spellings are ROR's: ROL takes a register amount
|
||||
// only, as the toolchain's own expansion requires.
|
||||
if mnem == "ROL" || mnem == "ROLW" {
|
||||
return nil, true, fmt.Errorf("%s takes a register shift amount", mnem)
|
||||
}
|
||||
imm := int(immFromOperand(ops[0]))
|
||||
shiftW := 63
|
||||
srlEnc := riscvInstrTable["SRLI"]
|
||||
sllEnc := riscvInstrTable["SLLI"]
|
||||
if mnem != "ROR" {
|
||||
if mnem == "RORW" || mnem == "RORIW" {
|
||||
shiftW = 31
|
||||
srlEnc = riscvInstrTable["SRLIW"]
|
||||
sllEnc = riscvInstrTable["SLLIW"]
|
||||
@@ -2817,16 +2877,17 @@ func encodeRISCVExtended(mnem string, instr *ast.Instr, pc int, offsets map[stri
|
||||
var out []byte
|
||||
out = append(out, wordLE(riscvRType(srlEnc, 31, rs1, imm))...)
|
||||
sll := (-imm) & shiftW
|
||||
if mnem == "ROR" && rd == rs1 && rd != 0 && sll >= 1 && sll <= 63 {
|
||||
if mnem != "RORW" && mnem != "RORIW" && rd == rs1 && rd != 0 && sll >= 1 && sll <= 63 {
|
||||
out = append(out, word16(rvcSLLI(uint32(rd), uint32(sll)))...) // C.SLLI
|
||||
} else {
|
||||
out = append(out, wordLE(riscvRType(sllEnc, rd, rs1, sll))...)
|
||||
}
|
||||
return append(out, wordLE(riscvRType(riscvInstrTable["OR"], rd, 31, rd))...), true, nil
|
||||
}
|
||||
// Register rotate: OR of the two opposite shifts through TMP.
|
||||
if mnem == "RORIW" {
|
||||
return nil, true, fmt.Errorf("RORIW takes an immediate shift amount")
|
||||
// Register rotate: OR of the two opposite shifts through TMP. RORI
|
||||
// and RORIW are the immediate spellings and take no register amount.
|
||||
if mnem == "RORIW" || mnem == "RORI" {
|
||||
return nil, true, fmt.Errorf("%s takes an immediate shift amount", mnem)
|
||||
}
|
||||
rs2 := regFromOperand(ops[0])
|
||||
rs1 := regFromOperand(ops[1])
|
||||
@@ -2837,16 +2898,27 @@ func encodeRISCVExtended(mnem string, instr *ast.Instr, pc int, offsets map[stri
|
||||
if rs1 < 0 || rs2 < 0 || rd < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid register", mnem)
|
||||
}
|
||||
sllEnc := riscvInstrTable["SLL"]
|
||||
srlEnc := riscvInstrTable["SRL"]
|
||||
if mnem == "RORW" {
|
||||
sllEnc = riscvInstrTable["SLLW"]
|
||||
srlEnc = riscvInstrTable["SRLW"]
|
||||
// ROR shifts right by the amount and left by its complement; ROL
|
||||
// swaps the two.
|
||||
wide := mnem == "ROL" || mnem == "ROR"
|
||||
shiftLeft := riscvInstrTable["SLL"]
|
||||
shiftRight := riscvInstrTable["SRL"]
|
||||
shiftLeftW := riscvInstrTable["SLLW"]
|
||||
shiftRightW := riscvInstrTable["SRLW"]
|
||||
tmpShift, rdShift := shiftLeft, shiftRight
|
||||
if mnem == "ROL" || mnem == "ROLW" {
|
||||
tmpShift, rdShift = shiftRight, shiftLeft
|
||||
}
|
||||
if !wide {
|
||||
tmpShift, rdShift = shiftLeftW, shiftRightW
|
||||
if mnem == "ROLW" {
|
||||
tmpShift, rdShift = shiftRightW, shiftLeftW
|
||||
}
|
||||
}
|
||||
var out []byte
|
||||
out = append(out, wordLE(riscvRType(riscvInstrTable["SUB"], 31, 0, rs2))...) // NEG
|
||||
out = append(out, wordLE(riscvRType(sllEnc, 31, rs1, 31))...)
|
||||
out = append(out, wordLE(riscvRType(srlEnc, rd, rs1, rs2))...)
|
||||
out = append(out, wordLE(riscvRType(tmpShift, 31, rs1, 31))...)
|
||||
out = append(out, wordLE(riscvRType(rdShift, rd, rs1, rs2))...)
|
||||
out = append(out, wordLE(riscvRType(riscvInstrTable["OR"], rd, 31, rd))...)
|
||||
return out, true, nil
|
||||
|
||||
@@ -3150,7 +3222,12 @@ func isRTypeInstr(m string) bool {
|
||||
"ADDW", "SUBW", "SLLW", "SRLW", "SRAW",
|
||||
"MUL", "MULH", "MULHSU", "MULHU", "DIV", "DIVU", "REM", "REMU",
|
||||
"MULW", "DIVW", "DIVUW", "REMW", "REMUW",
|
||||
"CZEROEQZ", "CZERONEZ":
|
||||
"CZEROEQZ", "CZERONEZ",
|
||||
// Zba address generation, Zbc carry-less multiplication and the
|
||||
// Zbs single-bit register forms.
|
||||
"ADDUW", "SH1ADD", "SH1ADDUW", "SH2ADD", "SH2ADDUW", "SH3ADD", "SH3ADDUW",
|
||||
"CLMUL", "CLMULH", "CLMULR",
|
||||
"BCLR", "BEXT", "BINV", "BSET":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -3158,12 +3235,39 @@ func isRTypeInstr(m string) bool {
|
||||
|
||||
func isShiftImmInstr(m string) bool {
|
||||
switch m {
|
||||
case "SLLI", "SRLI", "SRAI", "SLLIW", "SRLIW", "SRAIW":
|
||||
case "SLLI", "SRLI", "SRAI", "SLLIW", "SRLIW", "SRAIW",
|
||||
"BCLRI", "BEXTI", "BINVI", "BSETI", "SLLIUW":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isZbUnaryInstr reports whether m is a Zbb one-source bit operation: a
|
||||
// single source register with the rs2 field fixed, spelled INSTR rs, rd.
|
||||
func isZbUnaryInstr(m string) bool {
|
||||
switch m {
|
||||
case "CLZ", "CLZW", "CPOP", "CPOPW", "CTZ", "CTZW",
|
||||
"SEXTB", "SEXTH", "ORCB", "REV8", "ZEXTH":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// riscvZbUnaryRS2 carries the constant each Zbb unary operation fixes in the
|
||||
// rs2 field: the population counts, sign extensions and byte operations
|
||||
// address a width or a position, not a second register. CLZ, CLZW and ZEXTH
|
||||
// leave the field empty and are absent from the map.
|
||||
var riscvZbUnaryRS2 = map[string]int{
|
||||
"CPOP": 2,
|
||||
"CPOPW": 2,
|
||||
"CTZ": 1,
|
||||
"CTZW": 1,
|
||||
"SEXTB": 4,
|
||||
"SEXTH": 5,
|
||||
"ORCB": 7,
|
||||
"REV8": 24,
|
||||
}
|
||||
|
||||
// riscvShiftMax bounds a shift immediate at the instruction's width: the
|
||||
// doubleword forms shift 0-63, the word forms 0-31, the toolchain's own
|
||||
// validation boundary.
|
||||
|
||||
Reference in new issue
Block a user