Re: [RFC PATCH] riscv: clarify vector state semantics on syscall and context switch
From: daichengrong
Date: Sun May 24 2026 - 21:47:57 EST
On 5/21/26 14:48, Andy Chiu wrote:
> On Thu, Mar 19, 2026 at 03:37:09PM +0800, daichengrong wrote:
>> The RISC-V vector specification states that executing a system call
>> causes all caller-saved vector registers (v0-v31, vl, vtype) and vstart
>> to become unspecified.
>>
>> Currently, after calling riscv_v_vstate_discard(), the vector state
>> may still be marked as DIRTY, which can mislead the context switch
>> logic into treating the registers as containing valid user data.
>>
>> This patch clarifies and tightens the kernel-side semantics:
>>
>> 1. On syscall entry, the kernel checks the vector state via mstatus
>> and discards it if necessary. After discard, the state is explicitly
>> set to INIT instead of DIRTY, indicating that the vector registers
>> no longer contain meaningful user data.
>>
>> 2. During context switch, the vector state is interpreted as follows:
>> - INIT: no valid user data is present, so vector register data does
>> not need to be saved.
>> - non-INIT (e.g. DIRTY): vector register data must be saved.
>>
>> 3. On restore, if the state is INIT, the vector registers are treated
>> as invalid and are not restored from memory. Instead, they are
>> overwritten with a known initial value to avoid potential data
>> leakage from a previous task.
>>
>> This aligns the kernel's vector state tracking with the architectural
>> "unspecified" semantics while ensuring correct lazy context switching
>> and preventing cross-task data leakage.
> Hi daichengrong,
>
> Good catch on spotting this optimization opportunity!
>
> I have a patch series[1] that happens to be very similar as yours, but I
> think your coding style make the code more readable (I like the way you
> use _vstate_check for CLEAN on restore path).
>
> Here are some issues where this patch breaks:
> - first-use trap returns discarded vregs instead of zero'ed.
> - ptrace gets stale vregs in PTRACE_SYSCALL
> - context modifications through signal handler are dropped at syscall
> stops.
>
> I will merge your patch into my v3 with all the above fixed. Hope that
> sounds good to you!
>
Thanks for the detailed analysis and for pointing out these corner cases.
I agree the first-use trap, ptrace and signal-handler interactions need to be handled correctly.
Glad to hear the overall direction makes sense, and I’m happy for you to fold the idea into your v3 series.
My original motivation was to explore whether the syscall boundary could serve as a natural entry point for managing the RVV state lifecycle across the whole system,
especially for reducing unnecessary restore/init work on repeated syscall paths while still preserving the expected userspace semantics.
> [1]: https://lore.kernel.org/linux-riscv/20260402043414.2421916-2-andybnac@xxxxxxxxx/
>
>>
>> Signed-off-by: daichengrong <daichengrong@xxxxxxxxxxx>
>> ---
>> arch/riscv/include/asm/vector.h | 15 +++++++--------
>> 1 file changed, 7 insertions(+), 8 deletions(-)
>>
>> diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h
>> index 00cb9c0982b1..93c68a549b72 100644
>> --- a/arch/riscv/include/asm/vector.h
>> +++ b/arch/riscv/include/asm/vector.h
>> @@ -298,8 +298,9 @@ static inline void __riscv_v_vstate_discard(void)
>> static inline void riscv_v_vstate_discard(struct pt_regs *regs)
>> {
>> if (riscv_v_vstate_query(regs)) {
>> - __riscv_v_vstate_discard();
>> - __riscv_v_vstate_dirty(regs);
> We can skip discarding vstate here as it will be done in the restore path
>> + if (!__riscv_v_vstate_check(regs->status, INITIAL))
>> + __riscv_v_vstate_discard();
>> + riscv_v_vstate_on(regs);
>> }
>> }
>>
>> @@ -315,19 +316,17 @@ static inline void riscv_v_vstate_save(struct __riscv_v_ext_state *vstate,
>> static inline void riscv_v_vstate_restore(struct __riscv_v_ext_state *vstate,
>> struct pt_regs *regs)
>> {
>> - if (riscv_v_vstate_query(regs)) {
>> + if (__riscv_v_vstate_check(regs->status, INITIAL))
>> + __riscv_v_vstate_discard();
>> + else if (__riscv_v_vstate_check(regs->status, CLEAN))
>> __riscv_v_vstate_restore(vstate, vstate->datap);
>> - __riscv_v_vstate_clean(regs);
>> - }
>> }
>>
>> static inline void riscv_v_vstate_set_restore(struct task_struct *task,
>> struct pt_regs *regs)
>> {
>> - if (riscv_v_vstate_query(regs)) {
>> + if (riscv_v_vstate_query(regs))
>> set_tsk_thread_flag(task, TIF_RISCV_V_DEFER_RESTORE);
>> - riscv_v_vstate_on(regs);
>> - }
>> }
>>
>> #ifdef CONFIG_RISCV_ISA_V_PREEMPTIVE
>> --
>> 2.25.1
>>
>>
>> _______________________________________________
>> linux-riscv mailing list
>> linux-riscv@xxxxxxxxxxxxxxxxxxx
>> http://lists.infradead.org/mailman/listinfo/linux-riscv
>>
>
> Cheers,
> Andy