feat(asm): encode the explicit riscv64 compressed instructions
The C extension's own spellings were names the table carried and the encoder refused: CLWSP stopped the corpus audit's riscv64 file first. Thirty-eight mnemonics now encode directly to their halfword, with the toolchain's operand spellings and validation: the stack loads and stores pin their base to SP, the register-based loads, stores and arithmetic carry prime registers, CLUI refuses zero and SP, CADDI4SPN scales by four, CADDI16SP by sixteen, and CJ, CBEQZ and CBNEZ resolve their N(PC) targets against the final layout, taking a two-byte placeholder in the early passes so the offsets stay honest. CAND with an immediate is the toolchain's C.ANDI spelling. The toolchain's whole C extension testdata block is a differential test, halfword for halfword, beside a range test at the toolchain's own boundaries. Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
ddfa33ccc1
commit
e43fa39dc9
2 files changed
+716
-8
No files matched your search
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user