fix(riscv64): compressed store offsets, FENCE and branch range checks
Assisted-by: GLM 5.3
This commit is contained in:
@@ -5,6 +5,7 @@ package asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
@@ -691,6 +692,81 @@ DATA answer<>+0(SB)/8, $42
|
||||
}
|
||||
}
|
||||
|
||||
// TestRISCV_RVC_StorePatterns pins the register-relative compressed store
|
||||
// encodings for offsets with immediate bits 4 and 5 set, byte-identical to
|
||||
// the toolchain's encodeCS (patterns {5,4,3,7,6} and {5,4,3,2,6}).
|
||||
// Regression: the store-side patterns dropped imm[4], so every such store
|
||||
// silently encoded the wrong address while the loads stayed correct.
|
||||
func TestRISCV_RVC_StorePatterns(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·csstores(SB), NOSPLIT, $0
|
||||
SD X9, 24(X8)
|
||||
SW X10, 16(X11)
|
||||
FSD F8, 40(X12)
|
||||
LD 24(X8), X9
|
||||
LW 16(X11), X10
|
||||
FLD 40(X12), F8
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
want := []byte{
|
||||
0x04, 0xec, // c.sd x9, 24(x8)
|
||||
0x88, 0xc9, // c.sw x10, 16(x11)
|
||||
0x00, 0xb6, // c.fsd f8, 40(x12)
|
||||
0x04, 0x6c, // c.ld x9, 24(x8)
|
||||
0x88, 0x49, // c.lw x10, 16(x11)
|
||||
0x00, 0x36, // c.fld f8, 40(x12)
|
||||
0x67, 0x80, 0x00, 0x00, // jalr x0, 0(x1)
|
||||
}
|
||||
if !bytes.Equal(code, want) {
|
||||
t.Errorf("code = % x\nwant % x", code, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRISCV_FENCE pins the FENCE encoding: the toolchain expands the bare
|
||||
// mnemonic to fence iorw, iorw (0x0FF0000F), not fence 0,0.
|
||||
func TestRISCV_FENCE(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·fence(SB), NOSPLIT, $0
|
||||
FENCE
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
want := []byte{
|
||||
0x0f, 0x00, 0xf0, 0x0f, // fence iorw, iorw
|
||||
0x67, 0x80, 0x00, 0x00, // jalr x0, 0(x1)
|
||||
}
|
||||
if !bytes.Equal(code, want) {
|
||||
t.Errorf("code = % x\nwant % x", code, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRISCV_RVC_WidthSpellings pins the compression of the GOROOT width
|
||||
// spellings: MOVW and MOVD lower to their base load/store and compress
|
||||
// exactly like LW/SW/FLD/FSD would (the toolchain compresses these shapes;
|
||||
// before the normalisation they stayed 4 bytes).
|
||||
func TestRISCV_RVC_WidthSpellings(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·widths(SB), NOSPLIT, $0-16
|
||||
MOVW w+0(FP), X9
|
||||
MOVW X9, v+4(FP)
|
||||
MOVD d+0(FP), F8
|
||||
MOVD F8, r+8(FP)
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
want := []byte{
|
||||
0xa2, 0x44, // c.lwsp x9, 8
|
||||
0x26, 0xc6, // c.swsp x9, 12
|
||||
0x22, 0x24, // c.fldsp f8, 8
|
||||
0x22, 0xa8, // c.fsdsp f8, 16
|
||||
0x67, 0x80, 0x00, 0x00, // jalr x0, 0(x1)
|
||||
}
|
||||
if !bytes.Equal(code, want) {
|
||||
t.Errorf("code = % x\nwant % x", code, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_system_instrs(t *testing.T) {
|
||||
// Test FENCE, ECALL, EBREAK encoding.
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
@@ -782,3 +858,116 @@ TEXT ·f(SB), NOSPLIT, $0-0
|
||||
0x00008067, // jalr x0, 1(x0), 0 (RET)
|
||||
)
|
||||
}
|
||||
|
||||
// encodeOneInstrRISCV encodes a single parsed instruction against a synthetic
|
||||
// offsets map, the smallest honest harness for the branch-range diagnostics:
|
||||
// the spans are far larger than any source a test would want to spell out.
|
||||
func encodeOneInstrRISCV(t *testing.T, src string, pc int, offsets map[string]int) ([]byte, error) {
|
||||
t.Helper()
|
||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\n"+src)
|
||||
instr := fn.Body[0].(*ast.Instr)
|
||||
return encodeRISCVInstr(instr, pc, offsets, riscvFrameInfo{}, nil)
|
||||
}
|
||||
|
||||
// TestRISCVBranchJumpRange checks that displacements beyond the B-type span
|
||||
// [-4096, 4094] and the J-type span [-1048576, 1048574] are diagnosed instead
|
||||
// of wrapping silently to a wrong target.
|
||||
func TestRISCVBranchJumpRange(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
src string
|
||||
off int // the target's function-relative offset (pc 0)
|
||||
ok bool
|
||||
}{
|
||||
{"branch max", "BEQ X10, X11, tgt\nRET\n", 4094, true},
|
||||
{"branch past max", "BEQ X10, X11, tgt\nRET\n", 4096, false},
|
||||
{"branch back max", "BEQ X10, X11, tgt\nRET\n", -4096, true},
|
||||
{"branch back past max", "BEQ X10, X11, tgt\nRET\n", -4098, false},
|
||||
{"branchz past max", "BEQZ X10, tgt\nRET\n", 4096, false},
|
||||
{"jump max", "JMP tgt\nRET\n", 1048574, true},
|
||||
{"jump past max", "JMP tgt\nRET\n", 1048576, false},
|
||||
{"jump back max", "JMP tgt\nRET\n", -1048576, true},
|
||||
{"jump back past max", "JMP tgt\nRET\n", -1048578, false},
|
||||
{"jal past max", "JAL tgt\nRET\n", 1048576, false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
_, err := encodeOneInstrRISCV(t, "TEXT ·f(SB), NOSPLIT, $0\n\t"+c.src, 0, map[string]int{"tgt": c.off})
|
||||
if c.ok && err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !c.ok && err == nil {
|
||||
t.Fatal("expected an out-of-range diagnostic, got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRISCVBranchFarBody drives the range check through the full two-pass
|
||||
// assembler: a forward branch over a body larger than the B-type span must
|
||||
// error rather than wrap.
|
||||
func TestRISCVBranchFarBody(t *testing.T) {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("#include \"textflag.h\"\nTEXT ·far(SB), NOSPLIT, $0\n\tBEQ X10, X11, done\n")
|
||||
for range 1100 {
|
||||
sb.WriteString("\tADD X10, X11, X12\n")
|
||||
}
|
||||
sb.WriteString("done:\n\tRET\n")
|
||||
fn := firstTextRISCV(t, sb.String())
|
||||
if _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
||||
t.Error("expected a branch-out-of-range error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRISCV_CSRRange checks the CSR address range: the 12-bit field is
|
||||
// diagnosed rather than masked, so CSRRW $4096 does not silently address
|
||||
// CSR 0.
|
||||
func TestRISCV_CSRRange(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·csrhi(SB), NOSPLIT, $0
|
||||
CSRRW $4096, X10, X11
|
||||
RET
|
||||
`)
|
||||
if _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
||||
t.Error("expected an out-of-range error for CSR $4096, got none")
|
||||
}
|
||||
fn = firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·csrmax(SB), NOSPLIT, $0
|
||||
CSRRW $4095, X10, X11
|
||||
RET
|
||||
`)
|
||||
if _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
||||
t.Errorf("CSR $4095 must assemble: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRISCV_Imm64Rejected checks that immediates outside the signed 32-bit
|
||||
// span are diagnosed instead of silently truncated to their low 32 bits (the
|
||||
// toolchain materialises such constants via SLLI expansion, which this
|
||||
// assembler does not implement).
|
||||
func TestRISCV_Imm64Rejected(t *testing.T) {
|
||||
cases := []string{
|
||||
"MOV $0x123456789, X10",
|
||||
"ADDI $0x100000000, X10, X11",
|
||||
"ANDI $-0x800000001, X10, X11",
|
||||
"SUB $0x100000000, X10, X11",
|
||||
}
|
||||
for _, src := range cases {
|
||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·wide(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
||||
if _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
||||
t.Errorf("%s: expected an out-of-range error, got none", src)
|
||||
}
|
||||
}
|
||||
// The full signed 32-bit span still assembles, including the SUB form
|
||||
// whose negated immediate only just fits.
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·edge(SB), NOSPLIT, $0
|
||||
MOV $2147483647, X10
|
||||
MOV $-2147483648, X11
|
||||
SUB $0x80000000, X12, X13
|
||||
RET
|
||||
`)
|
||||
if _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
||||
t.Errorf("int32-span immediates must assemble: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user