2026-08-02 18:22:00 +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/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()
|
2026-08-13 14:41:57 +02:00
|
|
|
code, _, _, _, _, err := assembleRISCV(fn)
|
2026-08-02 18:22:00 +02:00
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// 5 R-type instructions + RET = 5*4 + 4 = 24
|
|
|
|
|
if len(code) != 24 {
|
|
|
|
|
t.Errorf("expected 24 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// 4 loads/stores (4B each) + JALR RET (4B) = 20
|
|
|
|
|
if len(code) != 20 {
|
|
|
|
|
t.Errorf("expected 20 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_immediate(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·imm(SB), NOSPLIT, $0
|
2026-08-13 15:13:31 +02:00
|
|
|
ADDI $42, X10, X11
|
|
|
|
|
ANDI $0xFF, X11, X12
|
|
|
|
|
ORI $1, X12, X13
|
|
|
|
|
XORI $0, X13, X14
|
2026-08-02 18:22:00 +02:00
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// 4 I-type + JALR = 4*4 + 4 = 20
|
|
|
|
|
if len(code) != 20 {
|
|
|
|
|
t.Errorf("expected 20 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_branches(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·br(SB), NOSPLIT, $0
|
2026-08-13 15:13:31 +02:00
|
|
|
ADDI $1, X10, X10
|
2026-08-02 18:22:00 +02:00
|
|
|
loop:
|
|
|
|
|
BEQ X10, X11, done
|
2026-08-13 15:13:31 +02:00
|
|
|
ADDI $1, X10, X10
|
2026-08-02 18:22:00 +02:00
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// ADDI (4B) + JALR (4B) = 8
|
|
|
|
|
if len(code) != 8 {
|
|
|
|
|
t.Errorf("expected 8 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// LUI (4B) + ADDIW (4B) + JALR (4B) = 12
|
|
|
|
|
if len(code) != 12 {
|
|
|
|
|
t.Errorf("expected 12 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.MV (2B) + JALR (4B) = 6
|
|
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes, got %d (% x)", len(code), code)
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.LDSP (2B) + C.SDSP (2B) + JALR (4B) = 8
|
|
|
|
|
if len(code) != 8 {
|
|
|
|
|
t.Errorf("expected 8 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_loadStore(t *testing.T) {
|
2026-08-03 08:51:00 +02:00
|
|
|
// Verify that loads/stores from SP are compressed.
|
2026-08-02 18:22:00 +02:00
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·rvcstore(SB), NOSPLIT, $0
|
2026-08-03 08:51:00 +02:00
|
|
|
LD 0(SP), X10
|
|
|
|
|
SD X10, 8(SP)
|
2026-08-02 18:22:00 +02:00
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.LDSP (2B) + C.SDSP (2B) + JALR (4B) = 8
|
|
|
|
|
if len(code) != 8 {
|
|
|
|
|
t.Errorf("expected 8 bytes, got %d (% x)", len(code), code)
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// 3 AMO instructions (4B each) + JALR (4B) = 16
|
|
|
|
|
if len(code) != 16 {
|
|
|
|
|
t.Errorf("expected 16 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// 5 FP instructions (4B each) + JALR (4B) = 24
|
|
|
|
|
if len(code) != 24 {
|
|
|
|
|
t.Errorf("expected 24 bytes, got %d (%d)", len(code), len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// 3 CSR instructions (4B each) + JALR (4B) = 16
|
|
|
|
|
if len(code) != 16 {
|
|
|
|
|
t.Errorf("expected 16 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// 4 FMA instructions (4B each) + JALR (4B) = 20
|
|
|
|
|
if len(code) != 20 {
|
|
|
|
|
t.Errorf("expected 20 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// 4 conversion instructions (4B each) + JALR (4B) = 20
|
|
|
|
|
if len(code) != 20 {
|
|
|
|
|
t.Errorf("expected 20 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// 3 FP compare (4B each) + JALR (4B) = 16
|
|
|
|
|
if len(code) != 16 {
|
|
|
|
|
t.Errorf("expected 16 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_forwardBranch(t *testing.T) {
|
|
|
|
|
// Forward label reference — must not fail.
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·fwd(SB), NOSPLIT, $0
|
2026-08-13 15:13:31 +02:00
|
|
|
ADDI $1, X10, X10
|
2026-08-02 18:22:00 +02:00
|
|
|
BEQ X10, X11, done
|
2026-08-13 15:13:31 +02:00
|
|
|
ADDI $1, X10, X10
|
2026-08-02 18:22:00 +02:00
|
|
|
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
|
2026-08-13 15:13:31 +02:00
|
|
|
ADDI $5, X10, X10
|
2026-08-02 18:22:00 +02:00
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.ADDI (2B) + JALR (4B) = 6
|
|
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_LI(t *testing.T) {
|
|
|
|
|
// ADDI X0, $imm, rd → C.LI
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cli(SB), NOSPLIT, $0
|
2026-08-13 15:13:31 +02:00
|
|
|
ADDI $7, X0, X10
|
2026-08-02 18:22:00 +02:00
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.LI (2B) + JALR (4B) = 6
|
|
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.LUI (2B) + JALR (4B) = 6
|
|
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes, got %d", len(code))
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
}
|
2026-08-13 14:41:57 +02:00
|
|
|
// func add: C.LDSP(2) + JALR(4) = 6
|
|
|
|
|
if img.Funcs[0].Size != 6 {
|
|
|
|
|
t.Errorf("add: expected 6 bytes, got %d", img.Funcs[0].Size)
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
2026-08-13 14:41:57 +02:00
|
|
|
// func sub: SUB(4) + JALR(4) = 8
|
|
|
|
|
if img.Funcs[1].Size != 8 {
|
|
|
|
|
t.Errorf("sub: expected 8 bytes, got %d", img.Funcs[1].Size)
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_encodings(t *testing.T) {
|
|
|
|
|
// Smoke test that all known RISC-V mnemonics encode successfully.
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name, src string
|
|
|
|
|
wantBytes int
|
|
|
|
|
}{
|
2026-08-13 14:41:57 +02:00
|
|
|
{"ADD", "ADD X10, X11, X12\nRET\n", 8},
|
|
|
|
|
{"SUBW", "SUBW X10, X11, X12\nRET\n", 8},
|
|
|
|
|
{"MUL", "MUL X10, X11, X12\nRET\n", 8},
|
|
|
|
|
{"DIVW", "DIVW X10, X11, X12\nRET\n", 8},
|
|
|
|
|
{"REMUW", "REMUW X10, X11, X12\nRET\n", 8},
|
2026-08-13 15:13:31 +02:00
|
|
|
{"ADDIW", "ADDIW $5, X10, X11\nRET\n", 8},
|
|
|
|
|
{"SLLI", "SLLI $3, X10, X11\nRET\n", 8}, // ADDI+SLLI? No, SLLI uses I-type
|
|
|
|
|
{"SRLI", "SRLI $2, X10, X11\nRET\n", 8},
|
|
|
|
|
{"SRAI", "SRAI $1, X10, X11\nRET\n", 8},
|
2026-08-13 14:41:57 +02:00
|
|
|
{"LB", "LB (X10), X11\nRET\n", 8},
|
|
|
|
|
{"LBU", "LBU (X10), X11\nRET\n", 8},
|
|
|
|
|
{"LH", "LH (X10), X11\nRET\n", 8},
|
|
|
|
|
{"LHU", "LHU (X10), X11\nRET\n", 8},
|
|
|
|
|
{"LWU", "LWU (X10), X11\nRET\n", 8},
|
|
|
|
|
{"SB", "SB X10, (X11)\nRET\n", 8},
|
|
|
|
|
{"SH", "SH X10, (X11)\nRET\n", 8},
|
|
|
|
|
{"SW", "SW X10, (X11)\nRET\n", 8},
|
|
|
|
|
{"LUI", "LUI X10, $0x12345\nRET\n", 8},
|
|
|
|
|
{"AUIPC", "AUIPC X10, $0\nRET\n", 8},
|
|
|
|
|
{"FLW", "FLW (X10), F10\nRET\n", 8},
|
|
|
|
|
{"FSW", "FSW F10, (X11)\nRET\n", 8},
|
|
|
|
|
{"FADDS", "FADDS F10, F11, F12\nRET\n", 8},
|
|
|
|
|
{"FMINS", "FMINS F10, F11, F12\nRET\n", 8},
|
|
|
|
|
{"FMAXD", "FMAXD F10, F11, F12\nRET\n", 8},
|
|
|
|
|
{"FCVTSD", "FCVTSD F10, F11\nRET\n", 8},
|
|
|
|
|
{"FCVTDS", "FCVTDS F10, F11\nRET\n", 8},
|
|
|
|
|
{"FMVXW", "FMVXW F10, X10\nRET\n", 8},
|
|
|
|
|
{"FMADD_S", "FMADDS F10, F11, F12, F13\nRET\n", 8},
|
2026-08-02 18:22:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-03 01:08:00 +02:00
|
|
|
|
|
|
|
|
func TestRISCV_RVC_branch(t *testing.T) {
|
|
|
|
|
// BEQ rs, X0, target → C.BEQZ when rs is in prime regs and offset fits.
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cbeqz(SB), NOSPLIT, $0
|
2026-08-13 15:13:31 +02:00
|
|
|
ADDI $1, X10, X10
|
2026-08-03 01:08:00 +02:00
|
|
|
BEQ X10, X0, done
|
2026-08-13 15:13:31 +02:00
|
|
|
ADDI $1, X10, X10
|
2026-08-03 01:08:00 +02:00
|
|
|
done:
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.ADDI(2) + C.BEQZ(2) + C.ADDI(2) + JALR(4) = 10 (all compress)
|
|
|
|
|
if len(code) != 10 {
|
|
|
|
|
t.Errorf("expected 10 bytes with C.BEQZ, got %d", len(code))
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_CJ(t *testing.T) {
|
|
|
|
|
// JMP target → C.J when offset fits.
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cj(SB), NOSPLIT, $0
|
|
|
|
|
JMP done
|
|
|
|
|
done:
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.J(2) + JALR(4) = 6
|
|
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes with C.J, got %d", len(code))
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_CADD(t *testing.T) {
|
|
|
|
|
// ADD where rd==rs1 and both in prime regs → C.ADD.
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cadd(SB), NOSPLIT, $0
|
|
|
|
|
ADD X10, X11, X10
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.ADD(2) + JALR(4) = 6
|
|
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes with C.ADD, got %d", len(code))
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_CADD_commute(t *testing.T) {
|
|
|
|
|
// ADD where rd==rs2 (commutative swap) → C.ADD.
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cadd2(SB), NOSPLIT, $0
|
|
|
|
|
ADD X11, X10, X10
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.ADD(2) + JALR(4) = 6
|
|
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes with C.ADD (commuted), got %d", len(code))
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_CSUB(t *testing.T) {
|
2026-08-13 15:13:31 +02:00
|
|
|
// SUB rs2, rs1, rd → C.SUB when rd == rs1 and both in prime regs.
|
2026-08-03 01:08:00 +02:00
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·csub(SB), NOSPLIT, $0
|
|
|
|
|
SUB X11, X10, X10
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 15:13:31 +02:00
|
|
|
// SUB X11, X10, X10 → rs2=X11, rs1=X10, rd=X10; rd==rs1 → C.SUB (2B) + JALR (4B) = 6.
|
|
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes with C.SUB, got %d (% x)", len(code), code)
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_CXOR(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cxor(SB), NOSPLIT, $0
|
|
|
|
|
XOR X10, X11, X10
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes with C.XOR, got %d", len(code))
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_COR(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cor(SB), NOSPLIT, $0
|
|
|
|
|
OR X10, X11, X10
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes with C.OR, got %d", len(code))
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_CAND(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cand(SB), NOSPLIT, $0
|
|
|
|
|
AND X10, X11, X10
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes with C.AND, got %d", len(code))
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_CFLDSP(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cfldsp(SB), NOSPLIT, $0-8
|
|
|
|
|
FLD a+0(FP), F10
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.FLDSP(2) + JALR(4) = 6
|
|
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes with C.FLDSP, got %d", len(code))
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_CFSDSP(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cfsdsp(SB), NOSPLIT, $0-8
|
|
|
|
|
FSD F10, ret+0(FP)
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// C.FSDSP(2) + JALR(4) = 6
|
|
|
|
|
if len(code) != 6 {
|
|
|
|
|
t.Errorf("expected 6 bytes with C.FSDSP, got %d", len(code))
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-08-03 08:51:00 +02:00
|
|
|
|
|
|
|
|
func TestRISCV_SB_addr(t *testing.T) {
|
|
|
|
|
// MOV $sym<>(SB), rd → AUIPC + ADDI (8 bytes for SB).
|
|
|
|
|
src := `#include "textflag.h"
|
|
|
|
|
TEXT ·sbaddr(SB), NOSPLIT, $0
|
|
|
|
|
MOV $answer<>(SB), X10
|
|
|
|
|
RET
|
|
|
|
|
GLOBL answer<>(SB), RODATA, $8
|
|
|
|
|
DATA answer<>+0(SB)/8, $42
|
|
|
|
|
`
|
|
|
|
|
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)
|
|
|
|
|
}
|
2026-08-13 14:41:57 +02:00
|
|
|
// AUIPC(4) + ADDI(4) + JALR(4) = 12
|
|
|
|
|
if img.Funcs[0].Size != 12 {
|
|
|
|
|
t.Errorf("expected 12 bytes, got %d", img.Funcs[0].Size)
|
2026-08-03 08:51:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_SB_store(t *testing.T) {
|
|
|
|
|
// MOV rd, sym<>(SB) → AUIPC + SD (8 bytes for SB).
|
|
|
|
|
src := `#include "textflag.h"
|
|
|
|
|
TEXT ·sbstore(SB), NOSPLIT, $0
|
|
|
|
|
MOV X10, result<>(SB)
|
|
|
|
|
RET
|
|
|
|
|
GLOBL result<>(SB), NOPTR, $8
|
|
|
|
|
`
|
|
|
|
|
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)
|
|
|
|
|
}
|
2026-08-13 14:41:57 +02:00
|
|
|
// AUIPC X31(4) + SD X10,0(X31)(4) + JALR(4) = 12
|
|
|
|
|
if img.Funcs[0].Size != 12 {
|
|
|
|
|
t.Errorf("expected 12 bytes, got %d", img.Funcs[0].Size)
|
2026-08-03 08:51:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_ELF(t *testing.T) {
|
|
|
|
|
src := `#include "textflag.h"
|
|
|
|
|
TEXT ·simple(SB), NOSPLIT, $0
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
obj, err := img.ELFRISCVObject()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ELFRISCVObject: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(obj) < 4 || obj[0] != 0x7f || obj[1] != 'E' || obj[2] != 'L' || obj[3] != 'F' {
|
|
|
|
|
t.Fatal("not a valid ELF file")
|
|
|
|
|
}
|
|
|
|
|
if len(obj) >= 20 {
|
|
|
|
|
machine := uint16(obj[18]) | uint16(obj[19])<<8
|
|
|
|
|
if machine != 243 {
|
|
|
|
|
t.Errorf("e_machine = %d, want 243 (EM_RISCV)", machine)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_ELF_withData(t *testing.T) {
|
|
|
|
|
src := `#include "textflag.h"
|
|
|
|
|
TEXT ·get(SB), NOSPLIT, $0
|
|
|
|
|
RET
|
|
|
|
|
GLOBL val<>(SB), RODATA, $4
|
|
|
|
|
DATA val<>+0(SB)/4, $7
|
|
|
|
|
`
|
|
|
|
|
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.DataSyms) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 data symbol, got %d", len(img.DataSyms))
|
|
|
|
|
}
|
|
|
|
|
if img.DataSyms[0].Name != "val" {
|
|
|
|
|
t.Errorf("data symbol name = %q, want val", img.DataSyms[0].Name)
|
|
|
|
|
}
|
|
|
|
|
if img.DataSyms[0].Size != 4 {
|
|
|
|
|
t.Errorf("data symbol size = %d, want 4", img.DataSyms[0].Size)
|
|
|
|
|
}
|
|
|
|
|
obj, err := img.ELFRISCVObject()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ELFRISCVObject: %v", err)
|
|
|
|
|
}
|
|
|
|
|
_ = obj
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_SB_load(t *testing.T) {
|
|
|
|
|
// MOV sym<>(SB), rd → AUIPC + LD (8 bytes for SB).
|
|
|
|
|
src := `#include "textflag.h"
|
|
|
|
|
TEXT ·sbload(SB), NOSPLIT, $0
|
|
|
|
|
MOV answer<>(SB), X10
|
|
|
|
|
RET
|
|
|
|
|
GLOBL answer<>(SB), RODATA, $8
|
|
|
|
|
DATA answer<>+0(SB)/8, $42
|
|
|
|
|
`
|
|
|
|
|
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)
|
|
|
|
|
}
|
2026-08-13 14:41:57 +02:00
|
|
|
// AUIPC(4) + LD(4) + JALR(4) = 12
|
|
|
|
|
if img.Funcs[0].Size != 12 {
|
|
|
|
|
t.Errorf("expected 12 bytes, got %d", img.Funcs[0].Size)
|
2026-08-03 08:51:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_system_instrs(t *testing.T) {
|
|
|
|
|
// Test FENCE, ECALL, EBREAK encoding.
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·sys(SB), NOSPLIT, $0
|
|
|
|
|
FENCE
|
|
|
|
|
ECALL
|
|
|
|
|
EBREAK
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 15:13:31 +02:00
|
|
|
// FENCE(4) + ECALL(4) + C.EBREAK(2) + JALR(4) = 14
|
|
|
|
|
if len(code) != 14 {
|
|
|
|
|
t.Errorf("expected 14 bytes, got %d (% x)", len(code), code)
|
2026-08-03 08:51:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_MOV_sym_FP_error(t *testing.T) {
|
|
|
|
|
// MOV $sym(FP), rd should return an error (unsupported).
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·badfp(SB), NOSPLIT, $0
|
|
|
|
|
MOV $arg(FP), X10
|
|
|
|
|
RET
|
|
|
|
|
`)
|
2026-08-13 14:41:57 +02:00
|
|
|
_, _, _, _, _, err := assembleRISCV(fn)
|
2026-08-03 08:51:00 +02:00
|
|
|
if err == nil {
|
|
|
|
|
t.Error("expected error for MOV $arg(FP), got nil")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_CALL(t *testing.T) {
|
|
|
|
|
// CALL target → AUIPC + JALR (8 bytes).
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·calltest(SB), NOSPLIT, $0
|
|
|
|
|
CALL sub
|
|
|
|
|
done:
|
|
|
|
|
RET
|
|
|
|
|
sub:
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 14:41:57 +02:00
|
|
|
// A zero-frame non-leaf function saves LR: prologue (8) + CALL (8) +
|
|
|
|
|
// two framed RETs (8 each) = 32.
|
|
|
|
|
if len(code) != 32 {
|
|
|
|
|
t.Errorf("expected 32 bytes with CALL, got %d", len(code))
|
2026-08-03 08:51:00 +02:00
|
|
|
}
|
|
|
|
|
}
|