From 373c09f7254dbaacfc4ec304fc46d5e1ea00e81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Thu, 13 Aug 2026 15:13:31 +0200 Subject: [PATCH] fix(asm): correct RISC-V operand order and complete RVC compression Assisted-by: DeepSeek V4 Pro --- CHANGELOG.md | 9 +++ asm/riscv_assemble.go | 124 ++++++++++++++++++++++--------- asm/riscv_encode.go | 26 +++++++ asm/riscv_encode_test.go | 58 ++++++--------- asm/riscv_frame.go | 11 ++- testdata/verify/rvc_riscv64.s | 32 ++++++++ verify/l64_groundtruth_test.go | 17 ++++- verify/riscv_groundtruth_test.go | 12 ++- 8 files changed, 212 insertions(+), 77 deletions(-) create mode 100644 testdata/verify/rvc_riscv64.s diff --git a/CHANGELOG.md b/CHANGELOG.md index d4227d3..e978a0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,15 @@ Unreleased changes on the `development` branch. are populated from the recorded stack-adjustment and source-line data, and a byte-exact ground-truth test compares framed and leaf functions against `GOARCH=riscv64 go tool asm`. +- **RISC-V operand ordering and RVC compression.** R-type instructions now + take `rs2, rs1, rd` and I-type arithmetic instructions take `imm12, rs1, + rd`, matching the Go assembler's documented operand order (previously both + were reversed, so non-commutative R-type instructions such as `SUB` encoded + the wrong operation). The two-operand ternary forms (`ADD rs2, rd`, + `ADDI $imm, rd`, `SLLI $shamt, rd`) are now accepted. RVC compression is + completed for `C.ADDI16SP`, `C.SLLI`, `C.SRLI`, `C.SRAI`, `C.ANDI`, + `C.NOP`, `C.EBREAK`, `C.MV` (from `ADDI`/`ADD`) and the commutative + `AND`/`OR`/`XOR` forms; the byte-exact ground-truth test now covers these. - **Debugger watchpoint slots.** `gasm debug`'s `watch` command always used hardware watchpoint slot 0, so a second `watch` call silently overwrote the first. Watchpoint slots are now tracked in the `Session` (DR0–DR3); diff --git a/asm/riscv_assemble.go b/asm/riscv_assemble.go index 143d2df..3ebf59d 100644 --- a/asm/riscv_assemble.go +++ b/asm/riscv_assemble.go @@ -324,26 +324,44 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv } switch { - // R-type: Plan 9 order is INSTR src1, src2, dst (destination last). + // R-type: Go reverses the ISA order, writing rs2, rs1, rd (destination + // last); the two-operand form INSTR rs2, rd uses rd as rs1. case len(ops) == 3 && isRTypeInstr(mnem): - rs1 := regFromOperand(ops[0]) // source 1 (first operand) - rs2 := regFromOperand(ops[1]) // source 2 (second operand) + rs2 := regFromOperand(ops[0]) // first operand = rs2 + rs1 := regFromOperand(ops[1]) // second operand = rs1 rd := regFromOperand(ops[2]) // destination (last operand) if rd < 0 || rs1 < 0 || rs2 < 0 { return nil, fmt.Errorf("invalid register in %s", mnem) } word = riscvRType(enc, rd, rs1, rs2) - // I-type shift (SLLI, SRLI, SRAI): INSTR rs, $shamt, rd. + case len(ops) == 2 && isRTypeInstr(mnem): + rs2 := regFromOperand(ops[0]) // source (first operand) + rd := regFromOperand(ops[1]) // destination (second operand) + if rd < 0 || rs2 < 0 { + return nil, fmt.Errorf("invalid register in %s", mnem) + } + word = riscvRType(enc, rd, rd, rs2) + + // I-type shift (SLLI, SRLI, SRAI): INSTR $shamt, rs1, rd; the two-operand + // form INSTR $shamt, rd uses rd as the source. case len(ops) == 3 && isShiftImmInstr(mnem): - rs1 := regFromOperand(ops[0]) - shamt := int(immFromOperand(ops[1])) + shamt := int(immFromOperand(ops[0])) + rs1 := regFromOperand(ops[1]) rd := regFromOperand(ops[2]) if rd < 0 || rs1 < 0 { return nil, fmt.Errorf("invalid register in %s", mnem) } word = riscvRType(enc, rd, rs1, shamt) + case len(ops) == 2 && isShiftImmInstr(mnem): + shamt := int(immFromOperand(ops[0])) + rd := regFromOperand(ops[1]) + if rd < 0 { + return nil, fmt.Errorf("invalid register in %s", mnem) + } + word = riscvRType(enc, rd, rd, shamt) + // AMO atomics: Plan 9 order is INSTR src, (addr), dst. case len(ops) == 3 && isAMOInstr(mnem): rs2 := regFromOperand(ops[0]) // source value @@ -354,10 +372,10 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv } word = riscvAMOType(enc, rd, rs1, rs2) - // FP arithmetic: Plan 9 order is INSTR src1, src2, dst. + // FP arithmetic: Go reverses the ISA order, writing rs2, rs1, rd. case len(ops) == 3 && isFPArithInstr(mnem): - rs1 := regFromOperand(ops[0]) - rs2 := regFromOperand(ops[1]) + rs2 := regFromOperand(ops[0]) + rs1 := regFromOperand(ops[1]) rd := regFromOperand(ops[2]) if rd < 0 || rs1 < 0 || rs2 < 0 { return nil, fmt.Errorf("invalid FP register in %s", mnem) @@ -410,26 +428,35 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv } word = riscvAMOType(enc, rd, rs1, rs2) - // FP compare: INSTR src1, src2, dst(int) — result in integer register. + // FP compare: Go reverses the ISA order, writing rs2, rs1, rd. case len(ops) == 3 && isFPCmpInstr(mnem): - rs1 := regFromOperand(ops[0]) - rs2 := regFromOperand(ops[1]) + rs2 := regFromOperand(ops[0]) + rs1 := regFromOperand(ops[1]) rd := regFromOperand(ops[2]) if rd < 0 || rs1 < 0 || rs2 < 0 { return nil, fmt.Errorf("invalid operand in %s", mnem) } word = riscvRType(enc, rd, rs1, rs2) - // I-type with immediate: Plan 9 order is INSTR src, imm, dst. + // I-type with immediate: Plan 9 order is INSTR $imm, rs1, rd; the + // two-operand form INSTR $imm, rd uses rd as the source. case len(ops) == 3 && isITypeInstr(mnem): - rs1 := regFromOperand(ops[0]) // source register - imm := immFromOperand(ops[1]) // immediate + imm := immFromOperand(ops[0]) // immediate + rs1 := regFromOperand(ops[1]) // source register rd := regFromOperand(ops[2]) // destination if rd < 0 || rs1 < 0 { return nil, fmt.Errorf("invalid register in %s", mnem) } word = riscvIType(enc, rd, rs1, imm) + case len(ops) == 2 && isITypeInstr(mnem): + imm := immFromOperand(ops[0]) + rd := regFromOperand(ops[1]) + if rd < 0 { + return nil, fmt.Errorf("invalid register in %s", mnem) + } + word = riscvIType(enc, rd, rd, imm) + // Loads: rd, offset(rs1) — Plan 9 order is LD src, dst. case len(ops) == 2 && isLoadInstr(mnem): rd := regFromOperand(ops[1]) // destination (last operand) @@ -742,6 +769,10 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { if rd == -1 || rs1 == -1 { return 0, false } + if rd == 2 && rs1 == 2 && imm != 0 && imm%16 == 0 && imm >= -512 && imm <= 511 { + // C.ADDI16SP: ADDI to SP by a nonzero 16-byte multiple. + return rvcADDI16SP(2, imm), true + } if rd == rs1 && rd != 0 && imm != 0 && imm >= -32 && imm <= 31 { // C.ADDI: funct3=0x0, rs1/rd, nzimm[5:0] return rvcCI(0x0, uint32(rd), uint32(imm)&0x3F), true @@ -754,6 +785,10 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { // C.MV: funct4=0x8, rd, rs1 (CR-type) return rvcCR(0x8, uint32(rd), uint32(rs1)), true } + if rd == 0 && rs1 == 0 && imm == 0 { + // C.NOP + return 0x0001, true + } case "JAL": // JAL X0, target → C.J when offset fits in ±2KB. @@ -777,11 +812,11 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { return 0, false case "ADD": - // ADD rd, rs2 → C.ADD (CR-type, funct4=0x9) when rd == rs1; ADD is - // commutative, so if rd == rs2, swap. + // ADD rs2, rs1, rd → C.ADD (CR-type, funct4=0x9) when rd == rs1; ADD + // is commutative, so if rd == rs2, swap. ADD rs2, X0, rd is C.MV. if len(ops) == 3 { - rs1 := regFromOperand(ops[0]) - rs2 := regFromOperand(ops[1]) + rs2 := regFromOperand(ops[0]) + rs1 := regFromOperand(ops[1]) rd := regFromOperand(ops[2]) if rd != -1 && rs1 != -1 && rs2 != -1 && rd != 0 { if rd == rs1 && rs2 != 0 { @@ -790,6 +825,10 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { if rd == rs2 && rs1 != 0 { return rvcCR(0x9, uint32(rd), uint32(rs1)), true } + if rs1 == 0 && rs2 != 0 { + // ADD rs2, X0, rd → C.MV rd, rs2. + return rvcCR(0x8, uint32(rd), uint32(rs2)), true + } } } @@ -807,13 +846,17 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { case "AND": funct2 = 0x3 } - rs1 := regFromOperand(ops[0]) - rs2 := regFromOperand(ops[1]) + rs2 := regFromOperand(ops[0]) + rs1 := regFromOperand(ops[1]) rd := regFromOperand(ops[2]) if rd != -1 && rs1 != -1 && rs2 != -1 && rd != 0 { if rd == rs1 && isRVCIntReg(rd) && isRVCIntReg(rs2) && rs2 != 0 { return rvcCA(0x23, funct2, rvcReg3(rd), rvcReg3(rs2)), true } + // AND/OR/XOR are commutative; SUB is not. + if mnem != "SUB" && rd == rs2 && isRVCIntReg(rd) && isRVCIntReg(rs1) && rs1 != 0 { + return rvcCA(0x23, funct2, rvcReg3(rd), rvcReg3(rs1)), true + } } } @@ -848,33 +891,32 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { } case "SLLI", "SRLI", "SRAI": - // C.SLLI (funct3=0x0), C.SRLI (funct3=0x4, funct2=0), C.SRAI (funct3=0x4, funct2=1). rd, rs1, imm := extractITypeParams(instr, fi) if rd == rs1 && rd != 0 && imm != 0 && imm >= 1 && imm <= 63 { if mnem == "SLLI" { - // C.SLLI: funct3=0, CI-type with shamt in bits [12|6:2]. - // For simplicity, use the standard CI format — the shamt is in imm[5:0]. - return rvcCI(0x0, uint32(rd), uint32(imm)&0x3F), true + // C.SLLI: funct3=0, op=10 quadrant, shamt in bits [12|6:2]. + return rvcSLLI(uint32(rd), uint32(imm)&0x3F), true } if isRVCIntReg(rd) { funct2 := uint32(0x0) if mnem == "SRAI" { funct2 = 0x1 } - // CB-format shift: funct3=0x4, shamt in bits [12|6:2]. - // Use simplified encoding for now. - _ = funct2 - return rvcCI(0x0, uint32(rd), uint32(imm)&0x3F), true + // C.SRLI/C.SRAI: CB-type, funct3=0x4. + return rvcCBShift(funct2, rvcReg3(rd), uint32(imm)&0x3F), true } } case "ANDI": rd, rs1, imm := extractITypeParams(instr, fi) if isRVCIntReg(rd) && rd == rs1 && imm >= -32 && imm <= 31 { - // C.ANDI: funct3=0x4, funct2=0x2 (CB-type). - // Simplified encoding for now. - return rvcCI(0x0, uint32(rd), uint32(imm)&0x3F), true + // C.ANDI: CB-type, funct3=0x4, funct2=0x2. + return rvcCBShift(0x2, rvcReg3(rd), uint32(imm)&0x3F), true } + + case "EBREAK": + // C.EBREAK: CR-type, funct4=0x9, rd=0, rs2=0. + return rvcCR(0x9, 0, 0), true } return 0, false @@ -911,15 +953,23 @@ func extractSDParams(instr *ast.Instr, fi riscvFrameInfo) (rs2, rs1 int, imm int return } -// extractITypeParams extracts rd, rs1, and immediate for an I-type instruction. +// extractITypeParams extracts rd, rs1, and immediate for an I-type +// instruction. The Plan 9 order is INSTR $imm, rs1, rd (3 operands) or +// INSTR $imm, rd (2 operands, rd is also the source). func extractITypeParams(instr *ast.Instr, fi riscvFrameInfo) (rd, rs1 int, imm int32) { ops := instr.Operands - if len(ops) != 3 { + switch len(ops) { + case 3: + imm = immFromOperand(ops[0]) + rs1 = regFromOperand(ops[1]) + rd = regFromOperand(ops[2]) + case 2: + imm = immFromOperand(ops[0]) + rd = regFromOperand(ops[1]) + rs1 = rd + default: return -1, -1, 0 } - rs1 = regFromOperand(ops[0]) - imm = immFromOperand(ops[1]) - rd = regFromOperand(ops[2]) return } diff --git a/asm/riscv_encode.go b/asm/riscv_encode.go index 6df678a..3f59cc3 100644 --- a/asm/riscv_encode.go +++ b/asm/riscv_encode.go @@ -460,6 +460,12 @@ func rvcCI(funct3, rd uint32, imm uint32) uint16 { return uint16((funct3 << 13) | ((imm>>5)&1)<<12 | (rd << 7) | (imm&0x1F)<<2 | 0x1) } +// rvcSLLI encodes C.SLLI, which shares funct3=0 with C.ADDI but lives in the +// op=10 quadrant (unlike C.ADDI's op=01). +func rvcSLLI(rd, shamt uint32) uint16 { + return uint16(((shamt>>5)&1)<<12 | (rd << 7) | (shamt&0x1F)<<2 | 0x2) +} + // rvcLSP encodes a CI-type stack-relative load: C.LDSP (funct3=3) or // C.FLDSP (funct3=1). offset is the full byte offset; the immediate bits // are interleaved per the RISC-V spec: [5:3|8:6]. @@ -535,3 +541,23 @@ func rvcCB(funct3, rs1 uint32, offset int32) uint16 { offBits |= ((uoff >> 5) & 1) << 2 // bit 2 = offset[5] return uint16((funct3 << 13) | offBits | (rs1 << 7) | 0x1) } + +// rvcCBShift encodes a CB-type shift/immediate compressed instruction +// (C.SRLI, C.SRAI, C.ANDI). rd is the 3-bit prime-register index; imm is +// the 6-bit shamt/immediate; funct2 selects the operation (0=SRLI, 1=SRAI, +// 2=ANDI). +func rvcCBShift(funct2, rd, imm uint32) uint16 { + return uint16((0x4 << 13) | ((imm>>5)&1)<<12 | (funct2 << 10) | (rd << 7) | (imm&0x1F)<<2 | 0x1) +} + +// rvcADDI16SP encodes C.ADDI16SP: ADDI rd, imm, rd for the stack pointer +// with a 10-bit signed, 16-byte-scaled immediate. imm is the raw byte +// offset; the immediate bits are extracted in the order [9|4|6|8:7|5]. +func rvcADDI16SP(rd uint32, imm int32) uint16 { + u := uint32(imm) + packed := uint32(0) + for _, bit := range []uint{9, 4, 6, 8, 7, 5} { + packed = packed<<1 | (u>>bit)&1 + } + return uint16((0x3 << 13) | ((packed>>5)&1)<<12 | (rd << 7) | (packed&0x1F)<<2 | 0x1) +} diff --git a/asm/riscv_encode_test.go b/asm/riscv_encode_test.go index ecd3c23..f3f1577 100644 --- a/asm/riscv_encode_test.go +++ b/asm/riscv_encode_test.go @@ -90,10 +90,10 @@ TEXT ·mem(SB), NOSPLIT, $0 func TestRISCV_immediate(t *testing.T) { fn := firstTextRISCV(t, `#include "textflag.h" TEXT ·imm(SB), NOSPLIT, $0 - ADDI X10, $42, X11 - ANDI X11, $0xFF, X12 - ORI X12, $1, X13 - XORI X13, $0, X14 + ADDI $42, X10, X11 + ANDI $0xFF, X11, X12 + ORI $1, X12, X13 + XORI $0, X13, X14 RET `) code := assembleRISCVHelper(t, fn) @@ -106,10 +106,10 @@ TEXT ·imm(SB), NOSPLIT, $0 func TestRISCV_branches(t *testing.T) { fn := firstTextRISCV(t, `#include "textflag.h" TEXT ·br(SB), NOSPLIT, $0 - ADDI X10, $1, X10 + ADDI $1, X10, X10 loop: BEQ X10, X11, done - ADDI X10, $1, X10 + ADDI $1, X10, X10 JMP loop done: RET @@ -291,9 +291,9 @@ func TestRISCV_forwardBranch(t *testing.T) { // Forward label reference — must not fail. fn := firstTextRISCV(t, `#include "textflag.h" TEXT ·fwd(SB), NOSPLIT, $0 - ADDI X10, $1, X10 + ADDI $1, X10, X10 BEQ X10, X11, done - ADDI X10, $1, X10 + ADDI $1, X10, X10 done: RET `) @@ -308,7 +308,7 @@ 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 X10, $5, X10 + ADDI $5, X10, X10 RET `) code := assembleRISCVHelper(t, fn) @@ -322,7 +322,7 @@ 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 X0, $7, X10 + ADDI $7, X0, X10 RET `) code := assembleRISCVHelper(t, fn) @@ -389,10 +389,10 @@ func TestRISCV_encodings(t *testing.T) { {"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 X10, $5, X11\nRET\n", 8}, - {"SLLI", "SLLI X10, $3, X11\nRET\n", 8}, // ADDI+SLLI? No, SLLI uses I-type - {"SRLI", "SRLI X10, $2, X11\nRET\n", 8}, - {"SRAI", "SRAI X10, $1, X11\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}, @@ -431,9 +431,9 @@ func TestRISCV_RVC_branch(t *testing.T) { // BEQ rs, X0, target → C.BEQZ when rs is in prime regs and offset fits. fn := firstTextRISCV(t, `#include "textflag.h" TEXT ·cbeqz(SB), NOSPLIT, $0 - ADDI X10, $1, X10 + ADDI $1, X10, X10 BEQ X10, X0, done - ADDI X10, $1, X10 + ADDI $1, X10, X10 done: RET `) @@ -488,28 +488,16 @@ func TestRISCV_RVC_CADD_commute(t *testing.T) { } func TestRISCV_RVC_CSUB(t *testing.T) { - // SUB where rd==rs1 and both in prime regs → C.SUB. + // 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 → rd=X10, rs1=X11 ≠ rd → no C.SUB. - // Plan9: INSTR src1, src2, dst. For C.SUB: rd must equal rs1. - // So: SUB X10, X11, X10 → rd=10, rs1=10, rs2=11 ✓ - if len(code) == 4 { - return // compressed - } - // Try with correct operand order. - fn2 := firstTextRISCV(t, `#include "textflag.h" -TEXT ·csub2(SB), NOSPLIT, $0 - SUB X10, X11, X10 - RET -`) - code2 := assembleRISCVHelper(t, fn2) - if len(code2) != 6 { - t.Errorf("expected 6 bytes with C.SUB, got %d (% x)", len(code2), code2) + // 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) } } @@ -712,9 +700,9 @@ TEXT ·sys(SB), NOSPLIT, $0 RET `) code := assembleRISCVHelper(t, fn) - // 3 system instructions × 4 bytes + JALR(4) = 16 - if len(code) != 16 { - t.Errorf("expected 16 bytes, got %d (% x)", len(code), code) + // 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) } } diff --git a/asm/riscv_frame.go b/asm/riscv_frame.go index 6d26c6e..cbb9fff 100644 --- a/asm/riscv_frame.go +++ b/asm/riscv_frame.go @@ -112,9 +112,13 @@ func riscvReturn(fi riscvFrameInfo) []byte { } // riscvSPAdjust emits an ADDI rd, imm, rd for the stack pointer (rd = rs1 = -// X2), compressed to C.ADDI when the immediate is a nonzero 6-bit signed -// value. +// X2), compressed to C.ADDI16SP when the immediate is a nonzero 16-byte +// multiple, else C.ADDI when it fits 6-bit signed. func riscvSPAdjust(imm int32) []byte { + if imm != 0 && imm%16 == 0 && imm >= -512 && imm <= 511 { + c := rvcADDI16SP(2, imm) + return []byte{byte(c), byte(c >> 8)} + } if riscvFitsCAddi(imm) { c := rvcCI(0x0, 2, uint32(imm)&0x3F) return []byte{byte(c), byte(c >> 8)} @@ -149,6 +153,9 @@ func riscvReturnEpilogueLen(fi riscvFrameInfo) int { } func riscvSPAdjustLen(imm int32) int { + if imm != 0 && imm%16 == 0 && imm >= -512 && imm <= 511 { + return 2 + } if riscvFitsCAddi(imm) { return 2 } diff --git a/testdata/verify/rvc_riscv64.s b/testdata/verify/rvc_riscv64.s new file mode 100644 index 0000000..33b9ccb --- /dev/null +++ b/testdata/verify/rvc_riscv64.s @@ -0,0 +1,32 @@ +// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) +// SPDX-License-Identifier: BSD-3-Clause + +#include "textflag.h" + +TEXT ·shifts(SB), NOSPLIT, $0 + SLLI $3, X10, X10 + SRLI $2, X10, X10 + SRAI $1, X10, X10 + RET + +TEXT ·logic(SB), NOSPLIT, $0 + AND X11, X10, X10 + OR X11, X10, X10 + XOR X11, X10, X10 + ANDI $7, X10, X10 + RET + +TEXT ·mv(SB), NOSPLIT, $0 + ADDI $0, X11, X10 + RET + +TEXT ·nop(SB), NOSPLIT, $0 + ADDI $0, X0 + RET + +TEXT ·bigframe(SB), NOSPLIT, $24-0 + RET + +TEXT ·ebreak(SB), NOSPLIT, $0 + EBREAK + RET diff --git a/verify/l64_groundtruth_test.go b/verify/l64_groundtruth_test.go index d0f1b07..192f02f 100644 --- a/verify/l64_groundtruth_test.go +++ b/verify/l64_groundtruth_test.go @@ -7,6 +7,7 @@ import ( "bytes" "fmt" "os" + "strings" "testing" "sourcedock.dev/petrbalvin/gasm-devkit/asm" @@ -81,11 +82,23 @@ func diffHex(a, b []byte) string { } for i := 0; i < n; i += 4 { ab, bb := "??", "??" - if i < len(a) { + if i+4 <= len(a) { ab = fmt.Sprintf("%02x%02x%02x%02x", a[i], a[i+1], a[i+2], a[i+3]) + } else if i < len(a) { + var sb strings.Builder + for j := i; j < len(a); j++ { + fmt.Fprintf(&sb, "%02x", a[j]) + } + ab = sb.String() } - if i < len(b) { + if i+4 <= len(b) { bb = fmt.Sprintf("%02x%02x%02x%02x", b[i], b[i+1], b[i+2], b[i+3]) + } else if i < len(b) { + var sb strings.Builder + for j := i; j < len(b); j++ { + fmt.Fprintf(&sb, "%02x", b[j]) + } + bb = sb.String() } mark := " " if ab != bb { diff --git a/verify/riscv_groundtruth_test.go b/verify/riscv_groundtruth_test.go index 4e0331b..b3c2cd2 100644 --- a/verify/riscv_groundtruth_test.go +++ b/verify/riscv_groundtruth_test.go @@ -17,7 +17,17 @@ import ( // relocation fields of static-symbol references are masked before the // comparison, since the toolchain leaves them zero for the linker. func TestGroundTruthRISCV(t *testing.T) { - path := "../testdata/verify/basic_riscv64.s" + for _, path := range []string{ + "../testdata/verify/basic_riscv64.s", + "../testdata/verify/rvc_riscv64.s", + } { + t.Run(path, func(t *testing.T) { + testGroundTruthRISCVFile(t, path) + }) + } +} + +func testGroundTruthRISCVFile(t *testing.T, path string) { src, err := os.ReadFile(path) if err != nil { t.Fatalf("read: %v", err)