feat(riscv): add MOV pseudo-instruction and RVC compressed encoding
Assisted-by: DeepSeek V4 Pro
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
// 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/ast"
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||
)
|
||||
|
||||
// firstTextRISCV parses assembly source and returns the first TEXT function body.
|
||||
func firstTextRISCV(t *testing.T, src string) *ast.Text {
|
||||
t.Helper()
|
||||
f, errs := parser.Parse("f_riscv64.s", src)
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
for _, d := range f.Decls {
|
||||
if fn, ok := d.(*ast.Text); ok {
|
||||
return fn
|
||||
}
|
||||
}
|
||||
t.Fatal("no TEXT found")
|
||||
return nil
|
||||
}
|
||||
|
||||
// assembleRISCVHelper assembles one TEXT function and returns its code bytes.
|
||||
func assembleRISCVHelper(t *testing.T, fn *ast.Text) []byte {
|
||||
t.Helper()
|
||||
code, _, err := assembleRISCV(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("assemble: %v", err)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func TestRISCV_add(t *testing.T) {
|
||||
// func add(a, b int64) int64
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·add(SB), NOSPLIT, $0-24
|
||||
MOV a+0(FP), X10
|
||||
MOV b+8(FP), X11
|
||||
ADD X11, X10, X10
|
||||
MOV X10, ret+16(FP)
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// should be 12 bytes with RVC: C.LDSP + C.LDSP + ADD + C.SDSP + C.JR
|
||||
_ = code
|
||||
if len(code) == 0 {
|
||||
t.Error("empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_arithmetic(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·arith(SB), NOSPLIT, $0
|
||||
ADD X10, X11, X12
|
||||
SUB X12, X13, X14
|
||||
MUL X14, X15, X16
|
||||
DIV X16, X17, X18
|
||||
REM X18, X19, X20
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 5 R-type instructions + RET compressed = 5*4 + 2 = 22
|
||||
if len(code) != 22 {
|
||||
t.Errorf("expected 22 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_loadStore(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·mem(SB), NOSPLIT, $0
|
||||
LD (X10), X11
|
||||
SD X11, (X12)
|
||||
LW (X13), X14
|
||||
SW X14, (X15)
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 4 loads/stores (4B each) + C.JR RET (2B) = 18
|
||||
if len(code) != 18 {
|
||||
t.Errorf("expected 18 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_immediate(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·imm(SB), NOSPLIT, $0
|
||||
ADDI X10, $42, X11
|
||||
ANDI X11, $0xFF, X12
|
||||
ORI X12, $1, X13
|
||||
XORI X13, $0, X14
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 4 I-type + C.JR = 4*4 + 2 = 18
|
||||
if len(code) != 18 {
|
||||
t.Errorf("expected 18 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_branches(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·br(SB), NOSPLIT, $0
|
||||
ADDI X10, $1, X10
|
||||
loop:
|
||||
BEQ X10, X11, done
|
||||
ADDI X10, $1, X10
|
||||
JMP loop
|
||||
done:
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
_ = code
|
||||
if len(code) == 0 {
|
||||
t.Error("empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOV_imm_small(t *testing.T) {
|
||||
// MOV $42, rd → ADDI (fits in 12 bits). Not RVC-compressed (treated as MOV, not ADDI).
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·small(SB), NOSPLIT, $0
|
||||
MOV $42, X10
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// ADDI (4B) + C.JR (2B) = 6
|
||||
if len(code) != 6 {
|
||||
t.Errorf("expected 6 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOV_imm_large(t *testing.T) {
|
||||
// MOV $0x12345, rd → LUI + ADDIW (8 bytes total)
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·large(SB), NOSPLIT, $0
|
||||
MOV $0x12345, X10
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// LUI (4B) + ADDIW (4B) + C.JR (2B) = 10
|
||||
if len(code) != 10 {
|
||||
t.Errorf("expected 10 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOV_reg(t *testing.T) {
|
||||
// MOV rs, rd → ADDI $0, rs, rd, compresses to C.MV
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·reg(SB), NOSPLIT, $0
|
||||
MOV X10, X11
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.MV (2B) + C.JR (2B) = 4
|
||||
if len(code) != 4 {
|
||||
t.Errorf("expected 4 bytes, got %d (% x)", len(code), code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOV_frame(t *testing.T) {
|
||||
// MOV name+off(FP), rd → load with frame mapping
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·frame(SB), NOSPLIT, $0-8
|
||||
MOV a+0(FP), X10
|
||||
MOV X10, ret+0(FP)
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.LDSP (2B) + C.SDSP (2B) + C.JR (2B) = 6
|
||||
if len(code) != 6 {
|
||||
t.Errorf("expected 6 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_RVC_loadStore(t *testing.T) {
|
||||
// Verify that loads/stores from SP (X2) are compressed.
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·rvcstore(SB), NOSPLIT, $0
|
||||
LD 0(X2), X10
|
||||
SD X10, 8(X2)
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.LDSP (2B) + C.SDSP (2B) + C.JR (2B) = 6
|
||||
if len(code) != 6 {
|
||||
t.Errorf("expected 6 bytes, got %d (% x)", len(code), code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_atomics(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·amo(SB), NOSPLIT, $0
|
||||
AMOADDD X10, (X11), X12
|
||||
LRD (X13), X14
|
||||
SCD X15, (X16), X17
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 3 AMO instructions (4B each) + C.JR (2B) = 14
|
||||
if len(code) != 14 {
|
||||
t.Errorf("expected 14 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_fpArith(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·fpadd(SB), NOSPLIT, $0
|
||||
FADDD F10, F11, F12
|
||||
FSUBD F12, F13, F14
|
||||
FMULD F14, F15, F16
|
||||
FDIVD F16, F17, F18
|
||||
FSQRTD F18, F19
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 5 FP instructions (4B each) + C.JR (2B) = 22
|
||||
if len(code) != 22 {
|
||||
t.Errorf("expected 22 bytes, got %d (%d)", len(code), len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_csr(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·csrtest(SB), NOSPLIT, $0
|
||||
CSRRS $0x300, X0, X10
|
||||
CSRRW $0x305, X10, X11
|
||||
CSRRSI $0x304, $5, X12
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 3 CSR instructions (4B each) + C.JR (2B) = 14
|
||||
if len(code) != 14 {
|
||||
t.Errorf("expected 14 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_fma(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·fmatest(SB), NOSPLIT, $0
|
||||
FMADDD F10, F11, F12, F13
|
||||
FMSUBD F13, F14, F15, F16
|
||||
FNMSUBD F16, F17, F18, F19
|
||||
FNMADDD F19, F10, F11, F12
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 4 FMA instructions (4B each) + C.JR (2B) = 18
|
||||
if len(code) != 18 {
|
||||
t.Errorf("expected 18 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_conversions(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·cvt(SB), NOSPLIT, $0
|
||||
FCVTDL X10, F10
|
||||
FCVTLD F10, X11
|
||||
FMVXD F10, X12
|
||||
FMVDX X12, F11
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 4 conversion instructions (4B each) + C.JR (2B) = 18
|
||||
if len(code) != 18 {
|
||||
t.Errorf("expected 18 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_fpCmp(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·cmp(SB), NOSPLIT, $0
|
||||
FEQD F10, F11, X10
|
||||
FLTD F12, F13, X11
|
||||
FLED F14, F15, X12
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// 3 FP compare (4B each) + C.JR (2B) = 14
|
||||
if len(code) != 14 {
|
||||
t.Errorf("expected 14 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_forwardBranch(t *testing.T) {
|
||||
// Forward label reference — must not fail.
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·fwd(SB), NOSPLIT, $0
|
||||
ADDI X10, $1, X10
|
||||
BEQ X10, X11, done
|
||||
ADDI X10, $1, X10
|
||||
done:
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
_ = code
|
||||
if len(code) == 0 {
|
||||
t.Error("empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_RVC_ADDI(t *testing.T) {
|
||||
// ADDI where rd=rs1 and small imm → C.ADDI
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·caddi(SB), NOSPLIT, $0
|
||||
ADDI X10, $5, X10
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.ADDI (2B) + C.JR (2B) = 4
|
||||
if len(code) != 4 {
|
||||
t.Errorf("expected 4 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_RVC_LI(t *testing.T) {
|
||||
// ADDI X0, $imm, rd → C.LI
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·cli(SB), NOSPLIT, $0
|
||||
ADDI X0, $7, X10
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.LI (2B) + C.JR (2B) = 4
|
||||
if len(code) != 4 {
|
||||
t.Errorf("expected 4 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_RVC_LUI(t *testing.T) {
|
||||
// LUI rd, small nonzero imm → C.LUI
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·clui(SB), NOSPLIT, $0
|
||||
LUI X10, $1
|
||||
RET
|
||||
`)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
// C.LUI (2B) + C.JR (2B) = 4
|
||||
if len(code) != 4 {
|
||||
t.Errorf("expected 4 bytes, got %d", len(code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_AssembleFile(t *testing.T) {
|
||||
src := `#include "textflag.h"
|
||||
|
||||
TEXT ·add(SB), NOSPLIT, $0-24
|
||||
MOV a+0(FP), X10
|
||||
RET
|
||||
|
||||
TEXT ·sub(SB), NOSPLIT, $0
|
||||
SUB X10, X11, X12
|
||||
RET
|
||||
`
|
||||
f, errs := parser.Parse("t_riscv64.s", src)
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileRISCV(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileRISCV: %v", err)
|
||||
}
|
||||
if len(img.Funcs) != 2 {
|
||||
t.Fatalf("expected 2 functions, got %d", len(img.Funcs))
|
||||
}
|
||||
// func add: C.LDSP(2) + C.JR(2) = 4
|
||||
if img.Funcs[0].Size != 4 {
|
||||
t.Errorf("add: expected 4 bytes, got %d", img.Funcs[0].Size)
|
||||
}
|
||||
// func sub: SUB(4) + C.JR(2) = 6
|
||||
if img.Funcs[1].Size != 6 {
|
||||
t.Errorf("sub: expected 6 bytes, got %d", img.Funcs[1].Size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_encodings(t *testing.T) {
|
||||
// Smoke test that all known RISC-V mnemonics encode successfully.
|
||||
tests := []struct {
|
||||
name, src string
|
||||
wantBytes int
|
||||
}{
|
||||
{"ADD", "ADD X10, X11, X12\nRET\n", 6},
|
||||
{"SUBW", "SUBW X10, X11, X12\nRET\n", 6},
|
||||
{"MUL", "MUL X10, X11, X12\nRET\n", 6},
|
||||
{"DIVW", "DIVW X10, X11, X12\nRET\n", 6},
|
||||
{"REMUW", "REMUW X10, X11, X12\nRET\n", 6},
|
||||
{"ADDIW", "ADDIW X10, $5, X11\nRET\n", 6},
|
||||
{"SLLI", "SLLI X10, $3, X11\nRET\n", 6}, // ADDI+SLLI? No, SLLI uses I-type
|
||||
{"SRLI", "SRLI X10, $2, X11\nRET\n", 6},
|
||||
{"SRAI", "SRAI X10, $1, X11\nRET\n", 6},
|
||||
{"LB", "LB (X10), X11\nRET\n", 6},
|
||||
{"LBU", "LBU (X10), X11\nRET\n", 6},
|
||||
{"LH", "LH (X10), X11\nRET\n", 6},
|
||||
{"LHU", "LHU (X10), X11\nRET\n", 6},
|
||||
{"LWU", "LWU (X10), X11\nRET\n", 6},
|
||||
{"SB", "SB X10, (X11)\nRET\n", 6},
|
||||
{"SH", "SH X10, (X11)\nRET\n", 6},
|
||||
{"SW", "SW X10, (X11)\nRET\n", 6},
|
||||
{"LUI", "LUI X10, $0x12345\nRET\n", 6},
|
||||
{"AUIPC", "AUIPC X10, $0\nRET\n", 6},
|
||||
{"FLW", "FLW (X10), F10\nRET\n", 6},
|
||||
{"FSW", "FSW F10, (X11)\nRET\n", 6},
|
||||
{"FADDS", "FADDS F10, F11, F12\nRET\n", 6},
|
||||
{"FMINS", "FMINS F10, F11, F12\nRET\n", 6},
|
||||
{"FMAXD", "FMAXD F10, F11, F12\nRET\n", 6},
|
||||
{"FCVTSD", "FCVTSD F10, F11\nRET\n", 6},
|
||||
{"FCVTDS", "FCVTDS F10, F11\nRET\n", 6},
|
||||
{"FMVXW", "FMVXW F10, X10\nRET\n", 6},
|
||||
{"FMADD_S", "FMADDS F10, F11, F12, F13\nRET\n", 6},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·`+tt.name+`(SB), NOSPLIT, $0
|
||||
`+tt.src)
|
||||
code := assembleRISCVHelper(t, fn)
|
||||
if len(code) != tt.wantBytes {
|
||||
t.Errorf("expected %d bytes, got %d", tt.wantBytes, len(code))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user