diff --git a/asm/riscv_assemble.go b/asm/riscv_assemble.go index 60c11ec..f509030 100644 --- a/asm/riscv_assemble.go +++ b/asm/riscv_assemble.go @@ -1459,23 +1459,37 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv word = riscvRType(enc, rd, rd, rs2) // I-type shift (SLLI, SRLI, SRAI): INSTR $shamt, rs1, rd; the two-operand - // form INSTR $shamt, rd uses rd as the source. + // form INSTR $shamt, rd uses rd as the source. The shift amount is + // bounded at the instruction width, as the toolchain validates it: 0-63 + // for the doubleword forms, 0-31 for the word forms. case len(ops) == 3 && isShiftImmInstr(mnem): - shamt := int(immFromOperand(ops[0])) + shamt, ok := riscvRawImm(ops[0]) + if !ok { + return nil, fmt.Errorf("%s expects an immediate shift amount", mnem) + } + if hi := riscvShiftMax(mnem); shamt < 0 || shamt > hi { + return nil, fmt.Errorf("%s: immediate %d out of range 0 to %d", mnem, shamt, hi) + } rs1 := regFromOperand(ops[1]) rd := regFromOperand(ops[2]) if rd < 0 || rs1 < 0 { return nil, fmt.Errorf("invalid register in %s", mnem) } - word = riscvRType(enc, rd, rs1, shamt) + word = riscvRType(enc, rd, rs1, int(shamt)) case len(ops) == 2 && isShiftImmInstr(mnem): - shamt := int(immFromOperand(ops[0])) + shamt, ok := riscvRawImm(ops[0]) + if !ok { + return nil, fmt.Errorf("%s expects an immediate shift amount", mnem) + } + if hi := riscvShiftMax(mnem); shamt < 0 || shamt > hi { + return nil, fmt.Errorf("%s: immediate %d out of range 0 to %d", mnem, shamt, hi) + } rd := regFromOperand(ops[1]) if rd < 0 { return nil, fmt.Errorf("invalid register in %s", mnem) } - word = riscvRType(enc, rd, rd, shamt) + word = riscvRType(enc, rd, rd, int(shamt)) // AMO atomics: Plan 9 order is INSTR src, (addr), dst. case len(ops) == 3 && isAMOInstr(mnem): @@ -3150,6 +3164,17 @@ func isShiftImmInstr(m string) bool { return false } +// 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. +func riscvShiftMax(m string) int64 { + switch m { + case "SLLIW", "SRLIW", "SRAIW": + return 31 + } + return 63 +} + func isITypeInstr(m string) bool { switch m { case "ADDI", "ADDIW", "SLTI", "SLTIU", "XORI", "ORI", "ANDI", "JALR": diff --git a/asm/riscv_encode_test.go b/asm/riscv_encode_test.go index 02eb2ca..71b1bcd 100644 --- a/asm/riscv_encode_test.go +++ b/asm/riscv_encode_test.go @@ -1389,3 +1389,32 @@ func TestRISCV_rawDataRange(t *testing.T) { } }) } + +// TestRISCV_shiftImmediateRange pins the shift immediate at the toolchain's +// validation boundary: 0-63 on the doubleword forms, 0-31 on the word forms, +// values beyond rejected on sight rather than masked into the field. +func TestRISCV_shiftImmediateRange(t *testing.T) { + asmOne := func(t *testing.T, stmt string) error { + t.Helper() + fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·s(SB), NOSPLIT, $0\n\t"+stmt+"\n\tRET\n") + _, _, _, _, _, _, err := assembleRISCV(fn) + return err + } + for _, s := range []string{ + "SLLI $63, X5, X6", "SLLI $0, X5, X6", "SRLI $63, X5", "SRAI $1, X5, X6", + "SLLIW $31, X5, X6", "SRLIW $31, X5", "SRAIW $1, X5, X6", + } { + if err := asmOne(t, s); err != nil { + t.Errorf("%s must assemble: %v", s, err) + } + } + for _, s := range []string{ + "SLLI $64, X5, X6", "SLLI $-1, X5", "SLLI $0x100000000, X5, X6", + "SRLI $64, X5", "SRAI $-1, X5, X6", + "SLLIW $32, X5, X6", "SRLIW $-1, X5", "SRAIW $32, X5, X6", + } { + if err := asmOne(t, s); err == nil { + t.Errorf("%s must be rejected, as go tool asm rejects it", s) + } + } +}