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:
1 parent
bffe408afa
commit
c3540f0549
2 files changed
+75
-3
No files matched your search
+26
-3
@@ -1042,11 +1042,15 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
|||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case "WORD":
|
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 {
|
if len(ops) != 1 {
|
||||||
return nil, fmt.Errorf("WORD expects 1 operand, got %d", len(ops))
|
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 {
|
if w < 0 || w > 0xFFFFFFFF {
|
||||||
return nil, fmt.Errorf("WORD: immediate %d does not fit a 32-bit word", w)
|
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.
|
// BYTE $b lays down one raw byte per operand.
|
||||||
var out []byte
|
var out []byte
|
||||||
for _, op := range ops {
|
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 {
|
if b < 0 || b > 0xFF {
|
||||||
return nil, fmt.Errorf("BYTE: immediate %d does not fit a byte", b)
|
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
|
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
|
// riscvImm32FromOperand reads an immediate for the MOV/I-type paths as a
|
||||||
// signed 32-bit value. The toolchain materialises wider constants through
|
// signed 32-bit value. The toolchain materialises wider constants through
|
||||||
// its SLLI expansion, which this assembler does not implement, so values
|
// its SLLI expansion, which this assembler does not implement, so values
|
||||||
|
|||||||
@@ -1340,3 +1340,52 @@ TEXT ·v(SB), NOSPLIT, $0
|
|||||||
0xEA056207, // vlsseg8e32.v v4, (x10), x0
|
0xEA056207, // vlsseg8e32.v v4, (x10), x0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRISCV_rawDataRange pins the WORD and BYTE immediate ranges at the
|
||||||
|
// toolchain's own boundaries: WORD takes [0, 0xffffffff] and BYTE [0, 0xff],
|
||||||
|
// and a value the source spells wider must reach the check whole. The read
|
||||||
|
// once truncated through int32, which rejected WORD $0xffffffff as -1 while
|
||||||
|
// accepting WORD $0x100000000 as 0.
|
||||||
|
func TestRISCV_rawDataRange(t *testing.T) {
|
||||||
|
asmOne := func(t *testing.T, stmt string) ([]byte, error) {
|
||||||
|
t.Helper()
|
||||||
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·w(SB), NOSPLIT, $0\n\t"+stmt+"\n\tRET\n")
|
||||||
|
code, _, _, _, _, _, err := assembleRISCV(fn)
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
t.Run("word bounds", func(t *testing.T) {
|
||||||
|
code, err := asmOne(t, "WORD $0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("WORD $0: %v", err)
|
||||||
|
}
|
||||||
|
if string(code[:4]) != "\x00\x00\x00\x00" {
|
||||||
|
t.Errorf("WORD $0 = % x", code[:4])
|
||||||
|
}
|
||||||
|
code, err = asmOne(t, "WORD $0xffffffff")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("WORD $0xffffffff must assemble, as go tool asm accepts it: %v", err)
|
||||||
|
}
|
||||||
|
if string(code[:4]) != "\xff\xff\xff\xff" {
|
||||||
|
t.Errorf("WORD $0xffffffff = % x", code[:4])
|
||||||
|
}
|
||||||
|
for _, w := range []string{"$-1", "$0x100000000", "$-4294967296"} {
|
||||||
|
if _, err := asmOne(t, "WORD "+w); err == nil {
|
||||||
|
t.Errorf("WORD %s must be rejected, as go tool asm rejects it", w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("byte bounds", func(t *testing.T) {
|
||||||
|
code, err := asmOne(t, "BYTE $255")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BYTE $255: %v", err)
|
||||||
|
}
|
||||||
|
if code[0] != 0xff {
|
||||||
|
t.Errorf("BYTE $255 = % x", code[:1])
|
||||||
|
}
|
||||||
|
for _, b := range []string{"$256", "$0x100000001", "$-1"} {
|
||||||
|
if _, err := asmOne(t, "BYTE "+b); err == nil {
|
||||||
|
t.Errorf("BYTE %s must be rejected: out of the byte range", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user