Files
gasm-sdk/asm/riscv_memoffset_test.go
T

128 lines
4.2 KiB
Go
Raw Normal View History

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package asm
import (
"strings"
"testing"
)
// TestRISCVBigMemOffset_Differential proves the memory-offset expansion
// against the oracle: a load or store whose offset leaves the signed 12-bit
// span materialises the high part in the assembler's temporary register
// (C.LUI, or LUI beyond its six-bit immediate) followed by C.ADD of the base,
// and accesses through it, exactly as the toolchain's instructionsForLoad and
// instructionsForStore synthesise. The battery spans both sides of the
// 12-bit edge, both int32 boundaries, the compressed and uncompressed LUI
// halves, every width family and the MOV spellings, under a prologue too so
// the size accounting keeps the layout honest.
func TestRISCVBigMemOffset_Differential(t *testing.T) {
src := `#include "textflag.h"
TEXT ·bigmem(SB), NOSPLIT, $16
LD 2047(X6), X5 // single word: the edge that still fits
LD 2048(X6), X5 // hi 1, lo -2048
LD 4096(X6), X5 // hi 1, lo 0
SD X5, 8192(X6) // store side of the same split
FLD 4096(X6), F5 // FP widths share the expansion
FSD F5, 8192(X6)
MOVB 4097(X6), X8 // the MOV widths route through the same load
MOVHU 4098(X6), X9
MOV X7, 16384(X6) // and the same store
LD 1048576(X6), X10 // hi 256: LUI, not C.LUI
LD -4097(X6), X11 // hi -1, lo -1
SD X11, -8192(X6)
LD 2147483647(X6), X12 // int32 upper boundary
LD -2147483648(X6), X13 // int32 lower boundary
RET
`
path := writeRISCVSrc(t, "bigmem_riscv64.s", src)
assertRISCVDifferential(t, path, src, "bigmem")
}
// TestRISCVBigMemOffsetFrame_Differential proves the expansion under the
// stack-split guard: a function that opens a frame and calls out gets the
// guard, the prologue and the epilogue ahead of the body, so every body
// offset now depends on the expansion's size accounting having kept the
// layout in step with the emitted bytes.
func TestRISCVBigMemOffsetFrame_Differential(t *testing.T) {
src := `#include "textflag.h"
TEXT ·bigframe(SB), $16
CALL extcal(SB)
LD 4096(X6), X5
SD X5, 4096(X6)
FLD 8192(X6), F5
FSD F5, 8192(X6)
RET
`
path := writeRISCVSrc(t, "bigframe_riscv64.s", src)
assertRISCVDifferential(t, path, src, "bigframe")
}
// TestRISCVBigMemOffsetRejections pins the honest rejections the expansion
// cannot carry: a constant beyond the signed 32-bit span is the toolchain's
// "constant too large" (its Split32BitImmediate has no wider split), and a
// JALR displacement beyond the signed 12-bit span is its I-type range check,
// because the jump would land somewhere else entirely.
func TestRISCVBigMemOffsetRejections(t *testing.T) {
cases := []struct {
name string
src string
want string
}{
{
name: "store beyond int32",
src: "\tSD X5, 4294967295(X6)\n",
want: "SD: constant 4294967295 too large",
},
{
name: "load below int32",
src: "\tLD -2147483649(X6), X5\n",
want: "LD: constant -2147483649 too large",
},
{
name: "MOV load beyond int32",
src: "\tMOV 4294967296(X6), X5\n",
want: "MOV: constant 4294967296 too large",
},
{
name: "MOV store beyond int32",
src: "\tMOV X5, 4294967296(X6)\n",
want: "MOV: constant 4294967296 too large",
},
{
name: "FP store beyond int32",
src: "\tFSD F5, 4294967296(X6)\n",
want: "FSD: constant 4294967296 too large",
},
{
name: "FP load beyond int32",
src: "\tFLD 4294967296(X6), F5\n",
want: "FLD: constant 4294967296 too large",
},
{
name: "JALR displacement above 12 bits",
src: "\tJALR 4096(X7)\n",
want: "JALR: signed immediate 4096 must be in range [-2048, 2047] (12 bits)",
},
{
name: "JALR displacement below 12 bits",
src: "\tJALR X5, -2049(X7)\n",
want: "JALR: signed immediate -2049 must be in range [-2048, 2047] (12 bits)",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
fn := firstTextRISCV(t, "#include \"textflag.h\"\n\nTEXT ·r(SB), NOSPLIT, $0\n"+tc.src+"\tRET\n")
_, _, _, _, _, _, err := assembleRISCV(fn, nil)
if err == nil {
t.Fatalf("source assembled, want rejection %q", tc.want)
}
if !strings.Contains(err.Error(), tc.want) {
t.Errorf("error %q does not carry %q", err.Error(), tc.want)
}
})
}
}