feat(asm): expand riscv64 memory offsets beyond the 12-bit immediate
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
7aba29ac67
commit
0211d6672d
2 files changed
+239
-19
No files matched your search
@@ -0,0 +1,128 @@
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user