From da35883633f4c0775218f98614192353e4655c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Wed, 7 Oct 2026 13:15:24 +0200 Subject: [PATCH] fix(asm): compress the riscv64 two-operand arithmetic and immediate tail Assisted-by: GLM 5.3 --- asm/riscv_assemble.go | 86 ++++++++++++++++++++++++++++++++----------- asm/riscv_frame.go | 2 +- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/asm/riscv_assemble.go b/asm/riscv_assemble.go index dafd86c..f8cf3cf 100644 --- a/asm/riscv_assemble.go +++ b/asm/riscv_assemble.go @@ -400,7 +400,7 @@ func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int { if isRVCIntReg(rd) && off > 0 && off < 1024 && off%4 == 0 { return 2 // C.ADDI4SPN } - return riscvItypeImmediateSize("ADDI", off) + return riscvItypeImmediateSize("ADDI", rd, 2, off) } // Frame-relative loads and stores: a frame offset beyond the signed // 12-bit range materialises the address in X31 first. @@ -417,7 +417,16 @@ func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int { if immNeg { imm = -imm } - return riscvItypeImmediateSize(mnem, imm) + rd, rs1 := -1, -1 + switch len(ops) { + case 3: + rs1 = regFromOperand(ops[1]) + rd = regFromOperand(ops[2]) + case 2: + rd = regFromOperand(ops[1]) + rs1 = rd + } + return riscvItypeImmediateSize(mnem, rd, rs1, imm) } // BYTE lays down one raw byte per operand. if mnem == "BYTE" { @@ -2310,13 +2319,23 @@ func encodeRISCVItypeImmediate(mnem string, enc riscvEnc, rd, rs1 int, imm int32 if !ok { return nil, fmt.Errorf("%s: unsupported operation %q", mnem, opMn) } + // The toolchain's compress pass runs over the expansion's instructions, + // and the final ADD takes the C.ADD form whenever rd == rs1 (the two- + // operand ADDI spelling); the other ops carry TMP (X31) as rs2, which + // only C.ADD's full-width rs2 field can hold. + if opMn == "ADD" && rd == rs1 && rd != 0 { + c := rvcCR(0x9, uint32(rd), uint32(tmp)) + out = append(out, byte(c), byte(c>>8)) + return out, nil + } out = append(out, wordLE(riscvRType(opEnc, rd, rs1, tmp))...) return out, nil } // riscvItypeImmediateSize returns the encoded byte length of an I-type -// immediate instruction, accounting for the large-immediate expansion. -func riscvItypeImmediateSize(mnem string, imm int32) int { +// immediate instruction, accounting for the large-immediate expansion and +// the C.ADD the compress pass gives the expansion's final ADD when rd == rs1. +func riscvItypeImmediateSize(mnem string, rd, rs1 int, imm int32) int { if imm >= -2048 && imm <= 2047 { return 4 } @@ -2329,7 +2348,12 @@ func riscvItypeImmediateSize(mnem string, imm int32) int { return 8 } low, high := splitRISCV32Imm(imm) - size := 4 // the R-type op (TMP is X31, never compressed) + // The R-type op: TMP is X31, whose full-width rs2 only C.ADD can hold, + // and only when rd == rs1 (the two-operand ADDI spelling). + size := 4 + if mnem == "ADDI" && rd == rs1 && rd != 0 { + size = 2 + } if high != 0 && high >= -32 && high <= 31 { size += 2 // C.LUI } else { @@ -2448,13 +2472,17 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { if mnem == "MOV" && len(ops) == 2 && isImmOperand(ops[0]) { return 0, false } - // MOV reg, reg → C.MV (CR-type: funct4=0x8). + // MOV reg, reg → C.MV (CR-type: funct4=0x8); the X0 source is ADDI + // $0, X0, rd, which the toolchain's compress pass turns into C.LI $0. if mnem == "MOV" && len(ops) == 2 && !isMemOperand(ops[0]) && !isMemOperand(ops[1]) && !isImmOperand(ops[0]) { rs1 := regFromOperand(ops[0]) rd := regFromOperand(ops[1]) if rs1 != -1 && rd != -1 && rs1 != 0 && rd != 0 { return rvcCR(0x8, uint32(rd), uint32(rs1)), true } + if rs1 == 0 && rd > 0 { + return rvcCI(0x2, uint32(rd), 0), true + } } rd, rs1, imm := extractLDParams(instr, fi) if rs1 == 2 && rd != 0 && rd != -1 && imm >= 0 && imm < 512 && imm%8 == 0 { @@ -2553,6 +2581,14 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { case "ADD": // ADD rs2, rs1, rd → C.ADD (CR-type, funct4=0x9) when rd == rs1; ADD // is commutative, so if rd == rs2, swap. ADD rs2, X0, rd is C.MV. + // The two-operand form ADD rs2, rd reads rd as rs1. + if len(ops) == 2 { + rs2 := regFromOperand(ops[0]) + rd := regFromOperand(ops[1]) + if rd != -1 && rs2 != -1 && rd != 0 && rs2 != 0 { + return rvcCR(0x9, uint32(rd), uint32(rs2)), true + } + } if len(ops) == 3 { rs2 := regFromOperand(ops[0]) rs1 := regFromOperand(ops[1]) @@ -2573,18 +2609,15 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { case "SUB", "XOR", "OR", "AND": // C.SUB (0x23,0), C.XOR (0x23,1), C.OR (0x23,2), C.AND (0x23,3) - if len(ops) == 3 { - var funct2 uint32 - switch mnem { - case "SUB": - funct2 = 0x0 - case "XOR": - funct2 = 0x1 - case "OR": - funct2 = 0x2 - case "AND": - funct2 = 0x3 + funct2 := map[string]uint32{"SUB": 0x0, "XOR": 0x1, "OR": 0x2, "AND": 0x3}[mnem] + if len(ops) == 2 { + rs2 := regFromOperand(ops[0]) + rd := regFromOperand(ops[1]) + if rd != -1 && rs2 != -1 && isRVCIntReg(rd) && isRVCIntReg(rs2) && rs2 != 0 { + return rvcCA(0x23, funct2, rvcReg3(rd), rvcReg3(rs2)), true } + } + if len(ops) == 3 { rs2 := regFromOperand(ops[0]) rs1 := regFromOperand(ops[1]) rd := regFromOperand(ops[2]) @@ -2601,11 +2634,18 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { case "ADDW", "SUBW": // C.ADDW (0x27,1) / C.SUBW (0x27,0), CA-type, prime regs. - if len(ops) == 3 { - funct2 := uint32(0x0) - if mnem == "ADDW" { - funct2 = 0x1 + funct2 := uint32(0x0) + if mnem == "ADDW" { + funct2 = 0x1 + } + if len(ops) == 2 { + rs2 := regFromOperand(ops[0]) + rd := regFromOperand(ops[1]) + if rs2 != -1 && rd != -1 && isRVCIntReg(rd) && isRVCIntReg(rs2) { + return rvcCA(0x27, funct2, rvcReg3(rd), rvcReg3(rs2)), true } + } + if len(ops) == 3 { rs2 := regFromOperand(ops[0]) rs1 := regFromOperand(ops[1]) rd := regFromOperand(ops[2]) @@ -2655,6 +2695,10 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { case "ADDIW": rd, rs1, imm := extractITypeParams(instr) + if immNeg { + // SUBW $imm, rd arrives as ADDIW with the negated immediate. + imm = -imm + } if rd == rs1 && rd != 0 && imm >= -32 && imm <= 31 { return rvcCI(0x1, uint32(rd), uint32(imm)&0x3F), true } diff --git a/asm/riscv_frame.go b/asm/riscv_frame.go index 8769459..3f356f4 100644 --- a/asm/riscv_frame.go +++ b/asm/riscv_frame.go @@ -344,7 +344,7 @@ func riscvGuard(fi riscvFrameInfo) ([]byte, Reloc, error) { off := int32(fi.autosize - stackSmall) mov := encodeRISCVLoadImm(7, off) out = append(out, mov...) - addiLen := riscvItypeImmediateSize("ADDI", -off) + addiLen := riscvItypeImmediateSize("ADDI", 7, 2, -off) out = append(out, wordLE(riscvBType(riscvEnc{0x63, 0x06, 0x00}, 2, 7, int32(addiLen+8)))...) addi, err := encodeRISCVItypeImmediate("ADDI", riscvEnc{0x13, 0x0, 0x00}, 7, 2, -off) if err != nil {