Files
gasm-sdk/asm/riscv_encode_test.go
T

1343 lines
37 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package asm
import (
"bytes"
"encoding/binary"
"encoding/hex"
"strings"
"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 = 5*4 + 4 = 24
if len(code) != 24 {
t.Errorf("expected 24 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)
// Four register-relative loads/stores compress (2B each) + JALR (4B) = 12.
if len(code) != 12 {
t.Errorf("expected 12 bytes, got %d", len(code))
}
}
func TestRISCV_immediate(t *testing.T) {
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·imm(SB), NOSPLIT, $0
ADDI $42, X10, X11
ANDI $0xFF, X11, X12
ORI $1, X12, X13
XORI $0, X13, X14
RET
`)
code := assembleRISCVHelper(t, fn)
// 4 I-type + JALR = 4*4 + 4 = 20
if len(code) != 20 {
t.Errorf("expected 20 bytes, got %d", len(code))
}
}
func TestRISCV_branches(t *testing.T) {
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·br(SB), NOSPLIT, $0
ADDI $1, X10, X10
loop:
BEQ X10, X11, done
ADDI $1, X10, 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, but not C.LI's 6-bit immediate).
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·small(SB), NOSPLIT, $0
MOV $42, X10
RET
`)
code := assembleRISCVHelper(t, fn)
// ADDI (4B) + JALR (4B) = 8
if len(code) != 8 {
t.Errorf("expected 8 bytes, got %d", len(code))
}
}
func TestRISCV_MOV_imm_large(t *testing.T) {
// MOV $0x12345, rd → C.LUI $18 (2B) + ADDIW $837 (4B).
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·large(SB), NOSPLIT, $0
MOV $0x12345, X10
RET
`)
code := assembleRISCVHelper(t, fn)
// C.LUI (2B) + ADDIW (4B) + JALR (4B) = 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) + JALR (4B) = 6
if len(code) != 6 {
t.Errorf("expected 6 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) + JALR (4B) = 8
if len(code) != 8 {
t.Errorf("expected 8 bytes, got %d", len(code))
}
}
func TestRISCV_RVC_loadStore(t *testing.T) {
// Verify that loads/stores from SP are compressed.
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·rvcstore(SB), NOSPLIT, $0
LD 0(SP), X10
SD X10, 8(SP)
RET
`)
code := assembleRISCVHelper(t, fn)
// C.LDSP (2B) + C.SDSP (2B) + JALR (4B) = 8
if len(code) != 8 {
t.Errorf("expected 8 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) + JALR (4B) = 16
if len(code) != 16 {
t.Errorf("expected 16 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) + JALR (4B) = 24
if len(code) != 24 {
t.Errorf("expected 24 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) + JALR (4B) = 16
if len(code) != 16 {
t.Errorf("expected 16 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) + JALR (4B) = 20
if len(code) != 20 {
t.Errorf("expected 20 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) + JALR (4B) = 20
if len(code) != 20 {
t.Errorf("expected 20 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) + JALR (4B) = 16
if len(code) != 16 {
t.Errorf("expected 16 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 $1, X10, X10
BEQ X10, X11, done
ADDI $1, X10, 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 $5, X10, X10
RET
`)
code := assembleRISCVHelper(t, fn)
// C.ADDI (2B) + JALR (4B) = 6
if len(code) != 6 {
t.Errorf("expected 6 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 $7, X0, X10
RET
`)
code := assembleRISCVHelper(t, fn)
// C.LI (2B) + JALR (4B) = 6
if len(code) != 6 {
t.Errorf("expected 6 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) + JALR (4B) = 6
if len(code) != 6 {
t.Errorf("expected 6 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) + JALR(4) = 6
if img.Funcs[0].Size != 6 {
t.Errorf("add: expected 6 bytes, got %d", img.Funcs[0].Size)
}
// 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)
}
}
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", 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},
{"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},
{"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", 6},
{"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},
}
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))
}
})
}
}
func TestRISCV_RVC_branch(t *testing.T) {
// Branches are never RVC-compressed (no C.BEQZ/C.BNEZ), matching go tool asm.
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·cbeqz(SB), NOSPLIT, $0
ADDI $1, X10, X10
BEQ X10, X0, done
ADDI $1, X10, X10
done:
RET
`)
code := assembleRISCVHelper(t, fn)
// 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))
}
}
func TestRISCV_RVC_CJ(t *testing.T) {
// JMP target → JAL X0 (never compressed to C.J), matching go tool asm.
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·cj(SB), NOSPLIT, $0
JMP done
done:
RET
`)
code := assembleRISCVHelper(t, fn)
// JAL(4) + JALR(4) = 8
if len(code) != 8 {
t.Errorf("expected 8 bytes with uncompressed JMP, got %d", len(code))
}
}
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)
// C.ADD(2) + JALR(4) = 6
if len(code) != 6 {
t.Errorf("expected 6 bytes with C.ADD, got %d", len(code))
}
}
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)
// C.ADD(2) + JALR(4) = 6
if len(code) != 6 {
t.Errorf("expected 6 bytes with C.ADD (commuted), got %d", len(code))
}
}
func TestRISCV_RVC_CSUB(t *testing.T) {
// SUB rs2, rs1, rd → C.SUB when rd == rs1 and both in prime regs.
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·csub(SB), NOSPLIT, $0
SUB X11, X10, X10
RET
`)
code := assembleRISCVHelper(t, fn)
// 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)
}
}
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)
if len(code) != 6 {
t.Errorf("expected 6 bytes with C.XOR, got %d", len(code))
}
}
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)
if len(code) != 6 {
t.Errorf("expected 6 bytes with C.OR, got %d", len(code))
}
}
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)
if len(code) != 6 {
t.Errorf("expected 6 bytes with C.AND, got %d", len(code))
}
}
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)
// C.FLDSP(2) + JALR(4) = 6
if len(code) != 6 {
t.Errorf("expected 6 bytes with C.FLDSP, got %d", len(code))
}
}
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)
// C.FSDSP(2) + JALR(4) = 6
if len(code) != 6 {
t.Errorf("expected 6 bytes with C.FSDSP, got %d", len(code))
}
}
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)
}
// AUIPC(4) + ADDI(4) + JALR(4) = 12
if img.Funcs[0].Size != 12 {
t.Errorf("expected 12 bytes, got %d", img.Funcs[0].Size)
}
}
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)
}
// 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)
}
}
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)
}
// AUIPC(4) + LD(4) + JALR(4) = 12
if img.Funcs[0].Size != 12 {
t.Errorf("expected 12 bytes, got %d", img.Funcs[0].Size)
}
}
// 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)
}
}
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)
// 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)
}
}
func TestRISCV_MOV_sym_FP(t *testing.T) {
// MOV $sym(FP), rd lowers to the frame-adjusted ADDI against SP: the
// toolchain's argframe spelling. A zero frame leaves the offset at the
// 8-byte link slot, compressed to C.ADDI4SPN.
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·argfp(SB), NOSPLIT, $0
MOV $arg(FP), X10
RET
`)
code, _, _, _, _, _, err := assembleRISCV(fn)
if err != nil {
t.Fatalf("assemble: %v", err)
}
// prologue (0: leaf, zero frame) + C.ADDI4SPN (2) + RET (4) = 6
want := []byte{0x28, 0x00, 0x67, 0x80, 0x00, 0x00}
if string(code) != string(want) {
t.Errorf("got % x, want % x", code, want)
}
}
func TestRISCV_Bookkeeping(t *testing.T) {
// FUNCDATA and PCDATA contribute no bytes; UNDEF is the toolchain's
// ebreak, compressed to C.EBREAK under RVC.
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·book(SB), NOSPLIT, $0-8
FUNCDATA $0, marks<>(SB)
PCDATA $1, $1
UNDEF
MOV $1, X10
MOV X10, ret+0(FP)
RET
`)
code, _, _, _, _, _, err := assembleRISCV(fn)
if err != nil {
t.Fatalf("assemble: %v", err)
}
// C.EBREAK (2) + C.LI X10, 1 (2) + C.SWSP (2) + RET (4) = 10: the
// FUNCDATA and PCDATA statements contribute nothing.
want := []byte{0x02, 0x90, 0x05, 0x45, 0x2a, 0xe4, 0x67, 0x80, 0x00, 0x00}
if string(code) != string(want) {
t.Errorf("got % x, want % x", code, want)
}
}
func TestRISCV_JMPPCRel(t *testing.T) {
// JMP N(PC): the displacement tracks the instruction N source slots
// away in the final layout (0 the jump itself, negative backwards).
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·slots(SB), NOSPLIT, $0-0
JMP 2(PC)
MOV $1, X11
MOV $2, X12
MOV X12, X11
JMP -3(PC)
RET
`)
code, _, _, _, _, _, err := assembleRISCV(fn)
if err != nil {
t.Fatalf("assemble: %v", err)
}
// JMP 2(PC) lands on the C.MV six bytes ahead; JMP -3(PC) lands back on
// the first C.LI, six bytes behind.
want := []byte{
0x6f, 0x00, 0x60, 0x00, // JAL X0, 6
0x85, 0x45, // C.LI X11, 1
0x09, 0x46, // C.LI X12, 2
0xb2, 0x85, // C.MV X11, X12
0x6f, 0xf0, 0xbf, 0xff, // JAL X0, -6
0x67, 0x80, 0x00, 0x00, // RET
}
if string(code) != string(want) {
t.Errorf("got % x, want % x", code, want)
}
}
func TestRISCV_MOVWideImm(t *testing.T) {
// Shift-sequence constants compress like the toolchain's expansion.
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·wide(SB), NOSPLIT, $0-0
MOV $0x8000000000000000, X5
MOV $0x100000000, X5
MOV $0x000fffffffffffda, X5
RET
`)
code, _, _, _, _, _, err := assembleRISCV(fn)
if err != nil {
t.Fatalf("assemble: %v", err)
}
// C.LI -1, C.SLLI 63; C.LI 1, C.SLLI 32; C.LI -19, C.SLLI 13, SRLI 12.
want := []byte{
0xfd, 0x52, 0xfe, 0x12,
0x85, 0x42, 0x82, 0x12,
0xb5, 0x52, 0xb6, 0x02, 0x93, 0xd2, 0xc2, 0x00,
0x67, 0x80, 0x00, 0x00,
}
if string(code) != string(want) {
t.Errorf("got % x, want % x", code, want)
}
}
func TestRISCV_MOVImmPool(t *testing.T) {
// A constant outside the shift shapes loads from the pooled $i64 data
// symbol via AUIPC+LD, named like the toolchain's pool.
src := `#include "textflag.h"
TEXT ·pool(SB), NOSPLIT, $0-8
MOV $0x0101010101010101, X16
MOV X16, ret+0(FP)
RET
`
f, errs := parser.Parse("pool_riscv64.s", src)
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
img, err := AssembleFileRISCV(f)
if err != nil {
t.Fatalf("AssembleFileRISCV: %v", err)
}
// AUIPC X16, 0 + LD X16, 0(X16): the relocation pair carries the symbol.
wantCode := []byte{0x17, 0x08, 0x00, 0x00, 0x03, 0x38, 0x08, 0x00}
if string(img.Code[0:8]) != string(wantCode) {
t.Errorf("pool load: got % x", img.Code[0:8])
}
var lit *DataSymbol
for i := range img.DataSyms {
if img.DataSyms[i].Name == "$i64.0101010101010101" {
lit = &img.DataSyms[i]
}
}
if lit == nil {
t.Fatalf("pool symbol missing: %v", img.DataSyms)
}
wantData := []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}
if string(img.Data[lit.Offset:lit.Offset+8]) != string(wantData) {
t.Errorf("pool bytes: got % x", img.Data[lit.Offset:lit.Offset+8])
}
}
func TestRISCV_CALL(t *testing.T) {
// CALL sym(SB) → JAL X1, sym(SB) with a single R_RISCV_JAL relocation.
fn := firstTextRISCV(t, `#include "textflag.h"
TEXT ·calltest(SB), NOSPLIT, $0
CALL ext(SB)
RET
`)
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
sub:
RET
`)
_, _, _, _, _, _, err := assembleRISCV(fn)
if err == nil {
t.Error("expected error for CALL to local label, got nil")
}
}
// 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)
)
}
// 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)
return encodeRISCVInstr(instr, pc, offsets, riscvFrameInfo{}, nil, nil, nil)
}
// 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")
}
})
}
}
// 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.
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())
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)
}
}
// 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 for
// the I-type arithmetic; the MOV forms materialise the wide constant instead
// (shift sequence or pooled load), like the toolchain.
func TestRISCV_Imm64Rejected(t *testing.T) {
cases := []string{
"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)
}
// Beyond the span the MOV forms materialise the constant like the
// toolchain instead of diagnosing it.
fn = firstTextRISCV(t, `#include "textflag.h"
TEXT ·pool(SB), NOSPLIT, $0
MOV $0x123456789, X10
RET
`)
if _, _, _, _, _, _, err := assembleRISCV(fn); err != nil {
t.Errorf("MOV with a 64-bit immediate must assemble: %v", err)
}
}
// 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
)
}