2026-08-13 14:41:57 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package asm
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TestRISCVFrameSpadjAndLines checks that a framed function records its
|
|
|
|
|
// stack-adjustment boundaries and source-line table, the inputs the GOOBJ
|
|
|
|
|
// emitter turns into the pcsp/pcfile/pcline tables.
|
|
|
|
|
func TestRISCVFrameSpadjAndLines(t *testing.T) {
|
|
|
|
|
f, errs := parser.Parse("frame_riscv64.s", `#include "textflag.h"
|
|
|
|
|
|
|
|
|
|
TEXT ·framed(SB), NOSPLIT, $16-16
|
|
|
|
|
MOV a+0(FP), X10
|
|
|
|
|
MOV b+8(FP), X11
|
|
|
|
|
ADD X11, X10, X10
|
|
|
|
|
MOV X10, ret+16(FP)
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
t.Fatalf("parse: %v", errs)
|
|
|
|
|
}
|
|
|
|
|
img, err := AssembleFileRISCV(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("AssembleFileRISCV: %v", err)
|
|
|
|
|
}
|
|
|
|
|
fn := img.Funcs[0]
|
|
|
|
|
if fn.Size != 24 {
|
|
|
|
|
t.Fatalf("size = %d, want 24", fn.Size)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// autosize = 16 + 8 = 24; the prologue boundary is just past its C.ADDI
|
|
|
|
|
// (SD 4 + C.ADDI 2 = 6), and the RET restores SP just past its C.ADDI
|
|
|
|
|
// (RET starts at 16; C.LDSP 2 + C.ADDI 2 = 20).
|
|
|
|
|
wantSpadj := []SpadjStep{{PC: 6, Value: 24}, {PC: 20, Value: 0}}
|
|
|
|
|
if len(fn.Spadj) != len(wantSpadj) {
|
|
|
|
|
t.Fatalf("spadj = %v, want %v", fn.Spadj, wantSpadj)
|
|
|
|
|
}
|
|
|
|
|
for i := range wantSpadj {
|
|
|
|
|
if fn.Spadj[i] != wantSpadj[i] {
|
|
|
|
|
t.Errorf("spadj[%d] = %v, want %v", i, fn.Spadj[i], wantSpadj[i])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// One line entry per instruction, in emission order.
|
|
|
|
|
wantLines := []LineEntry{
|
|
|
|
|
{Offset: 8, Line: 4},
|
|
|
|
|
{Offset: 10, Line: 5},
|
|
|
|
|
{Offset: 12, Line: 6},
|
|
|
|
|
{Offset: 14, Line: 7},
|
|
|
|
|
{Offset: 16, Line: 8},
|
|
|
|
|
}
|
|
|
|
|
if len(fn.Lines) != len(wantLines) {
|
|
|
|
|
t.Fatalf("lines = %v, want %v", fn.Lines, wantLines)
|
|
|
|
|
}
|
|
|
|
|
for i := range wantLines {
|
|
|
|
|
if fn.Lines[i] != wantLines[i] {
|
|
|
|
|
t.Errorf("lines[%d] = %v, want %v", i, fn.Lines[i], wantLines[i])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 23:49:13 +02:00
|
|
|
// TestRISCVFrameSpadjLargeFrame checks the stack-adjustment boundaries of a
|
|
|
|
|
// frame past the imm12 range: the prologue materialises the LR-store address
|
|
|
|
|
// and the SP adjustment through X31 (C.LUI + C.ADD + SD, then C.LUI + ADDIW +
|
|
|
|
|
// C.ADD), so the SP boundary lands at PC 16, and the RET closes with
|
|
|
|
|
// C.LDSP plus the same X31 adjustment, 10 bytes. Regression: both helpers
|
|
|
|
|
// assumed the small-frame prologue and reported 8 and 6.
|
|
|
|
|
func TestRISCVFrameSpadjLargeFrame(t *testing.T) {
|
|
|
|
|
f, errs := parser.Parse("bigframe_riscv64.s", `#include "textflag.h"
|
|
|
|
|
|
|
|
|
|
TEXT ·big(SB), NOSPLIT, $9000-8
|
|
|
|
|
MOV a+0(FP), X10
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
t.Fatalf("parse: %v", errs)
|
|
|
|
|
}
|
|
|
|
|
img, err := AssembleFileRISCV(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("AssembleFileRISCV: %v", err)
|
|
|
|
|
}
|
|
|
|
|
fn := img.Funcs[0]
|
|
|
|
|
|
|
|
|
|
// autosize = 9008. Prologue: C.LUI X31 + C.ADD X31,SP (4) + SD (4) +
|
|
|
|
|
// C.LUI X31 + ADDIW X31 + C.ADD SP,X31 (8) = 16 bytes to the SP boundary;
|
|
|
|
|
// C.SDSP X1 (2) follows, so the body starts at 18.
|
|
|
|
|
wantSpadj := []SpadjStep{{PC: 16, Value: 9008}, {PC: 36, Value: 0}}
|
|
|
|
|
if len(fn.Spadj) != len(wantSpadj) {
|
|
|
|
|
t.Fatalf("spadj = %v, want %v", fn.Spadj, wantSpadj)
|
|
|
|
|
}
|
|
|
|
|
for i := range wantSpadj {
|
|
|
|
|
if fn.Spadj[i] != wantSpadj[i] {
|
|
|
|
|
t.Errorf("spadj[%d] = %v, want %v", i, fn.Spadj[i], wantSpadj[i])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// The FP load materialises its 9016-byte offset through X31 as well
|
|
|
|
|
// (8 bytes), then RET's epilogue (C.LDSP + X31 adjust = 10) plus JALR.
|
|
|
|
|
if fn.Size != 18+8+14 {
|
|
|
|
|
t.Errorf("size = %d, want %d", fn.Size, 18+8+14)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-13 14:41:57 +02:00
|
|
|
// TestRISCVRegAliases checks the Go ABI register aliases that the toolchain
|
|
|
|
|
// defines: LR is the link register (X1) and TMP is the assembler scratch
|
|
|
|
|
// register (X31/T6).
|
|
|
|
|
func TestRISCVRegAliases(t *testing.T) {
|
|
|
|
|
for name, want := range map[string]int{
|
|
|
|
|
"X1": 1, "RA": 1, "LR": 1,
|
|
|
|
|
"X31": 31, "T6": 31, "TMP": 31,
|
|
|
|
|
"X2": 2, "SP": 2,
|
|
|
|
|
} {
|
|
|
|
|
if got := riscvRegNum(name); got != want {
|
|
|
|
|
t.Errorf("riscvRegNum(%q) = %d, want %d", name, got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|