// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package asm import ( "bytes" "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) } } 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_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 `) _, _, _, _, _, err := assembleRISCV(fn) if err == nil { t.Error("expected error for MOV $arg(FP), got nil") } } 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") } }