Skip to content

vsm3me.vv intrinsic allows vd/vs2 register overlap, generates illegal instruction causing trap in spike #1872

Description

@usmanfaizyab

Summary

The vsm3me.vv instruction (Zvksh, SM3 message expansion) is documented as a
reserved encoding when the destination register group (vd) overlaps the
first source register group (vs2). When compiling code that uses the
__riscv_vsm3me_vv_u32m1 / __riscv_vsm3me_vv_u32m4 intrinsics with GCC,
the compiler allocates the same physical vector register group for vd and
vs2, producing an encoding that traps as trap_illegal_instruction when
executed on Spike. This reproduces at both LMUL=1 and LMUL=4, so it does not
appear to be LMUL-specific.

#include <riscv_vector.h>

vuint32m1_t f(vuint32m1_t vs2, vuint32m1_t vs1, size_t vl) {
    return __riscv_vsm3me_vv_u32m1(vs2, vs1, vl);
}

Image

#include <riscv_vector.h>

vuint32m4_t f(vuint32m4_t vs2, vuint32m4_t vs1, size_t vl) {
    return __riscv_vsm3me_vv_u32m4(vs2, vs1, vl);
}

Image

Spec reference

Per the RISC-V Vector Crypto specification for vsm3me.vv
(https://docs.riscv.org/reference/isa/v20260120/unpriv/vector-crypto.html#insns-vsm3me),
the destination register group must not overlap the vs2 source register
group; an encoding where they overlap is reserved. Spike enforces this at
runtime (see require_vsm3_constraints in riscv-isa-sim, which checks
insn.rd() != insn.rs2()), so any such encoding traps.

Image

Steps to reproduce

start.S

    .section .text
    .global _start
_start:
    la sp, _stack_top
    li t0, 0x200
    csrs mstatus, t0
    call main
1:
    j 1b
    .section .bss
    .align 4
_stack_bottom:
    .space 4096
_stack_top:

test.ld

ENTRY(_start)

SECTIONS
{
    . = 0x80000000;

    .text   : { *(.text*) }
    .rodata : { *(.rodata*) }
    .data   : { *(.data*) }
    .bss    : { *(.bss*) *(COMMON) }
}

test.c

#include <riscv_vector.h>

volatile uint32_t src1[8] = {1, 2, 3, 4, 5, 6, 7, 8};
volatile uint32_t src2[8] = {9, 10, 11, 12, 13, 14, 15, 16};
volatile uint32_t dst[8];

int main(void)
{
    size_t vl = __riscv_vsetvl_e32m1(8);

    vuint32m1_t vs1 = __riscv_vle32_v_u32m1((const uint32_t *)src1, vl);
    vuint32m1_t vs2 = __riscv_vle32_v_u32m1((const uint32_t *)src2, vl);
    vuint32m1_t vd = __riscv_vsm3me_vv_u32m1(vs2, vs1, vl);

    __riscv_vse32_v_u32m1((uint32_t *)dst, vd, vl);

    return 0;
}

Compile the test

riscv64-unknown-elf-gcc \
    -march=rv64gcv_zvksh \
    -mabi=lp64d \
    -nostdlib \
    -nostartfiles \
    -mcmodel=medany \
    -static \
    -Wl,-Ttest.ld \
    -o test.elf \
    start.S test.c

Simulate with Spike

spike --isa=rv64gcv_zvksh --varch=vlen:2048,elen:64 -d -m0x80000000 test.elf

The compiler allocates vd == vs2. Spike traps with:

Image

Additional notes

If I used inline assembly to use different vector registers for vd and vs2 then there was no trap:
test.c

#include <stdint.h>

volatile uint32_t src1[8] = {1,2,3,4,5,6,7,8};
volatile uint32_t src2[8] = {9,10,11,12,13,14,15,16};
volatile uint32_t dst[8];

int main(void)
{
    asm volatile (
        "vsetivli zero, 8, e32, m1, ta, ma\n\t"
        "vle32.v   v8, (%[src2])\n\t"
        "vle32.v   v16, (%[src1])\n\t"
        "vsm3me.vv v24, v8, v16\n\t"
        "vse32.v   v24, (%[dst])\n\t"
        :
        : [src1] "r"(src1),
          [src2] "r"(src2),
          [dst]  "r"(dst)
        : "memory"
    );

    return 0;
}

Image

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions