fix(asm): read the riscv64 raw-data immediates at full width

WORD and BYTE read their immediate through the truncating helper, so the
int32 wrap turned WORD $0xffffffff into -1 and rejected it, while WORD
$0x100000000 and BYTE $0x100000001 arrived pre-truncated and slipped
past the range check as small values.  Both statements now read the
immediate the source wrote and bound it at the toolchain's own limits:
[0, 0xffffffff] for WORD, [0, 0xff] for BYTE, with the bounds pinned in
a test.

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 00:47:27 +02:00
1 parent bffe408afa
commit c3540f0549
2 files changed
+75 -3

No files matched your search

+26 -3
View File
@@ -1042,11 +1042,15 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
}
return nil, nil
case "WORD":
// WORD $w lays down a raw 32-bit little-endian word.
// WORD $w lays down a raw 32-bit little-endian word, in the range
// [0, 0xffffffff] exactly as the toolchain's validation bounds it.
if len(ops) != 1 {
return nil, fmt.Errorf("WORD expects 1 operand, got %d", len(ops))
}
w := int64(immFromOperand(ops[0]))
w, ok := riscvRawImm(ops[0])
if !ok {
return nil, fmt.Errorf("WORD expects an immediate")
}
if w < 0 || w > 0xFFFFFFFF {
return nil, fmt.Errorf("WORD: immediate %d does not fit a 32-bit word", w)
}
@@ -1055,7 +1059,10 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
// BYTE $b lays down one raw byte per operand.
var out []byte
for _, op := range ops {
b := int64(immFromOperand(op))
b, ok := riscvRawImm(op)
if !ok {
return nil, fmt.Errorf("BYTE expects immediates")
}
if b < 0 || b > 0xFF {
return nil, fmt.Errorf("BYTE: immediate %d does not fit a byte", b)
}
@@ -3249,6 +3256,22 @@ func immFromOperand(op *ast.Operand) int32 {
return 0
}
// riscvRawImm reads an immediate at its full written width: the raw-data
// statements (WORD, BYTE) validate against their own ranges, so a value the
// source spelled wider than int32 must reach the check whole, never truncated
// through an int32 read (WORD $0xffffffff is in range, WORD $0x100000000 is
// not, and neither may arrive disguised as the other).
func riscvRawImm(op *ast.Operand) (int64, bool) {
if !op.Imm.HasVal {
return 0, false
}
v := op.Imm.Val
if op.Imm.Neg {
v = -v
}
return v, true
}
// riscvImm32FromOperand reads an immediate for the MOV/I-type paths as a
// signed 32-bit value. The toolchain materialises wider constants through
// its SLLI expansion, which this assembler does not implement, so values