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 (
|
2026-08-13 18:12:22 +02:00
|
|
|
"bytes"
|
2026-09-20 06:44:51 +02:00
|
|
|
"encoding/binary"
|
|
|
|
|
"encoding/hex"
|
2026-09-19 23:49:13 +02:00
|
|
|
"strings"
|
2026-08-02 18:22:00 +02:00
|
|
|
"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 15:42:38 +02:00
|
|
|
// Four register-relative loads/stores compress (2B each) + 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_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) {
|
2026-08-13 17:41:16 +02:00
|
|
|
// MOV $42, rd → ADDI (fits in 12 bits, but not C.LI's 6-bit immediate).
|
2026-08-02 18:22:00 +02:00
|
|
|
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) {
|
2026-08-13 17:41:16 +02:00
|
|
|
// MOV $0x12345, rd → C.LUI $18 (2B) + ADDIW $837 (4B).
|
2026-08-02 18:22:00 +02:00
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·large(SB), NOSPLIT, $0
|
|
|
|
|
MOV $0x12345, X10
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 17:41:16 +02:00
|
|
|
// C.LUI (2B) + ADDIW (4B) + JALR (4B) = 10
|
|
|
|
|
if len(code) != 10 {
|
|
|
|
|
t.Errorf("expected 10 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) {
|
2026-09-16 23:12:31 +02:00
|
|
|
// Forward label reference; must not fail.
|
2026-08-02 18:22:00 +02:00
|
|
|
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},
|
2026-08-13 15:42:38 +02:00
|
|
|
{"SW", "SW X10, (X11)\nRET\n", 6},
|
2026-08-13 14:41:57 +02:00
|
|
|
{"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) {
|
2026-08-13 17:57:10 +02:00
|
|
|
// Branches are never RVC-compressed (no C.BEQZ/C.BNEZ), matching go tool asm.
|
2026-08-03 01:08:00 +02:00
|
|
|
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 17:57:10 +02:00
|
|
|
// C.ADDI(2) + BEQ(4) + C.ADDI(2) + JALR(4) = 12
|
|
|
|
|
if len(code) != 12 {
|
|
|
|
|
t.Errorf("expected 12 bytes with uncompressed BEQ, got %d", len(code))
|
2026-08-03 01:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_RVC_CJ(t *testing.T) {
|
2026-08-13 17:57:10 +02:00
|
|
|
// JMP target → JAL X0 (never compressed to C.J), matching go tool asm.
|
2026-08-03 01:08:00 +02:00
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·cj(SB), NOSPLIT, $0
|
|
|
|
|
JMP done
|
|
|
|
|
done:
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
2026-08-13 17:57:10 +02:00
|
|
|
// JAL(4) + JALR(4) = 8
|
|
|
|
|
if len(code) != 8 {
|
|
|
|
|
t.Errorf("expected 8 bytes with uncompressed JMP, 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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 23:49:13 +02:00
|
|
|
// TestRISCV_RVC_StorePatterns pins the register-relative compressed store
|
|
|
|
|
// encodings for offsets with immediate bits 4 and 5 set, byte-identical to
|
|
|
|
|
// the toolchain's encodeCS (patterns {5,4,3,7,6} and {5,4,3,2,6}).
|
|
|
|
|
// Regression: the store-side patterns dropped imm[4], so every such store
|
|
|
|
|
// silently encoded the wrong address while the loads stayed correct.
|
|
|
|
|
func TestRISCV_RVC_StorePatterns(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·csstores(SB), NOSPLIT, $0
|
|
|
|
|
SD X9, 24(X8)
|
|
|
|
|
SW X10, 16(X11)
|
|
|
|
|
FSD F8, 40(X12)
|
|
|
|
|
LD 24(X8), X9
|
|
|
|
|
LW 16(X11), X10
|
|
|
|
|
FLD 40(X12), F8
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
|
|
|
|
want := []byte{
|
|
|
|
|
0x04, 0xec, // c.sd x9, 24(x8)
|
|
|
|
|
0x88, 0xc9, // c.sw x10, 16(x11)
|
|
|
|
|
0x00, 0xb6, // c.fsd f8, 40(x12)
|
|
|
|
|
0x04, 0x6c, // c.ld x9, 24(x8)
|
|
|
|
|
0x88, 0x49, // c.lw x10, 16(x11)
|
|
|
|
|
0x00, 0x36, // c.fld f8, 40(x12)
|
|
|
|
|
0x67, 0x80, 0x00, 0x00, // jalr x0, 0(x1)
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(code, want) {
|
|
|
|
|
t.Errorf("code = % x\nwant % x", code, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestRISCV_FENCE pins the FENCE encoding: the toolchain expands the bare
|
|
|
|
|
// mnemonic to fence iorw, iorw (0x0FF0000F), not fence 0,0.
|
|
|
|
|
func TestRISCV_FENCE(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·fence(SB), NOSPLIT, $0
|
|
|
|
|
FENCE
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
|
|
|
|
want := []byte{
|
|
|
|
|
0x0f, 0x00, 0xf0, 0x0f, // fence iorw, iorw
|
|
|
|
|
0x67, 0x80, 0x00, 0x00, // jalr x0, 0(x1)
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(code, want) {
|
|
|
|
|
t.Errorf("code = % x\nwant % x", code, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestRISCV_RVC_WidthSpellings pins the compression of the GOROOT width
|
|
|
|
|
// spellings: MOVW and MOVD lower to their base load/store and compress
|
|
|
|
|
// exactly like LW/SW/FLD/FSD would (the toolchain compresses these shapes;
|
|
|
|
|
// before the normalisation they stayed 4 bytes).
|
|
|
|
|
func TestRISCV_RVC_WidthSpellings(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·widths(SB), NOSPLIT, $0-16
|
|
|
|
|
MOVW w+0(FP), X9
|
|
|
|
|
MOVW X9, v+4(FP)
|
|
|
|
|
MOVD d+0(FP), F8
|
|
|
|
|
MOVD F8, r+8(FP)
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
|
|
|
|
want := []byte{
|
|
|
|
|
0xa2, 0x44, // c.lwsp x9, 8
|
|
|
|
|
0x26, 0xc6, // c.swsp x9, 12
|
|
|
|
|
0x22, 0x24, // c.fldsp f8, 8
|
|
|
|
|
0x22, 0xa8, // c.fsdsp f8, 16
|
|
|
|
|
0x67, 0x80, 0x00, 0x00, // jalr x0, 0(x1)
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(code, want) {
|
|
|
|
|
t.Errorf("code = % x\nwant % x", code, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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) {
|
2026-08-13 18:12:22 +02:00
|
|
|
// CALL sym(SB) → JAL X1, sym(SB) with a single R_RISCV_JAL relocation.
|
2026-08-03 08:51:00 +02:00
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·calltest(SB), NOSPLIT, $0
|
2026-08-13 18:12:22 +02:00
|
|
|
CALL ext(SB)
|
2026-08-03 08:51:00 +02:00
|
|
|
RET
|
2026-08-13 18:12:22 +02:00
|
|
|
`)
|
|
|
|
|
code, _, relocs, _, _, err := assembleRISCV(fn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("assemble: %v", err)
|
|
|
|
|
}
|
|
|
|
|
// prologue (8) + JAL (4) + epilogue+JALR (8) = 20
|
|
|
|
|
if len(code) != 20 {
|
|
|
|
|
t.Fatalf("expected 20 bytes with CALL sym(SB), got %d", len(code))
|
|
|
|
|
}
|
|
|
|
|
if len(relocs) != 1 {
|
|
|
|
|
t.Fatalf("relocs = %d, want 1", len(relocs))
|
|
|
|
|
}
|
|
|
|
|
r := relocs[0]
|
|
|
|
|
if r.Kind != RelRISCVJal || r.Name != "ext" || r.Off != 8 || r.After != 12 || r.Addend != 0 {
|
|
|
|
|
t.Errorf("reloc = {kind %v off %d after %d name %q addend %d}", r.Kind, r.Off, r.After, r.Name, r.Addend)
|
|
|
|
|
}
|
|
|
|
|
// The JAL instruction itself is JAL X1, 0 at function offset 8.
|
|
|
|
|
wantJAL := wordLE(riscvJType(1, 0))
|
|
|
|
|
if !bytes.Equal(code[8:12], wantJAL) {
|
|
|
|
|
t.Errorf("JAL = % x, want % x", code[8:12], wantJAL)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRISCV_CALL_local_error(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·calllocal(SB), NOSPLIT, $0
|
|
|
|
|
CALL sub
|
2026-08-03 08:51:00 +02:00
|
|
|
sub:
|
|
|
|
|
RET
|
|
|
|
|
`)
|
2026-08-13 18:12:22 +02:00
|
|
|
_, _, _, _, _, err := assembleRISCV(fn)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Error("expected error for CALL to local label, got nil")
|
2026-08-03 08:51:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-09-19 19:17:07 +02:00
|
|
|
|
|
|
|
|
// TestRISCVIndirectBranch pins the indirect branch encodings: JMP (X5) is the
|
|
|
|
|
// toolchain's JALR X0, 0(X5), and the trampoline form JALR rd, offset(rs1)
|
|
|
|
|
// takes its destination from the first operand (regression: the base
|
|
|
|
|
// register was once read as the destination, silently jumping to X0).
|
|
|
|
|
func TestRISCVIndirectBranch(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·f(SB), NOSPLIT, $0-0
|
|
|
|
|
JMP (X5)
|
|
|
|
|
JALR X0, 0(X6)
|
|
|
|
|
JALR X28, 0(X9)
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x00028067, // jalr x0, 5(x0), 0
|
|
|
|
|
0x00030067, // jalr x0, 6(x0), 0
|
|
|
|
|
0x00048e67, // jalr x28, 9(x0), 0
|
|
|
|
|
0x00008067, // jalr x0, 1(x0), 0 (RET)
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-09-19 23:49:13 +02:00
|
|
|
|
|
|
|
|
// encodeOneInstrRISCV encodes a single parsed instruction against a synthetic
|
|
|
|
|
// offsets map, the smallest honest harness for the branch-range diagnostics:
|
|
|
|
|
// the spans are far larger than any source a test would want to spell out.
|
|
|
|
|
func encodeOneInstrRISCV(t *testing.T, src string, pc int, offsets map[string]int) ([]byte, error) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\n"+src)
|
|
|
|
|
instr := fn.Body[0].(*ast.Instr)
|
2026-09-20 14:25:47 +02:00
|
|
|
return encodeRISCVInstr(instr, pc, offsets, riscvFrameInfo{}, nil, nil)
|
2026-09-19 23:49:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestRISCVBranchJumpRange checks that displacements beyond the B-type span
|
|
|
|
|
// [-4096, 4094] and the J-type span [-1048576, 1048574] are diagnosed instead
|
|
|
|
|
// of wrapping silently to a wrong target.
|
|
|
|
|
func TestRISCVBranchJumpRange(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
src string
|
|
|
|
|
off int // the target's function-relative offset (pc 0)
|
|
|
|
|
ok bool
|
|
|
|
|
}{
|
|
|
|
|
{"branch max", "BEQ X10, X11, tgt\nRET\n", 4094, true},
|
|
|
|
|
{"branch past max", "BEQ X10, X11, tgt\nRET\n", 4096, false},
|
|
|
|
|
{"branch back max", "BEQ X10, X11, tgt\nRET\n", -4096, true},
|
|
|
|
|
{"branch back past max", "BEQ X10, X11, tgt\nRET\n", -4098, false},
|
|
|
|
|
{"branchz past max", "BEQZ X10, tgt\nRET\n", 4096, false},
|
|
|
|
|
{"jump max", "JMP tgt\nRET\n", 1048574, true},
|
|
|
|
|
{"jump past max", "JMP tgt\nRET\n", 1048576, false},
|
|
|
|
|
{"jump back max", "JMP tgt\nRET\n", -1048576, true},
|
|
|
|
|
{"jump back past max", "JMP tgt\nRET\n", -1048578, false},
|
|
|
|
|
{"jal past max", "JAL tgt\nRET\n", 1048576, false},
|
|
|
|
|
}
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
t.Run(c.name, func(t *testing.T) {
|
|
|
|
|
_, err := encodeOneInstrRISCV(t, "TEXT ·f(SB), NOSPLIT, $0\n\t"+c.src, 0, map[string]int{"tgt": c.off})
|
|
|
|
|
if c.ok && err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !c.ok && err == nil {
|
|
|
|
|
t.Fatal("expected an out-of-range diagnostic, got none")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-20 14:25:47 +02:00
|
|
|
// TestRISCVBranchFarBody drives the relaxation pass through the full
|
|
|
|
|
// assembler: a forward branch over a body larger than the B-type span is
|
|
|
|
|
// rewritten as an inverted branch over an inserted JMP, the same layout the
|
|
|
|
|
// toolchain produces, instead of wrapping to a wrong target.
|
2026-09-19 23:49:13 +02:00
|
|
|
func TestRISCVBranchFarBody(t *testing.T) {
|
|
|
|
|
var sb strings.Builder
|
|
|
|
|
sb.WriteString("#include \"textflag.h\"\nTEXT ·far(SB), NOSPLIT, $0\n\tBEQ X10, X11, done\n")
|
|
|
|
|
for range 1100 {
|
|
|
|
|
sb.WriteString("\tADD X10, X11, X12\n")
|
|
|
|
|
}
|
|
|
|
|
sb.WriteString("done:\n\tRET\n")
|
|
|
|
|
fn := firstTextRISCV(t, sb.String())
|
2026-09-20 14:25:47 +02:00
|
|
|
out, _, _, _, _, err := assembleRISCV(fn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
// The relaxed branch at offset 0 targets the inserted JMP at 4 (bne
|
|
|
|
|
// x10, x11, +4); the JMP at 4 carries the far forward displacement.
|
|
|
|
|
wantBranch := wordLE(riscvBType(riscvEnc{0x63, 0x1, 0x00}, 10, 11, 4))
|
|
|
|
|
if !bytes.Equal(out[0:4], wantBranch) {
|
|
|
|
|
t.Errorf("relaxed branch = %x, want %x", out[0:4], wantBranch)
|
|
|
|
|
}
|
|
|
|
|
// done sits after 1100 ADDs: 4 + 4400, i.e. offset 4404 from the JMP at 4.
|
|
|
|
|
wantJmp := wordLE(riscvJType(0, 4404))
|
|
|
|
|
if !bytes.Equal(out[4:8], wantJmp) {
|
|
|
|
|
t.Errorf("inserted JMP = %x, want %x", out[4:8], wantJmp)
|
2026-09-19 23:49:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestRISCV_CSRRange checks the CSR address range: the 12-bit field is
|
|
|
|
|
// diagnosed rather than masked, so CSRRW $4096 does not silently address
|
|
|
|
|
// CSR 0.
|
|
|
|
|
func TestRISCV_CSRRange(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·csrhi(SB), NOSPLIT, $0
|
|
|
|
|
CSRRW $4096, X10, X11
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
if _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
|
|
|
|
t.Error("expected an out-of-range error for CSR $4096, got none")
|
|
|
|
|
}
|
|
|
|
|
fn = firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·csrmax(SB), NOSPLIT, $0
|
|
|
|
|
CSRRW $4095, X10, X11
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
if _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
|
|
|
|
t.Errorf("CSR $4095 must assemble: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestRISCV_Imm64Rejected checks that immediates outside the signed 32-bit
|
|
|
|
|
// span are diagnosed instead of silently truncated to their low 32 bits (the
|
|
|
|
|
// toolchain materialises such constants via SLLI expansion, which this
|
|
|
|
|
// assembler does not implement).
|
|
|
|
|
func TestRISCV_Imm64Rejected(t *testing.T) {
|
|
|
|
|
cases := []string{
|
|
|
|
|
"MOV $0x123456789, X10",
|
|
|
|
|
"ADDI $0x100000000, X10, X11",
|
|
|
|
|
"ANDI $-0x800000001, X10, X11",
|
|
|
|
|
"SUB $0x100000000, X10, X11",
|
|
|
|
|
}
|
|
|
|
|
for _, src := range cases {
|
|
|
|
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·wide(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
|
|
|
|
if _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
|
|
|
|
t.Errorf("%s: expected an out-of-range error, got none", src)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// The full signed 32-bit span still assembles, including the SUB form
|
|
|
|
|
// whose negated immediate only just fits.
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·edge(SB), NOSPLIT, $0
|
|
|
|
|
MOV $2147483647, X10
|
|
|
|
|
MOV $-2147483648, X11
|
|
|
|
|
SUB $0x80000000, X12, X13
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
if _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
|
|
|
|
t.Errorf("int32-span immediates must assemble: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-20 06:44:51 +02:00
|
|
|
|
|
|
|
|
// riscvWants decodes code as little-endian words and pins each one; the
|
|
|
|
|
// expected values below were read off GOARCH=riscv64 go tool objdump of
|
|
|
|
|
// kernels assembled with go tool asm (the toolchain's riscv64.s testdata
|
|
|
|
|
// cross-checks the same words).
|
|
|
|
|
func riscvWants(t *testing.T, code []byte, want ...uint32) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
got := make([]uint32, 0, len(code)/4)
|
|
|
|
|
for i := 0; i+4 <= len(code); i += 4 {
|
|
|
|
|
got = append(got, binary.LittleEndian.Uint32(code[i:]))
|
|
|
|
|
}
|
|
|
|
|
if len(got) < len(want) {
|
|
|
|
|
t.Fatalf("word count = %d, want %d\ncode: % x", len(got), len(want), code)
|
|
|
|
|
}
|
|
|
|
|
// The RET (JALR) ends the sequence; only the pinned prefix is compared.
|
|
|
|
|
for i := range want {
|
|
|
|
|
if got[i] != want[i] {
|
|
|
|
|
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// riscvWantsHex pins the exact hex encoding of a function's instruction
|
|
|
|
|
// bytes, including any 2-byte compressed instructions in the stream; the
|
|
|
|
|
// expected strings were read off GOARCH=riscv64 go tool objdump of kernels
|
|
|
|
|
// assembled with go tool asm (the toolchain's riscv64.s testdata
|
|
|
|
|
// cross-checks the same words).
|
|
|
|
|
func riscvWantsHex(t *testing.T, code []byte, wantHex string) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
got := hex.EncodeToString(code)
|
|
|
|
|
if got != wantHex {
|
|
|
|
|
t.Errorf("code = %s, want %s", got, wantHex)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestRISCV_extendedPseudos pins the toolchain-synthesised instructions:
|
|
|
|
|
// ANDN/ORN (XORI + AND/OR through the destination or TMP), the five-word
|
|
|
|
|
// MIN/MAX expansion, the four-word rotate, ROR's compressed reverse shift
|
|
|
|
|
// (C.SLLI when rd == rs1, both non-zero, 1 <= sll <= 63), the identical-
|
|
|
|
|
// input MIN/MAX fold to C.MV, FABSD (FSGNJX.D), SEQZ and RDTIME (csrrs with
|
|
|
|
|
// the time CSR).
|
|
|
|
|
func TestRISCV_extendedPseudos(t *testing.T) {
|
|
|
|
|
t.Run("logic and minmax", func(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·l(SB), NOSPLIT, $0
|
|
|
|
|
ANDN X19, X20, X21
|
|
|
|
|
ANDN X19, X20
|
|
|
|
|
ORN X20, X19
|
|
|
|
|
MAX X26, X28, X29
|
|
|
|
|
MIN X29, X30, X5
|
|
|
|
|
MAX X5, X5
|
|
|
|
|
MAX X5, X5, X6
|
|
|
|
|
SEQZ X5, X6
|
|
|
|
|
NEG X5, X6
|
|
|
|
|
NOT X5
|
|
|
|
|
RDTIME X5
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
|
|
|
|
// Words 0-10 up to the folded C.MV pair (halfwords 96 82 and 16 83),
|
|
|
|
|
// then SEQZ, NEG, NOT and RDTIME.
|
|
|
|
|
riscvWantsHex(t, code,
|
|
|
|
|
"93caf9ffb37a5a01"+"93cff9ff337afa01"+"934ffaffb3e9f901"+
|
|
|
|
|
"b32fae01b30ff041b34eae01b3fedf01b34ede01"+
|
|
|
|
|
"b3afee01b30ff041b342df01b3f25f00b3425f00"+
|
|
|
|
|
"9682"+"1683"+
|
|
|
|
|
"13b31200"+"33035040"+"93c2f2ff"+"f32210c0"+"67800000")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("rotate", func(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·r(SB), NOSPLIT, $0
|
|
|
|
|
ROR X10, X11, X12
|
|
|
|
|
ROR X10, X11
|
|
|
|
|
ROR $63, X11
|
|
|
|
|
RORIW $31, X13, X14
|
|
|
|
|
RORIW $1, X14, X15
|
|
|
|
|
RORIW $3, X14
|
|
|
|
|
RORW X15, X16, X17
|
|
|
|
|
RORW $31, X13
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
|
|
|
|
// The third ROR carries the compressed C.SLLI (05 86) in mid-stream.
|
|
|
|
|
riscvWantsHex(t, code,
|
|
|
|
|
"b30fa040b39ff50133d6a50033e6cf00"+
|
|
|
|
|
"b30fa040b39ff501b3d5a500b3e5bf00"+
|
|
|
|
|
"93dff5038605b3e5bf00"+
|
|
|
|
|
"9bdff6011b97160033e7ef00"+
|
|
|
|
|
"9b5f17009b17f701b3e7ff00"+
|
|
|
|
|
"9b5f37001b17d70133e7ef00"+
|
|
|
|
|
"b30ff040bb1ff801bb58f800b3e81f01"+
|
|
|
|
|
"9bdff6019b961600b3e6df00"+"67800000")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("fp and branches", func(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·f(SB), NOSPLIT, $0
|
|
|
|
|
FABSD F1, F2
|
|
|
|
|
FSGNJD F1, F0, F2
|
|
|
|
|
FMADDD F1, F2, F3, F4
|
|
|
|
|
FMSUBD F1, F2, F3, F4
|
|
|
|
|
FNMSUBD F1, F2, F3, F4
|
|
|
|
|
BGT X5, X6, tgt
|
|
|
|
|
BLE X5, X6, tgt
|
|
|
|
|
BGTU X5, X6, tgt
|
|
|
|
|
BLEU X5, X6, tgt
|
|
|
|
|
tgt:
|
|
|
|
|
RDTIME X5
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
|
|
|
|
riscvWantsHex(t, code,
|
|
|
|
|
"53a11022"+"53011022"+"4382201a4782201a4b82201a"+
|
|
|
|
|
"63485300635653006364530063725300"+ // blt/bge/bltu/bgeu x6, x5
|
|
|
|
|
"f32210c0"+"67800000")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestRISCV_amoWords pins the full AMO family: every AMO carries aq and rl
|
|
|
|
|
// (funct7 |= 3), LR is acquire (funct7 |= 2) and SC release (funct7 |= 1),
|
|
|
|
|
// exactly as GOARCH=riscv64 go tool asm encodes them.
|
|
|
|
|
func TestRISCV_amoWords(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·amo(SB), NOSPLIT, $0
|
|
|
|
|
AMOSWAPW X5, (X6), X7
|
|
|
|
|
AMOSWAPD X5, (X6), X7
|
|
|
|
|
AMOADDW X5, (X6), X7
|
|
|
|
|
AMOADDD X5, (X6), X7
|
|
|
|
|
AMOANDW X5, (X6), X7
|
|
|
|
|
AMOANDD X5, (X6), X7
|
|
|
|
|
AMOORW X5, (X6), X7
|
|
|
|
|
AMOORD X5, (X6), X7
|
|
|
|
|
AMOXORW X5, (X6), X7
|
|
|
|
|
AMOXORD X5, (X6), X7
|
|
|
|
|
AMOMAXW X5, (X6), X7
|
|
|
|
|
AMOMAXD X5, (X6), X7
|
|
|
|
|
AMOMAXUW X5, (X6), X7
|
|
|
|
|
AMOMAXUD X5, (X6), X7
|
|
|
|
|
AMOMINUW X5, (X6), X7
|
|
|
|
|
AMOMINUD X5, (X6), X7
|
|
|
|
|
LRW (X5), X6
|
|
|
|
|
LRD (X5), X6
|
|
|
|
|
SCW X5, (X6), X7
|
|
|
|
|
SCD X5, (X6), X7
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
|
|
|
|
riscvWants(t, code,
|
|
|
|
|
0x0E5323AF, // amoswap.w
|
|
|
|
|
0x0E5333AF, // amoswap.d
|
|
|
|
|
0x065323AF, // amoaddd.w
|
|
|
|
|
0x065333AF, // amoadd.d
|
|
|
|
|
0x665323AF, // amoand.w
|
|
|
|
|
0x665333AF, // amoand.d
|
|
|
|
|
0x465323AF, // amoor.w
|
|
|
|
|
0x465333AF, // amoor.d
|
|
|
|
|
0x265323AF, // amoxor.w
|
|
|
|
|
0x265333AF, // amoxor.d
|
|
|
|
|
0xA65323AF, // amomax.w
|
|
|
|
|
0xA65333AF, // amomax.d
|
|
|
|
|
0xE65323AF, // amomaxu.w
|
|
|
|
|
0xE65333AF, // amomaxu.d
|
|
|
|
|
0xC65323AF, // amominu.w
|
|
|
|
|
0xC65333AF, // amominu.d
|
|
|
|
|
0x1402A32F, // lr.w (aq)
|
|
|
|
|
0x1402B32F, // lr.d
|
|
|
|
|
0x1A5323AF, // sc.w (rl)
|
|
|
|
|
0x1A5333AF, // sc.d
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestRISCV_vectorWords pins the RVV slice and the VSET* encodings. The
|
|
|
|
|
// toolchain canonicalises an immediate avl to vsetivli even under the
|
|
|
|
|
// VSETVLI spelling (`VSETVLI $15` and `VSETIVLI $15` come out byte-
|
|
|
|
|
// identical), which is what the 0xC00 bit of the first word carries.
|
|
|
|
|
func TestRISCV_vectorWords(t *testing.T) {
|
|
|
|
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·v(SB), NOSPLIT, $0
|
|
|
|
|
VSETVLI X5, E8, M8, TA, MA, X6
|
|
|
|
|
VSETIVLI $4, E32, M1, TA, MA, X0
|
|
|
|
|
VSETVLI $15, E32, M1, TA, MA, X12
|
|
|
|
|
VADDVV V1, V2, V3
|
|
|
|
|
VADDVX X12, V12, V12
|
|
|
|
|
VXORVV V8, V16, V24
|
|
|
|
|
VMSEQVX X12, V8, V0
|
|
|
|
|
VMSNEVV V8, V16, V0
|
|
|
|
|
VSLLVI $8, V28, V30
|
|
|
|
|
VSRLVI $25, V29, V29
|
|
|
|
|
VFIRSTM V0, X6
|
|
|
|
|
VIDV V12
|
|
|
|
|
VMV4RV V8, V24
|
|
|
|
|
VLE8V (X10), V8
|
|
|
|
|
VSE8V V24, (X10)
|
|
|
|
|
VSE32V V9, (X11)
|
|
|
|
|
VLSSEG4E32V (X14), X0, V0
|
|
|
|
|
VLSSEG8E32V (X10), X0, V4
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleRISCVHelper(t, fn)
|
|
|
|
|
riscvWants(t, code,
|
|
|
|
|
0x0C32F357, // vsetvli x6, x5, vtype 0xc3 (E8, M8, TA, MA)
|
|
|
|
|
0xCD027057, // vsetivli x0, 4
|
|
|
|
|
0xCD07F657, // vsetivli x12, 15: VSETVLI $15 canonicalises to the same word
|
|
|
|
|
0x022081D7, // vadd.vv v3, v2, v1
|
|
|
|
|
0x02C64657, // vadd.vx v12, v12, x12
|
|
|
|
|
0x2F040C57, // vxor.vv v24, v16, v8
|
|
|
|
|
0x62864057, // vmseq.vx v0, v8, x12
|
|
|
|
|
0x67040057, // vmsne.vv v0, v16, v8
|
|
|
|
|
0x97C43F57, // vsll.vi v30, v28, 8
|
|
|
|
|
0xA3DCBED7, // vsrl.vi v29, v29, 25
|
|
|
|
|
0x4208A357, // vmfirst.m x6, v0
|
|
|
|
|
0x5208A657, // vid.v v12
|
|
|
|
|
0x9E81BC57, // vmv4r.v v24, v8
|
|
|
|
|
0x02050407, // vle8.v v8, (x10)
|
|
|
|
|
0x02050C27, // vse8.v v24, (x10)
|
|
|
|
|
0x0205E4A7, // vse32.v v9, (x11)
|
|
|
|
|
0x6A076007, // vlsseg4e32.v v0, (x14), x0
|
|
|
|
|
0xEA056207, // vlsseg8e32.v v4, (x10), x0
|
|
|
|
|
)
|
|
|
|
|
}
|