[PATCH v7 5/6] riscv: vector: optimize vstate operations

Conor Dooley conor at kernel.org
Wed Nov 11 15:00:13 PST 2026


On Wed, Nov 11, 2026 at 12:43:27PM -0600, Andy Chiu wrote:
> Introduce the riscv_novstateopt to control the optimization of vector
> state operations in the kernel space.
> 
> The optimization enables vector in kernel mode and bypasses vector
> register nulling for user space on the syscall fast path when the boot
> argument "riscv_novstateopt" is not set
> 
> Enabling this optimization yields the following performance benefits:
>  - Cut 80 cycles latency for getpid on Ascalon.
>  - 1.1% ~ 4.09% request throughput improvment on nginx workload with web
>    serving size under 4KB
> 
> Signed-off-by: Andy Chiu <tchiu at tenstorrent.com>
> ---
> Changelog v6:
>  - upgrade it to use alternative and change default to enable the
>    optimization

Why? Argument for the default being like this needs to be in the commit
message.

> Changelog v5:
>  - new patch since v5
> ---
>  .../admin-guide/kernel-parameters.txt          | 13 +++++++++++++
>  arch/riscv/include/asm/alternative-macros.h    |  6 ++++++
>  arch/riscv/include/asm/alternative.h           |  3 ---
>  arch/riscv/include/asm/cpufeature-macros.h     |  6 ++++--
>  arch/riscv/include/asm/vector.h                | 16 +++++++++++++++-
>  arch/riscv/kernel/cpufeature.c                 | 18 ++++++++++++++++++
>  arch/riscv/kernel/entry.S                      | 15 +++++++++++++--
>  arch/riscv/kernel/process.c                    |  3 +++
>  arch/riscv/kernel/vector.c                     | 17 +++++++++++++++--
>  9 files changed, 87 insertions(+), 10 deletions(-)
> 
> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> index 0b3c140babd9..e1146ee6ab1e 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -6722,6 +6722,19 @@ Kernel parameters
>  		fcfi	Disable user forward CFI ABI to userspace even if the
>  			landing pad extension is available.
>  
> +	riscv_novstateopt [RISCV,EARLY]
> +			Disable the vector state optimization. By default the
> +			kernel keeps Vector enabled while running in kernel
> +			space, rather than toggling sstatus.VS on every use of
> +			the kernel-mode vector, and skips nulling the vector
> +			registers for user space. Pass this option to restore the
> +			conservative behavior, which catches illegal use of
> +			Vector in kernel code. The optimization roughly cuts
> +			30 ns of getpid() latency on a Blackhole x280 and gives
> +			a 4.09% request throughput improvement for an nginx
> +			workload serving a 2KB web page on a four-core
> +			Ascalon-S.
> +
>  	ro		[KNL] Mount root device read-only on boot
>  
>  	rodata=		[KNL,EARLY]
> diff --git a/arch/riscv/include/asm/alternative-macros.h b/arch/riscv/include/asm/alternative-macros.h
> index 629d884578da..45d9ae1e0aa8 100644
> --- a/arch/riscv/include/asm/alternative-macros.h
> +++ b/arch/riscv/include/asm/alternative-macros.h
> @@ -2,6 +2,12 @@
>  #ifndef __ASM_ALTERNATIVE_MACROS_H
>  #define __ASM_ALTERNATIVE_MACROS_H
>  
> +#include <linux/wordpart.h>
> +#define PATCH_ID_CPUFEATURE_ID(p)		lower_16_bits(p)
> +#define PATCH_ID_CPUFEATURE_VALUE(p)		upper_16_bits(p)
> +
> +#define RISCV_CPUFEAT_VSTATEOPT	0x1

This isn't a cpu feature at all though, so it should not be named like that.

> +
>  #ifdef __ASSEMBLER__
>  
>  .macro ALT_ENTRY oldptr newptr vendor_id patch_id new_len
> diff --git a/arch/riscv/include/asm/alternative.h b/arch/riscv/include/asm/alternative.h
> index 6be7b2b6ade9..bc2ecaa9125a 100644
> --- a/arch/riscv/include/asm/alternative.h
> +++ b/arch/riscv/include/asm/alternative.h
> @@ -16,9 +16,6 @@
>  #include <linux/stddef.h>
>  #include <asm/hwcap.h>
>  
> -#define PATCH_ID_CPUFEATURE_ID(p)		lower_16_bits(p)
> -#define PATCH_ID_CPUFEATURE_VALUE(p)		upper_16_bits(p)
> -
>  #define RISCV_ALTERNATIVES_BOOT		0 /* alternatives applied during regular boot */
>  #define RISCV_ALTERNATIVES_MODULE	1 /* alternatives applied during module-init */
>  #define RISCV_ALTERNATIVES_EARLY_BOOT	2 /* alternatives applied before mmu start */
> diff --git a/arch/riscv/include/asm/cpufeature-macros.h b/arch/riscv/include/asm/cpufeature-macros.h
> index adaf9e3fb25c..7bd900899bb0 100644
> --- a/arch/riscv/include/asm/cpufeature-macros.h
> +++ b/arch/riscv/include/asm/cpufeature-macros.h
> @@ -45,14 +45,16 @@ static __always_inline bool __riscv_has_extension_unlikely(const unsigned long v
>  
>  static __always_inline bool riscv_has_extension_unlikely(const unsigned long ext)
>  {
> -	compiletime_assert(ext < RISCV_ISA_EXT_MAX, "ext must be < RISCV_ISA_EXT_MAX");
> +	int realext = PATCH_ID_CPUFEATURE_ID(ext);
> +	compiletime_assert(realext < RISCV_ISA_EXT_MAX, "ext must be < RISCV_ISA_EXT_MAX");
>  
>  	return __riscv_has_extension_unlikely(STANDARD_EXT, ext);
>  }

Please don't abuse riscv_has_extension_*() for things that are not
extensions. I don't mind if you do something that wraps
__riscv_has_extension_*() to make use of the underlying infrastructure
but I am against muddying the standard extension space with linux internal
stuff - particularly in this manner because this:
> +static __always_inline bool has_vstate_opt(void)
> +{
> +	return riscv_has_extension_likely((RISCV_CPUFEAT_VSTATEOPT << 16) | RISCV_ISA_EXT_ZVE32X);
> +}
is incredibly confusing and error prone.

Recently enough I talked about something very very similar with Jisheng,
in the context of using an alternative for misaligned access, and I
said:
| ...I'd rather see __riscv_has_extension_*() get renamed
| to __riscv_has_cap*(), so that things like unaligned access speed get a
| different namespace to standard and vendor extensions. IOW you'd end up
| with
| 
| static __always_inline bool riscv_has_extension_unlikely(const unsigned long ext)
| {
| 	compiletime_assert(ext < RISCV_ISA_EXT_MAX, "ext must be < RISCV_ISA_EXT_MAX");
| 
| 	return __riscv_has_cap_unlikely(STANDARD_EXT, ext);
| }
| 
| static __always_inline bool riscv_has_vendor_extension_unlikely(const unsigned long vendor,
| 								const unsigned long ext)
| {
| 	if (!IS_ENABLED(CONFIG_RISCV_ISA_VENDOR_EXT))
| 		return false;
| 
| 	return __riscv_has_cap_unlikely(vendor, ext + RISCV_VENDOR_EXT_ALTERNATIVES_BASE);
| }
| 
| static __always_inline bool riscv_has_cap_unlikely(const unsigned long cap)
| {
| 	return __riscv_has_cap_unlikely(LINUX_CAP, cap);
| }
| 
| Currently standard extensions use 0 as the vendor, perhaps we could then
| use -1 (the value is an unsigned long) for non extension related caps?

What's here, with this realext business definitely looks like a hack.
Doing what I suggest above allows you to have a real key that you can
set, just like the vendor extensions do. You can call a function from
riscv_fill_hwcap() to populate the bitmap and modify
_apply_alternatives() to additionally patch this sort of thing.

Cheers,
Conor.

pw-bot: changes-requested

>  
>  static __always_inline bool riscv_has_extension_likely(const unsigned long ext)
>  {
> -	compiletime_assert(ext < RISCV_ISA_EXT_MAX, "ext must be < RISCV_ISA_EXT_MAX");
> +	int realext = PATCH_ID_CPUFEATURE_ID(ext);
> +	compiletime_assert(realext < RISCV_ISA_EXT_MAX, "ext must be < RISCV_ISA_EXT_MAX");
>  
>  	return __riscv_has_extension_likely(STANDARD_EXT, ext);
>  }
> diff --git a/arch/riscv/include/asm/vector.h b/arch/riscv/include/asm/vector.h
> index e83f9e0046a9..745ac3ca67b7 100644
> --- a/arch/riscv/include/asm/vector.h
> +++ b/arch/riscv/include/asm/vector.h
> @@ -49,6 +49,7 @@
>  	_res;								\
>  })
>  
> +extern bool riscv_v_vstate_opt;
>  extern unsigned long riscv_v_vsize;
>  int riscv_v_setup_vsize(void);
>  bool insn_is_vector(u32 insn_buf);
> @@ -78,6 +79,11 @@ static __always_inline bool has_vector(void)
>  	return riscv_has_extension_unlikely(RISCV_ISA_EXT_ZVE32X);
>  }
>  
> +static __always_inline bool has_vstate_opt(void)
> +{
> +	return riscv_has_extension_likely((RISCV_CPUFEAT_VSTATEOPT << 16) | RISCV_ISA_EXT_ZVE32X);
> +}
> +
>  static __always_inline bool has_xtheadvector_no_alternatives(void)
>  {
>  	if (IS_ENABLED(CONFIG_RISCV_ISA_XTHEADVECTOR))
> @@ -122,6 +128,9 @@ static inline bool riscv_v_vstate_query(struct pt_regs *regs)
>  
>  static __always_inline void riscv_v_enable(void)
>  {
> +	if (has_vstate_opt())
> +		return;
> +
>  	if (has_xtheadvector())
>  		csr_set(CSR_SSTATUS, SR_VS_THEAD);
>  	else
> @@ -130,6 +139,9 @@ static __always_inline void riscv_v_enable(void)
>  
>  static __always_inline void riscv_v_disable(void)
>  {
> +	if (has_vstate_opt())
> +		return;
> +
>  	if (has_xtheadvector())
>  		csr_clear(CSR_SSTATUS, SR_VS_THEAD);
>  	else
> @@ -336,7 +348,8 @@ static inline void riscv_v_vstate_set_restore(struct task_struct *task,
>  static inline void riscv_v_vstate_discard(struct pt_regs *regs)
>  {
>  	if (__riscv_v_vstate_check_gt(regs->status, INITIAL)) {
> -		riscv_v_vstate_set_restore(current, regs);
> +		if (!has_vstate_opt())
> +			riscv_v_vstate_set_restore(current, regs);
>  		riscv_v_vstate_init(regs);
>  	}
>  }
> @@ -422,6 +435,7 @@ struct pt_regs;
>  
>  static inline int riscv_v_setup_vsize(void) { return -EOPNOTSUPP; }
>  static __always_inline bool has_vector(void) { return false; }
> +static __always_inline bool has_vstate_opt(void) { return false; }
>  static __always_inline bool insn_is_vector(u32 insn_buf) { return false; }
>  static __always_inline bool has_xtheadvector_no_alternatives(void) { return false; }
>  static __always_inline bool has_xtheadvector(void) { return false; }
> diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c
> index 72bce616491f..041f6a35fba3 100644
> --- a/arch/riscv/kernel/cpufeature.c
> +++ b/arch/riscv/kernel/cpufeature.c
> @@ -1294,6 +1294,19 @@ void riscv_clear_hypervisor_csr(void)
>  	csr_clear(CSR_HSTATUS, HSTATUS_HU);
>  }
>  
> +/*
> + * Leaving Vector enabled while in the kernel relies on the vstate alternatives
> + * being patched in. It can be turned off on the command line to catch illegal
> + * use of Vector in kernel code.
> + */
> +bool riscv_v_vstate_opt = true;
> +static int __init riscv_novstateopt_setup(char *__unused)
> +{
> +	riscv_v_vstate_opt = false;
> +	return 0;
> +}
> +early_param("riscv_novstateopt", riscv_novstateopt_setup);
> +
>  /*
>   * Alternative patch sites consider 48 bits when determining when to patch
>   * the old instruction sequence with the new. These bits are broken into a
> @@ -1322,6 +1335,11 @@ static bool riscv_cpufeature_patch_check(u16 id, u16 value)
>  		 * then the alternative cannot be applied.
>  		 */
>  		return riscv_cboz_block_size <= (1U << value);
> +	case RISCV_ISA_EXT_ZVE32X:
> +		if (value == RISCV_CPUFEAT_VSTATEOPT)
> +			return riscv_v_vstate_opt;
> +
> +		return true;
>  	}
>  
>  	return false;
> diff --git a/arch/riscv/kernel/entry.S b/arch/riscv/kernel/entry.S
> index 628b85e3915a..bb4787711f2c 100644
> --- a/arch/riscv/kernel/entry.S
> +++ b/arch/riscv/kernel/entry.S
> @@ -6,6 +6,7 @@
>  
>  #include <linux/init.h>
>  #include <linux/linkage.h>
> +#include <linux/stringify.h>
>  
>  #include <asm/alternative-macros.h>
>  #include <asm/asm.h>
> @@ -175,10 +176,13 @@ SYM_CODE_START(handle_exception)
>  	 * Disable user-mode memory access as it should only be set in the
>  	 * actual user copy routines.
>  	 *
> -	 * Disable the FPU/Vector to detect illegal usage of floating point
> +	 * Disable the FPU to detect illegal usage of floating point
>  	 * or vector in kernel space.
>  	 */
> -	li t0, SR_SUM | SR_FS_VS
> +	li t0, SR_SUM | SR_FS
> +	ALTERNATIVE(__stringify(ori t0, t0, SR_VS), "nop", 0,
> +		    (RISCV_CPUFEAT_VSTATEOPT << 16) | RISCV_ISA_EXT_ZVE32X,
> +		    CONFIG_RISCV_ISA_V)
>  #ifdef CONFIG_64BIT
>  	li t1, SR_ELP
>  	or t0, t0, t1
> @@ -186,6 +190,13 @@ SYM_CODE_START(handle_exception)
>  
>  	REG_L s0, TASK_TI_USER_SP(tp)
>  	csrrc s1, CSR_STATUS, t0
> +	ALTERNATIVE("j .Lskip_v_enable", __stringify(andi t0, s1, SR_VS), 0,
> +		    (RISCV_CPUFEAT_VSTATEOPT << 16) | RISCV_ISA_EXT_ZVE32X,
> +		    CONFIG_RISCV_ISA_V)
> +	bnez t0, .Lskip_v_enable
> +	li a0, SR_VS_INITIAL
> +	csrs CSR_STATUS, a0
> +.Lskip_v_enable:
>  	save_userssp s2, s1
>  	csrr s2, CSR_EPC
>  	csrr s3, CSR_TVAL
> diff --git a/arch/riscv/kernel/process.c b/arch/riscv/kernel/process.c
> index 34c1d498d1ca..27146b3fa738 100644
> --- a/arch/riscv/kernel/process.c
> +++ b/arch/riscv/kernel/process.c
> @@ -258,6 +258,9 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
>  		/* Supervisor/Machine, irqs on: */
>  		childregs->status = SR_PP | SR_PIE;
>  
> +		if (has_vstate_opt())
> +			riscv_v_vstate_init(childregs);
> +
>  		p->thread.s[0] = (unsigned long)args->fn;
>  		p->thread.s[1] = (unsigned long)args->fn_arg;
>  		p->thread.ra = (unsigned long)ret_from_fork_kernel_asm;
> diff --git a/arch/riscv/kernel/vector.c b/arch/riscv/kernel/vector.c
> index 6fd541f5d5cb..eec9deff8da7 100644
> --- a/arch/riscv/kernel/vector.c
> +++ b/arch/riscv/kernel/vector.c
> @@ -12,6 +12,7 @@
>  #include <linux/prctl.h>
>  
>  #include <asm/thread_info.h>
> +#include <asm/cpufeature.h>
>  #include <asm/processor.h>
>  #include <asm/insn.h>
>  #include <asm/vector.h>
> @@ -69,6 +70,16 @@ void riscv_v_ucontext_save(struct task_struct *tsk)
>  int riscv_v_setup_vsize(void)
>  {
>  	unsigned long this_vsize;
> +	bool v_always_on = false;
> +
> +	/*
> +	 * has_vstate_opt() cannot be used here if called from riscv_fill_hwcap(), before
> +	 * apply_boot_alternatives(),
> +	 */
> +	if (__riscv_isa_extension_available(NULL, RISCV_ISA_EXT_ZVE32X) && riscv_v_vstate_opt) {
> +		v_always_on = true;
> +		csr_set(CSR_SSTATUS, SR_VS_INITIAL);
> +	}
>  
>  	/*
>  	 * There are 32 vector registers with vlenb length.
> @@ -81,9 +92,11 @@ int riscv_v_setup_vsize(void)
>  		return 0;
>  	}
>  
> -	riscv_v_enable();
> +	if (!v_always_on)
> +		riscv_v_enable();
>  	this_vsize = csr_read(CSR_VLENB) * 32;
> -	riscv_v_disable();
> +	if (!v_always_on)
> +		riscv_v_disable();
>  
>  	if (!riscv_v_vsize) {
>  		riscv_v_vsize = this_vsize;
> -- 
> 2.43.0
> 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 228 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-riscv/attachments/20261111/14d362be/attachment.sig>


More information about the linux-riscv mailing list