KVM: riscv: Unify and optimize FP register loops
Subsystems (3)
v2 cover KVM: riscv: Unify and optimize FP register loops
The change to both patches in this series is a structural/architectural one. Both functions set_reg and get_reg use identical code to locate the register the application specified in the token even though they do different things with that location
information: the set_reg function adds data the application requested to send to that identified location (in the copy_from_user function) while the get_reg function retrieves what data is at that identified location and sends it to the application (using the copy_to_user function). Everything else is the same. That is the only difference between the code.
Therefore I was able to apply the same solution to both of the function's finding-the-requested-register parts (which is everything up until the final if statement) which were filled with redundancy. The solution I came up with is to create local variables for the entire function for all the variables of the original code that would be different, depending on the floating point type. Then once I ask whether it is a double floating point or single floating point mode, I assign the aforementioned variables to the values they should have for that mode so that when I go through the if statements that ask whether the requested register number is a status control register or data register, I only have to go through it once because the respective floating point values/addresses would have been assigned to the aforementioned local variables, so the whole code kind of changes or is tailored to the floating point type.
This architectural change/solution will make the cpu execute the code faster (as the cpu will have less decisions to make and the bare-metal math used to figure out the address of the register is executed faster by the cpu than looking up object properties and array indexes), is more maintainable, and is more readable.
---
v2:
- Both patches had the same flawed line of code. The checking if the token's size was 32 or 64 bits line allowed token's with mismatched sizes & types to pass. I fixed this by checking it had the right size for its type by replacing the u32/u64 parts with a mode-dependent local variable, byte_offset.
Paul White (2):
KVM: riscv: Eliminate redundancy in get_reg function via pointers
KVM: riscv: Eliminate redundancy in set_reg function via pointers
arch/riscv/kvm/vcpu_fp.c | 124 +++++++++++++++++++++++---------------- 1 file changed, 72 insertions(+), 52 deletions(-)
Signature · 1 line
2.53.0
PATCH v2 1/2 KVM: riscv: Eliminate redundancy in get_reg function via pointers
The original code is essentially asking: for each possible floating point mode, ask if reg_num is a status control register or a data register. It is redundant to ask those same questions for every possible floating point mode case.
In order to only ask the "status control register or data register" question once, the variables in the status control if statement and the data register if statement that would be different depending on the type of floating point mode (for example, KVM_REG_RISCV_FP_F_REG(fcsr) would be KVM_REG_RISCV_FP_D_REG(fcsr) if the floating point mode was double) were made local variables of the entire function so that once the cpu passes the if statement inquiring whether it is double point mode or single point mode, all the variables that change depending on the type in the status control if statements and data register if statements will be set to the appropriate values they should be when in that mode before/when it enters the status control and data register if statements, allowing me to only have to ask those questions once and creating a dynamic code that changes based on the information. This results in a "asking the type then asking status control or data register question once" structure rather than a type-questions, type-questions redundant structure like before.
This results in faster cpu execution because despite the longer lines of code the cpu has to make less decisions, which saves time in case, due to its tendency to make assumptions about what an if statement will evaluate to, it has to backpedal because it realized it needed to go down a different if statement/path. Finding an address via my bare-metal approach is also faster than looking up an object properties and then an array. It is also more maintainable in the future. If the original code needed to be changed, you would have to remember to do the same thing to both type blocks whereas mine would just require a change in the beginning if statements. If a new floating point mode was added you have to ask those same questions all over again, which may make the code longer in the long run.
I also renamed reg_num to token_reg_num in my code as I felt it made it clearer that it was the register number the application wants and requested in a token. It differentiated it from the many other variables with the word reg in it.
---
v2:
- Only checked whether size token listed was 32 or 64 bits, which could create/allow tokens with mismatched sizes and types to pass. Should've checked if token's size was correct for its FP type. Did this by replacing u32 and u64 parts with byte_offset because it is mode-dependent local variable.
--- arch/riscv/kvm/vcpu_fp.c | 68 +++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 29 deletions(-)
arch/riscv/kvm/vcpu_fp.c +39 -29
@@ -83,41 +83,51 @@ int kvm_riscv_vcpu_get_reg_fp(struct kvm_vcpu *vcpu,struct kvm_cpu_context *cntx = &vcpu->arch.guest_context;unsigned long __user *uaddr = (unsigned long __user *)(unsigned long )reg->addr; -unsigned long reg_num = reg->id & ~(KVM_REG_ARCH_MASK | +unsigned long token_reg_num = reg->id & ~(KVM_REG_ARCH_MASK |KVM_REG_SIZE_MASK | rtype);void *reg_val; +u32 *fcsr_addr; +void *f_array_addr; +size_t byte_offset =0 ; +unsigned long offical_fcsr_num =0 ; +unsigned long offical_first_reg =0 ; +unsigned long offical_last_reg =0 ; +if ((rtype ==KVM_REG_RISCV_FP_F ) &&riscv_isa_extension_available (vcpu->arch.isa,F )) { + fcsr_addr = &cntx->fp.f.fcsr; + f_array_addr = &cntx->fp.f.f; + byte_offset =4 ; + offical_fcsr_num =KVM_REG_RISCV_FP_F_REG (fcsr); + offical_first_reg =KVM_REG_RISCV_FP_F_REG (f[0 ]); + offical_last_reg =KVM_REG_RISCV_FP_F_REG (f[31 ]); + }else if ((rtype ==KVM_REG_RISCV_FP_D ) && +riscv_isa_extension_available (vcpu->arch.isa,D )) { + fcsr_addr = &cntx->fp.d.fcsr; + f_array_addr = &cntx->fp.d.f; + byte_offset =8 ; + offical_fcsr_num =KVM_REG_RISCV_FP_D_REG (fcsr); + offical_first_reg =KVM_REG_RISCV_FP_D_REG (f[0 ]); + offical_last_reg =KVM_REG_RISCV_FP_D_REG (f[31 ]); + }else { +return -ENOENT ; + } + +if (token_reg_num == offical_fcsr_num) {if (KVM_REG_SIZE (reg->id) !=sizeof (u32))return -EINVAL ; -if (reg_num ==KVM_REG_RISCV_FP_F_REG (fcsr)) - reg_val = &cntx->fp.f.fcsr; -else if ((KVM_REG_RISCV_FP_F_REG (f[0 ]) <= reg_num) && - reg_num <=KVM_REG_RISCV_FP_F_REG (f[31 ])) { - reg_num =array_index_nospec (reg_num, -ARRAY_SIZE (cntx->fp.f.f)); - reg_val = &cntx->fp.f.f[reg_num]; - }else -return -ENOENT ; - }else if ((rtype ==KVM_REG_RISCV_FP_D ) && -riscv_isa_extension_available (vcpu->arch.isa,D )) { -if (reg_num ==KVM_REG_RISCV_FP_D_REG (fcsr)) { -if (KVM_REG_SIZE (reg->id) !=sizeof (u32)) -return -EINVAL ; - reg_val = &cntx->fp.d.fcsr; - }else if ((KVM_REG_RISCV_FP_D_REG (f[0 ]) <= reg_num) && - reg_num <=KVM_REG_RISCV_FP_D_REG (f[31 ])) { -if (KVM_REG_SIZE (reg->id) !=sizeof (u64)) -return -EINVAL ; - reg_num =array_index_nospec (reg_num, -ARRAY_SIZE (cntx->fp.d.f)); - reg_val = &cntx->fp.d.f[reg_num]; - }else -return -ENOENT ; - }else + reg_val = fcsr_addr; + }else if ((offical_first_reg <= token_reg_num) && (token_reg_num <= offical_last_reg)) { +if (KVM_REG_SIZE (reg->id) != byte_offset) +return -EINVAL ; + token_reg_num =array_index_nospec (token_reg_num, +32 ); + reg_val = (u8 *)f_array_addr + (token_reg_num * byte_offset); + }else {return -ENOENT ; + }if (copy_to_user (uaddr, reg_val,KVM_REG_SIZE (reg->id)))return -EFAULT ;
@@ -144,9 +154,9 @@ int kvm_riscv_vcpu_set_reg_fp(struct kvm_vcpu *vcpu,if (reg_num ==KVM_REG_RISCV_FP_F_REG (fcsr)) reg_val = &cntx->fp.f.fcsr;else if ((KVM_REG_RISCV_FP_F_REG (f[0 ]) <= reg_num) && - reg_num <=KVM_REG_RISCV_FP_F_REG (f[31 ])) { + reg_num <=KVM_REG_RISCV_FP_F_REG (f[31 ])) { reg_num =array_index_nospec (reg_num, -ARRAY_SIZE (cntx->fp.f.f)); +ARRAY_SIZE (cntx->fp.f.f)); reg_val = &cntx->fp.f.f[reg_num]; }else return -ENOENT ;
@@ -161,7 +171,7 @@ int kvm_riscv_vcpu_set_reg_fp(struct kvm_vcpu *vcpu,if (KVM_REG_SIZE (reg->id) !=sizeof (u64))return -EINVAL ; reg_num =array_index_nospec (reg_num, -ARRAY_SIZE (cntx->fp.d.f)); +ARRAY_SIZE (cntx->fp.d.f)); reg_val = &cntx->fp.d.f[reg_num]; }else return -ENOENT ;
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH v2 1/2] KVM: riscv: Eliminate redundancy in get_reg function via pointers
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Low] KVM: riscv: runtime math replaces constants in kvm_riscv_vcpu_get_reg_fp() - [Low] KVM: riscv…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] KVM: riscv: runtime math replaces constants in kvm_riscv_vcpu_get_reg_fp()
- [Low] KVM: riscv: magic numbers and untyped math in kvm_riscv_vcpu_get_reg_fp() --
commit 6aba685edaa53c11084934b49cbe82d9d80cee01
Author: Paul White <[email protected]>
KVM: riscv: Eliminate redundancy in get_reg function via pointers
This commit refactors kvm_riscv_vcpu_get_reg_fp() in arch/riscv/kvm/vcpu_fp.c to store floating-point mode parameters in local variables and compute register addresses using byte-level pointer arithmetic.
Quoted 66 lines
arch/riscv/kvm/vcpu_fp.c +27
-23@@ -83,41 +83,51 @@ int kvm_riscv_vcpu_get_reg_fp(struct kvm_vcpu *vcpu,struct kvm_cpu_context *cntx = &vcpu->arch.guest_context;unsigned long __user *uaddr = (unsigned long __user *)(unsigned long )reg->addr; -unsigned long reg_num = reg->id & ~(KVM_REG_ARCH_MASK | +unsigned long token_reg_num = reg->id & ~(KVM_REG_ARCH_MASK |KVM_REG_SIZE_MASK | rtype);void *reg_val; +u32 *fcsr_addr; +void *f_array_addr; +size_t byte_offset =0 ; +unsigned long offical_fcsr_num =0 ; +unsigned long offical_first_reg =0 ; +unsigned long offical_last_reg =0 ; +if ((rtype ==KVM_REG_RISCV_FP_F ) &&riscv_isa_extension_available (vcpu->arch.isa,F )) { + fcsr_addr = &cntx->fp.f.fcsr; + f_array_addr = &cntx->fp.f.f; + byte_offset =4 ; + offical_fcsr_num =KVM_REG_RISCV_FP_F_REG (fcsr); + offical_first_reg =KVM_REG_RISCV_FP_F_REG (f[0 ]); + offical_last_reg =KVM_REG_RISCV_FP_F_REG (f[31 ]); + }else if ((rtype ==KVM_REG_RISCV_FP_D ) && +riscv_isa_extension_available (vcpu->arch.isa,D )) { + fcsr_addr = &cntx->fp.d.fcsr; + f_array_addr = &cntx->fp.d.f; + byte_offset =8 ; + offical_fcsr_num =KVM_REG_RISCV_FP_D_REG (fcsr); + offical_first_reg =KVM_REG_RISCV_FP_D_REG (f[0 ]); + offical_last_reg =KVM_REG_RISCV_FP_D_REG (f[31 ]); + }else { +return -ENOENT ; + } + +if (token_reg_num == offical_fcsr_num) {if (KVM_REG_SIZE (reg->id) !=sizeof (u32))return -EINVAL ; -if (reg_num ==KVM_REG_RISCV_FP_F_REG (fcsr)) - reg_val = &cntx->fp.f.fcsr; -else if ((KVM_REG_RISCV_FP_F_REG (f[0 ]) <= reg_num) && - reg_num <=KVM_REG_RISCV_FP_F_REG (f[31 ])) { - reg_num =array_index_nospec (reg_num, -ARRAY_SIZE (cntx->fp.f.f)); - reg_val = &cntx->fp.f.f[reg_num]; - }else -return -ENOENT ; - }else if ((rtype ==KVM_REG_RISCV_FP_D ) && -riscv_isa_extension_available (vcpu->arch.isa,D )) { -if (reg_num ==KVM_REG_RISCV_FP_D_REG (fcsr)) { -if (KVM_REG_SIZE (reg->id) !=sizeof (u32)) -return -EINVAL ; - reg_val = &cntx->fp.d.fcsr; - }else if ((KVM_REG_RISCV_FP_D_REG (f[0 ]) <= reg_num) && - reg_num <=KVM_REG_RISCV_FP_D_REG (f[31 ])) { -if (KVM_REG_SIZE (reg->id) !=sizeof (u64)) -return -EINVAL ; - reg_num =array_index_nospec (reg_num, -ARRAY_SIZE (cntx->fp.d.f)); - reg_val = &cntx->fp.d.f[reg_num];
arch/riscv/kvm/vcpu_fp.c int kvm_riscv_vcpu_get_reg_fp(struct kvm_vcpu *vcpu, · line 127
Quoted 10 lines
arch/riscv/kvm/vcpu_fp.c +7
-3- }else -return -ENOENT ; - }else + reg_val = fcsr_addr; + }else if ((offical_first_reg <= token_reg_num) && (token_reg_num <= offical_last_reg)) { +if (KVM_REG_SIZE (reg->id) != byte_offset) +return -EINVAL ; + token_reg_num =array_index_nospec (token_reg_num, +32 ); + reg_val = (u8 *)f_array_addr + (token_reg_num * byte_offset);
[Severity: Low]
Does this refactoring in kvm_riscv_vcpu_get_reg_fp() actually reduce branch decisions or improve address calculation when userspace invokes the KVM_GET_ONE_REG ioctl for F or D extension registers?
In the original code, the KVM_REG_RISCV_FP_F and KVM_REG_RISCV_FP_D branches are mutually exclusive, so the CPU evaluates the same number of conditional checks per call. In C, struct member access and typed array indexing such as &cntx->fp.f.f[reg_num] and &cntx->fp.d.f[reg_num] resolve struct offsets at compile time and scale the index by a constant power of two.
Moving byte_offset, offical_fcsr_num, offical_first_reg, and offical_last_reg into runtime local variables replaces compile-time constants (sizeof(u32), sizeof(u64), and KVM_REG_RISCV_FP_F_REG / KVM_REG_RISCV_FP_D_REG) with runtime state and variable multiplication (token_reg_num * byte_offset).
Quoted 19 lines
arch/riscv/kvm/vcpu_fp.c +4
-2+ }else {return -ENOENT ; + }if (copy_to_user (uaddr, reg_val,KVM_REG_SIZE (reg->id)))return -EFAULT ;@@ -144,9 +154,9 @@ int kvm_riscv_vcpu_set_reg_fp(struct kvm_vcpu *vcpu,if (reg_num ==KVM_REG_RISCV_FP_F_REG (fcsr)) reg_val = &cntx->fp.f.fcsr;else if ((KVM_REG_RISCV_FP_F_REG (f[0 ]) <= reg_num) && - reg_num <=KVM_REG_RISCV_FP_F_REG (f[31 ])) { + reg_num <=KVM_REG_RISCV_FP_F_REG (f[31 ])) { reg_num =array_index_nospec (reg_num, -ARRAY_SIZE (cntx->fp.f.f)); +ARRAY_SIZE (cntx->fp.f.f)); reg_val = &cntx->fp.f.f[reg_num]; }else return -ENOENT ;
arch/riscv/kvm/vcpu_fp.c int kvm_riscv_vcpu_set_reg_fp(struct kvm_vcpu *vcpu, · line 174
Quoted 5 lines
arch/riscv/kvm/vcpu_fp.c +1
-1@@ -161,7 +171,7 @@ int kvm_riscv_vcpu_set_reg_fp(struct kvm_vcpu *vcpu,if (KVM_REG_SIZE (reg->id) !=sizeof (u64))return -EINVAL ; reg_num =array_index_nospec (reg_num, -ARRAY_SIZE (cntx->fp.d.f)); +ARRAY_SIZE (cntx->fp.d.f));
[Severity: Low]
This isn't a bug, but there are several code quality issues in arch/riscv/kvm/vcpu_fp.c:
In kvm_riscv_vcpu_get_reg_fp(), sizeof(u32), sizeof(u64), and ARRAY_SIZE() are replaced with hardcoded magic numbers (4, 8, and 32), and typed array access is replaced with untyped void * and u8 * pointer arithmetic.
In addition, while offical_first_reg and offical_last_reg are introduced to abstract the register range check, the array indexing does not subtract offical_first_reg and hardcodes 32 in array_index_nospec(token_reg_num, 32). Would (u8 *)f_array_addr + (token_reg_num * byte_offset) compute the wrong offset if a future floating-point mode has a non-zero first register offset or a different register count?
Also, the new variable names have a typo (offical_fcsr_num, offical_first_reg, and offical_last_reg instead of official_*), and this patch includes unrelated whitespace changes in kvm_riscv_vcpu_set_reg_fp().
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
PATCH v2 2/2 KVM: riscv: Eliminate redundancy in set_reg function via pointers
In reply to Paul WhiteThe original code is essentially asking: for each possible floating point mode, ask if reg_num is a status control register or a data register. It is redundant to ask those same questions for every possible floating point mode case.
In order to only ask the "status control register or data register" question once, the variables in the status control if statement and the data register if statement that would be different depending on the type of floating point mode (for example, KVM_REG_RISCV_FP_F_REG(fcsr) would be KVM_REG_RISCV_FP_D_REG(fcsr) if the floating point mode was double) were made local variables of the entire function so that once the cpu passes the if statement inquiring whether it is double point mode or single point mode, all the variables that change depending on the type in the status control if statements and data register if statements will be set to the appropriate values they should be when in that mode before/when it enters the status control and data register if statements, allowing me to only have to ask those questions once and creating a dynamic code that changes based on the information. This results in a "asking the type then asking status control or data register question once" structure rather than a type-questions, type-questions redundant structure like before.
This results in faster cpu execution because despite the longer lines of code the cpu has to make less decisions, which saves time in case, due to its tendency to make assumptions about what an if statement will evaluate to, it has to backpedal because it realized it needed to go down a different if statement/path. Finding an address via my bare-metal approach is also faster than looking up an object properties and then an array. It is also more maintainable in the future. If the original code needed to be changed, you would have to remember to do the same thing to both type blocks whereas mine would just require a change in the beginning if statements. If a new floating point mode was added you have to ask those same questions all over again, which may make the code longer in the long run.
I also renamed reg_num to token_reg_num in my code as I felt it made it clearer that it was the register number the application wants and requested in a token so that it can write data into that register. It differentiated it from the many other variables with the word reg in it.
---
v2:
- only checked whether size token listed was either 32 or 64 which could create/allow tokens with mismatched sizes and types to pass, but should've checked if token's size was correct for its FP type. Did this by replacing u32 and u64 with byte_offset because it is a existing mode-dependent variable.
--- arch/riscv/kvm/vcpu_fp.c | 62 +++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 26 deletions(-)
arch/riscv/kvm/vcpu_fp.c +36 -26
@@ -142,41 +142,51 @@ int kvm_riscv_vcpu_set_reg_fp(struct kvm_vcpu *vcpu,struct kvm_cpu_context *cntx = &vcpu->arch.guest_context;unsigned long __user *uaddr = (unsigned long __user *)(unsigned long )reg->addr; -unsigned long reg_num = reg->id & ~(KVM_REG_ARCH_MASK | +unsigned long token_reg_num = reg->id & ~(KVM_REG_ARCH_MASK |KVM_REG_SIZE_MASK | rtype);void *reg_val; +u32 *fcsr_addr; +void *f_array_addr; +size_t byte_offset =0 ; +unsigned long offical_fcsr_num =0 ; +unsigned long offical_first_reg =0 ; +unsigned long offical_last_reg =0 ; +if ((rtype ==KVM_REG_RISCV_FP_F ) &&riscv_isa_extension_available (vcpu->arch.isa,F )) { + fcsr_addr = &cntx->fp.f.fcsr; + f_array_addr = &cntx->fp.f.f; + byte_offset =4 ; + offical_fcsr_num =KVM_REG_RISCV_FP_F_REG (fcsr); + offical_first_reg =KVM_REG_RISCV_FP_F_REG (f[0 ]); + offical_last_reg =KVM_REG_RISCV_FP_F_REG (f[31 ]); + }else if ((rtype ==KVM_REG_RISCV_FP_D ) && +riscv_isa_extension_available (vcpu->arch.isa,D )) { + fcsr_addr = &cntx->fp.d.fcsr; + f_array_addr = &cntx->fp.d.f; + byte_offset =8 ; + offical_fcsr_num =KVM_REG_RISCV_FP_D_REG (fcsr); + offical_first_reg =KVM_REG_RISCV_FP_D_REG (f[0 ]); + offical_last_reg =KVM_REG_RISCV_FP_D_REG (f[31 ]); + }else { +return -ENOENT ; + } + +if (token_reg_num == offical_fcsr_num) {if (KVM_REG_SIZE (reg->id) !=sizeof (u32))return -EINVAL ; -if (reg_num ==KVM_REG_RISCV_FP_F_REG (fcsr)) - reg_val = &cntx->fp.f.fcsr; -else if ((KVM_REG_RISCV_FP_F_REG (f[0 ]) <= reg_num) && - reg_num <=KVM_REG_RISCV_FP_F_REG (f[31 ])) { - reg_num =array_index_nospec (reg_num, -ARRAY_SIZE (cntx->fp.f.f)); - reg_val = &cntx->fp.f.f[reg_num]; - }else -return -ENOENT ; - }else if ((rtype ==KVM_REG_RISCV_FP_D ) && -riscv_isa_extension_available (vcpu->arch.isa,D )) { -if (reg_num ==KVM_REG_RISCV_FP_D_REG (fcsr)) { -if (KVM_REG_SIZE (reg->id) !=sizeof (u32)) -return -EINVAL ; - reg_val = &cntx->fp.d.fcsr; - }else if ((KVM_REG_RISCV_FP_D_REG (f[0 ]) <= reg_num) && - reg_num <=KVM_REG_RISCV_FP_D_REG (f[31 ])) { -if (KVM_REG_SIZE (reg->id) !=sizeof (u64)) -return -EINVAL ; - reg_num =array_index_nospec (reg_num, -ARRAY_SIZE (cntx->fp.d.f)); - reg_val = &cntx->fp.d.f[reg_num]; - }else -return -ENOENT ; - }else + reg_val = fcsr_addr; + }else if ((offical_first_reg <= token_reg_num) && (token_reg_num <= offical_last_reg)) { +if (KVM_REG_SIZE (reg->id) != byte_offset) +return -EINVAL ; + token_reg_num =array_index_nospec (token_reg_num, +32 ); + reg_val = (u8 *)f_array_addr + (token_reg_num * byte_offset); + }else {return -ENOENT ; + }if (copy_from_user (reg_val, uaddr,KVM_REG_SIZE (reg->id)))return -EFAULT ;
Signature · 1 line
2.53.0