// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package asm import ( "os" "path/filepath" "testing" ) // TestRISCVCompressedDifferential proves the explicit compressed-instruction // mnemonics against the toolchain: the "C" extension block of the toolchain's // own testdata (every stack, register, control-transfer, constant-generation, // shift and register-register spelling it carries) assembled by gasm and by // go tool asm must agree halfword for halfword. The lines are the oracle's // own, so a wrong bit pattern, scale or register field names itself through // the first differing halfword. func TestRISCVCompressedDifferential(t *testing.T) { src := `#include "textflag.h" TEXT ·compressed(SB), NOSPLIT, $0 CLWSP 20(SP), X10 CLDSP 24(SP), X10 CFLDSP 32(SP), F10 CSWSP X10, 20(SP) CSDSP X10, 24(SP) CFSDSP F10, 32(SP) CLW 20(X10), X11 CLD 24(X10), X11 CFLD 32(X10), F11 CSW X11, 20(X10) CSD X11, 24(X10) CFSD F11, 32(X10) CJ 1(PC) CJR X5 CJALR X5 CBEQZ X10, 1(PC) CBNEZ X10, 1(PC) CLI $-32, X5 CLI $31, X5 CLUI $-32, X5 CLUI $31, X5 CADD $-32, X5 CADD $31, X5 CADDI $-32, X5 CADDI $31, X5 CADDW $-32, X5 CADDW $31, X5 CADDIW $-32, X5 CADDIW $31, X5 CADDI16SP $-512, SP CADDI16SP $496, SP CADDI4SPN $4, SP, X10 CADDI4SPN $1020, SP, X10 CSLLI $63, X5 CSRLI $63, X10 CSRAI $63, X10 CAND $-32, X10 CAND $31, X10 CANDI $-32, X10 CANDI $31, X10 CMV X6, X5 CADD X9, X8 CAND X9, X8 COR X9, X8 CXOR X9, X8 CSUB X9, X8 CADDW X9, X8 CSUBW X9, X8 CNOP CEBREAK RET ` dir := t.TempDir() path := filepath.Join(dir, "compressed_riscv64.s") if err := os.WriteFile(path, []byte(src), 0o644); err != nil { t.Fatal(err) } assertRISCVDifferential(t, path, src, "compressed") } // TestRISCVCompressedRange pins the compressed immediate and offset ranges at // the toolchain's own boundaries: a stack load off the scale or range, a // non-prime register in the CL/CS and CA shapes, a zero immediate where the // toolchain forbids one and a CLUI into SP are all rejected on sight. func TestRISCVCompressedRange(t *testing.T) { asmOne := func(t *testing.T, stmt string) error { t.Helper() fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·c(SB), NOSPLIT, $0\n\t"+stmt+"\n\tRET\n") _, _, _, _, _, _, err := assembleRISCV(fn, nil) return err } for _, s := range []string{ "CLWSP $0(SP), X10", // never spelled; the parser rejects the shape } { if err := asmOne(t, s); err == nil { t.Errorf("%s must be rejected", s) } } for _, s := range []string{ "CLWSP 21(SP), X10", // not a multiple of 4 "CLWSP 256(SP), X10", // out of range "CLDSP 25(SP), X10", // not a multiple of 8 "CFLDSP 33(SP), F10", // not a multiple of 8 "CLWSP 20(X10), X10", // base must be SP "CLW 22(X10), X11", // not a multiple of 4 "CLW 128(X10), X11", // out of range "CLW 20(X5), X11", // base must be prime "CLW 20(X10), X5", // rd must be prime "CLI $32, X5", // out of range "CLI $-33, X5", // out of range "CLUI $0, X5", // zero "CLUI $3, X2", // SP as destination "CSLLI $0, X5", // zero shift "CSLLI $64, X5", // out of range "CSRLI $63, X5", // rd must be prime "CANDI $63, X10", // out of range "CMV X0, X5", // X0 in rd "CMV X5, X0", // X0 in rs2 "CADD X5, X0", // X0 in rs2 "CSUB X5, X5", // X0-free but rd prime required "CADDI4SPN $4, X5, X10", /* base must be SP */ } { if err := asmOne(t, s); err == nil { t.Errorf("%s must be rejected, as go tool asm rejects it", s) } } for _, s := range []string{ "CLWSP 20(SP), X10", "CLDSP 24(SP), X10", "CLW 20(X10), X11", "CSD X11, 24(X10)", "CLI $-32, X5", "CLUI $-32, X5", "CADD $-32, X5", "CADDIW $31, X5", "CSLLI $63, X5", "CSRLI $63, X10", "CANDI $-32, X10", "CMV X6, X5", "CADD X9, X8", "CSUB X9, X8", "CADDW X9, X8", "CADDI16SP $496, SP", "CADDI4SPN $1020, SP, X10", } { if err := asmOne(t, s); err != nil { t.Errorf("%s must assemble: %v", s, err) } } }