diff mbox series

[bpf] riscv, bpf: Fix kfunc parameters incompatibility between bpf and riscv abi

Message ID 20240324103306.2202954-1-pulehui@huaweicloud.com (mailing list archive)
State Handled Elsewhere
Headers show
Series [bpf] riscv, bpf: Fix kfunc parameters incompatibility between bpf and riscv abi | expand

Checks

Context Check Description
conchuod/vmtest-for-next-PR success PR summary
conchuod/patch-1-test-1 success .github/scripts/patches/tests/build_rv32_defconfig.sh
conchuod/patch-1-test-2 success .github/scripts/patches/tests/build_rv64_clang_allmodconfig.sh
conchuod/patch-1-test-3 success .github/scripts/patches/tests/build_rv64_gcc_allmodconfig.sh
conchuod/patch-1-test-4 success .github/scripts/patches/tests/build_rv64_nommu_k210_defconfig.sh
conchuod/patch-1-test-5 success .github/scripts/patches/tests/build_rv64_nommu_virt_defconfig.sh
conchuod/patch-1-test-6 success .github/scripts/patches/tests/checkpatch.sh
conchuod/patch-1-test-7 success .github/scripts/patches/tests/dtb_warn_rv64.sh
conchuod/patch-1-test-8 success .github/scripts/patches/tests/header_inline.sh
conchuod/patch-1-test-9 success .github/scripts/patches/tests/kdoc.sh
conchuod/patch-1-test-10 success .github/scripts/patches/tests/module_param.sh
conchuod/patch-1-test-11 success .github/scripts/patches/tests/verify_fixes.sh
conchuod/patch-1-test-12 success .github/scripts/patches/tests/verify_signedoff.sh

Commit Message

Pu Lehui March 24, 2024, 10:33 a.m. UTC
From: Pu Lehui <pulehui@huawei.com>

We encountered a failing case when running selftest in no_alu32 mode:

The failure case is `kfunc_call/kfunc_call_test4` and its source code is
like bellow:
```
long bpf_kfunc_call_test4(signed char a, short b, int c, long d) __ksym;
int kfunc_call_test4(struct __sk_buff *skb)
{
	...
	tmp = bpf_kfunc_call_test4(-3, -30, -200, -1000);
	...
}
```

And its corresponding asm code is:
```
0: r1 = -3
1: r2 = -30
2: r3 = 0xffffff38 # opcode: 18 03 00 00 38 ff ff ff 00 00 00 00 00 00 00 00
4: r4 = -1000
5: call bpf_kfunc_call_test4
```

insn 2 is parsed to ld_imm64 insn to emit 0x00000000ffffff38 imm, and
converted to int type and then send to bpf_kfunc_call_test4. But since
it is zero-extended in the bpf calling convention, riscv jit will
directly treat it as an unsigned 32-bit int value, and then fails with
the message "actual 4294966063 != expected -1234".

The reason is the incompatibility between bpf and riscv abi, that is,
bpf will do zero-extension on uint, but riscv64 requires sign-extension
on int or uint. We can solve this problem by sign extending the 32-bit
parameters in kfunc.

The issue is related to [0], and thanks to Yonghong and Alexei.

Link: https://github.com/llvm/llvm-project/pull/84874 [0]
Fixes: d40c3847b485 ("riscv, bpf: Add kfunc support for RV64")
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
 arch/riscv/net/bpf_jit_comp64.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

Comments

Puranjay Mohan March 24, 2024, 6:26 p.m. UTC | #1
Pu Lehui <pulehui@huaweicloud.com> writes:

> From: Pu Lehui <pulehui@huawei.com>
>
> We encountered a failing case when running selftest in no_alu32 mode:
>
> The failure case is `kfunc_call/kfunc_call_test4` and its source code is
> like bellow:
> ```
> long bpf_kfunc_call_test4(signed char a, short b, int c, long d) __ksym;
> int kfunc_call_test4(struct __sk_buff *skb)
> {
> 	...
> 	tmp = bpf_kfunc_call_test4(-3, -30, -200, -1000);
> 	...
> }
> ```
>
> And its corresponding asm code is:
> ```
> 0: r1 = -3
> 1: r2 = -30
> 2: r3 = 0xffffff38 # opcode: 18 03 00 00 38 ff ff ff 00 00 00 00 00 00 00 00
> 4: r4 = -1000
> 5: call bpf_kfunc_call_test4
> ```
>
> insn 2 is parsed to ld_imm64 insn to emit 0x00000000ffffff38 imm, and
> converted to int type and then send to bpf_kfunc_call_test4. But since
> it is zero-extended in the bpf calling convention, riscv jit will
> directly treat it as an unsigned 32-bit int value, and then fails with
> the message "actual 4294966063 != expected -1234".
>
> The reason is the incompatibility between bpf and riscv abi, that is,
> bpf will do zero-extension on uint, but riscv64 requires sign-extension
> on int or uint. We can solve this problem by sign extending the 32-bit
> parameters in kfunc.
>
> The issue is related to [0], and thanks to Yonghong and Alexei.
>
> Link: https://github.com/llvm/llvm-project/pull/84874 [0]
> Fixes: d40c3847b485 ("riscv, bpf: Add kfunc support for RV64")
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
> ---
>  arch/riscv/net/bpf_jit_comp64.c | 16 ++++++++++++++++
>  1 file changed, 16 insertions(+)
>
> diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
> index 869e4282a2c4..e3fc39370f7d 100644
> --- a/arch/riscv/net/bpf_jit_comp64.c
> +++ b/arch/riscv/net/bpf_jit_comp64.c
> @@ -1454,6 +1454,22 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
>  		if (ret < 0)
>  			return ret;
>  
> +		if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
> +			const struct btf_func_model *fm;
> +			int idx;
> +
> +			fm = bpf_jit_find_kfunc_model(ctx->prog, insn);
> +			if (!fm)
> +				return -EINVAL;
> +
> +			for (idx = 0; idx < fm->nr_args; idx++) {
> +				u8 reg = bpf_to_rv_reg(BPF_REG_1 + idx, ctx);
> +
> +				if (fm->arg_size[idx] == sizeof(int))
> +					emit_sextw(reg, reg, ctx);
> +			}
> +		}
> +
>  		ret = emit_call(addr, fixed_addr, ctx);
>  		if (ret)
>  			return ret;
> -- 
> 2.34.1

Thanks for doing this, it fixes the issue I was seeing with arena_htab
selftest after enabling arena on RISCV.

Tested-by: Puranjay Mohan <puranjay12@gmail.com>
Reviewed-by: Puranjay Mohan <puranjay12@gmail.com>
Alexei Starovoitov March 24, 2024, 6:40 p.m. UTC | #2
On Sun, Mar 24, 2024 at 3:32 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
>
> From: Pu Lehui <pulehui@huawei.com>
>
> We encountered a failing case when running selftest in no_alu32 mode:
>
> The failure case is `kfunc_call/kfunc_call_test4` and its source code is
> like bellow:
> ```
> long bpf_kfunc_call_test4(signed char a, short b, int c, long d) __ksym;
> int kfunc_call_test4(struct __sk_buff *skb)
> {
>         ...
>         tmp = bpf_kfunc_call_test4(-3, -30, -200, -1000);
>         ...
> }
> ```
>
> And its corresponding asm code is:
> ```
> 0: r1 = -3
> 1: r2 = -30
> 2: r3 = 0xffffff38 # opcode: 18 03 00 00 38 ff ff ff 00 00 00 00 00 00 00 00
> 4: r4 = -1000
> 5: call bpf_kfunc_call_test4
> ```
>
> insn 2 is parsed to ld_imm64 insn to emit 0x00000000ffffff38 imm, and
> converted to int type and then send to bpf_kfunc_call_test4. But since
> it is zero-extended in the bpf calling convention, riscv jit will
> directly treat it as an unsigned 32-bit int value, and then fails with
> the message "actual 4294966063 != expected -1234".
>
> The reason is the incompatibility between bpf and riscv abi, that is,
> bpf will do zero-extension on uint, but riscv64 requires sign-extension
> on int or uint. We can solve this problem by sign extending the 32-bit
> parameters in kfunc.
>
> The issue is related to [0], and thanks to Yonghong and Alexei.
>
> Link: https://github.com/llvm/llvm-project/pull/84874 [0]
> Fixes: d40c3847b485 ("riscv, bpf: Add kfunc support for RV64")
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
> ---
>  arch/riscv/net/bpf_jit_comp64.c | 16 ++++++++++++++++
>  1 file changed, 16 insertions(+)
>
> diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
> index 869e4282a2c4..e3fc39370f7d 100644
> --- a/arch/riscv/net/bpf_jit_comp64.c
> +++ b/arch/riscv/net/bpf_jit_comp64.c
> @@ -1454,6 +1454,22 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
>                 if (ret < 0)
>                         return ret;
>
> +               if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
> +                       const struct btf_func_model *fm;
> +                       int idx;
> +
> +                       fm = bpf_jit_find_kfunc_model(ctx->prog, insn);
> +                       if (!fm)
> +                               return -EINVAL;
> +
> +                       for (idx = 0; idx < fm->nr_args; idx++) {
> +                               u8 reg = bpf_to_rv_reg(BPF_REG_1 + idx, ctx);
> +
> +                               if (fm->arg_size[idx] == sizeof(int))
> +                                       emit_sextw(reg, reg, ctx);
> +                       }
> +               }
> +

The btf_func_model usage looks good.
Glad that no new flags were necessary, since both int and uint
need to be sign extend the existing arg_size was enough.

Since we're at it. Do we need to do zero extension of return value ?
There is
__bpf_kfunc int bpf_kfunc_call_test2(struct sock *sk, u32 a, u32 b)
but the selftest with it is too simple:
        return bpf_kfunc_call_test2((struct sock *)sk, 1, 2);

Could you extend this selftest with a return of large int/uint
with 31th bit set to force sign extension in native
kernel risc-v code ?
I suspect the bpf side will be confused.
Which would mean that risc-v JIT in addition to:
        if (insn->src_reg != BPF_PSEUDO_CALL)
            emit_mv(bpf_to_rv_reg(BPF_REG_0, ctx), RV_REG_A0, ctx);

need to conditionally do:
 if (fm->ret_size == sizeof(int))
   emit_zextw(bpf_to_rv_reg(BPF_REG_0, ctx),
              bpf_to_rv_reg(BPF_REG_0, ctx), ctx);
?
Pu Lehui March 25, 2024, 3:27 p.m. UTC | #3
On 2024/3/25 2:40, Alexei Starovoitov wrote:
> On Sun, Mar 24, 2024 at 3:32 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
[SNIP]
>>
>> diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
>> index 869e4282a2c4..e3fc39370f7d 100644
>> --- a/arch/riscv/net/bpf_jit_comp64.c
>> +++ b/arch/riscv/net/bpf_jit_comp64.c
>> @@ -1454,6 +1454,22 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
>>                  if (ret < 0)
>>                          return ret;
>>
>> +               if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
>> +                       const struct btf_func_model *fm;
>> +                       int idx;
>> +
>> +                       fm = bpf_jit_find_kfunc_model(ctx->prog, insn);
>> +                       if (!fm)
>> +                               return -EINVAL;
>> +
>> +                       for (idx = 0; idx < fm->nr_args; idx++) {
>> +                               u8 reg = bpf_to_rv_reg(BPF_REG_1 + idx, ctx);
>> +
>> +                               if (fm->arg_size[idx] == sizeof(int))
>> +                                       emit_sextw(reg, reg, ctx);
>> +                       }
>> +               }
>> +
> 
> The btf_func_model usage looks good.
> Glad that no new flags were necessary, since both int and uint
> need to be sign extend the existing arg_size was enough.
> 
> Since we're at it. Do we need to do zero extension of return value ?
> There is
> __bpf_kfunc int bpf_kfunc_call_test2(struct sock *sk, u32 a, u32 b)
> but the selftest with it is too simple:
>          return bpf_kfunc_call_test2((struct sock *)sk, 1, 2); >
> Could you extend this selftest with a return of large int/uint
> with 31th bit set to force sign extension in native

Sorry for late. riscv64 will sign-extend int/uint return values. I 
thought this would be a good test, so I tried the following:
```
u32 bpf_kfunc_call_test2(u32 a, u32 b) __ksym; <-- here change int to u32
int kfunc_call_test2(struct __sk_buff *skb)
{
	long tmp;
	
	tmp = bpf_kfunc_call_test2(0xfffffff0, 2);
	return (tmp >> 32) + tmp;
}
```
As expected, if the return value is sign-extended, the bpf program will 
return 0xfffffff1. If the return value is zero-extended, the bpf program 
will return 0xfffffff2. But in fact, riscv returns 0xfffffff2. Upon 
further discovery, it seems clang will compensate for unsigned return 
values. Curious!
for example:
```
u32 bpf_kfunc_call_test2(u32 a, u32 b) __ksym;
int kfunc_call_test2(struct __sk_buff *skb)
{
	long tmp;
	
	tmp = bpf_kfunc_call_test2(0xfffffff0, 2);
	bpf_printk("tmp: 0x%lx", tmp);
	return (tmp >> 32) + tmp;
}
```
and the bytecode will be:
```
  0:       18 01 00 00 00 00 00 f0 00 00 00 00 00 00 00 00 r1 = 
0xf0000000 ll
  2:       b7 02 00 00 02 00 00 00 r2 = 0x2
  3:       85 10 00 00 ff ff ff ff call -0x1
  4:       bf 06 00 00 00 00 00 00 r6 = r0
  5:       bf 63 00 00 00 00 00 00 r3 = r6
  6:       67 03 00 00 20 00 00 00 r3 <<= 0x20 <-- zero extension
  7:       77 03 00 00 20 00 00 00 r3 >>= 0x20
  8:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll
10:       b7 02 00 00 0b 00 00 00 r2 = 0xb
11:       85 00 00 00 06 00 00 00 call 0x6
12:       bf 60 00 00 00 00 00 00 r0 = r6
13:       95 00 00 00 00 00 00 00 exit
```

another example:
```
u32 bpf_kfunc_call_test2(u32 a, u32 b) __ksym;
int kfunc_call_test2(struct __sk_buff *skb)
{
	long tmp;
	
	tmp = bpf_kfunc_call_test2(0xfffffff0, 2);
	return (tmp >> 20) + tmp; <-- change from 32 to 20
}
```
and the bytecode will be:
```
  0:       18 01 00 00 00 00 00 f0 00 00 00 00 00 00 00 00 r1 = 
0xf0000000 ll
  2:       b7 02 00 00 02 00 00 00 r2 = 0x2
  3:       85 10 00 00 ff ff ff ff call -0x1
  4:       18 02 00 00 00 00 f0 ff 00 00 00 00 00 00 00 00 r2 = 
0xfff00000 ll <-- 32-bit truncation
  6:       bf 01 00 00 00 00 00 00 r1 = r0
  7:       5f 21 00 00 00 00 00 00 r1 &= r2
  8:       77 01 00 00 14 00 00 00 r1 >>= 0x14
  9:       0f 01 00 00 00 00 00 00 r1 += r0
10:       bf 10 00 00 00 00 00 00 r0 = r1
11:       95 00 00 00 00 00 00 00 exit
```

It is difficult to construct this test case.

> kernel risc-v code ?
> I suspect the bpf side will be confused.
> Which would mean that risc-v JIT in addition to:
>          if (insn->src_reg != BPF_PSEUDO_CALL)
>              emit_mv(bpf_to_rv_reg(BPF_REG_0, ctx), RV_REG_A0, ctx);
> 
> need to conditionally do:
>   if (fm->ret_size == sizeof(int))
>     emit_zextw(bpf_to_rv_reg(BPF_REG_0, ctx),
>                bpf_to_rv_reg(BPF_REG_0, ctx), ctx);
> ?

Agree on zero-extending int/uint return values ​​when returning from 
kfunc to bpf ctx. I will add it in next version. Thanks.
Alexei Starovoitov March 25, 2024, 6:34 p.m. UTC | #4
On Mon, Mar 25, 2024 at 8:28 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
>
>
>
> On 2024/3/25 2:40, Alexei Starovoitov wrote:
> > On Sun, Mar 24, 2024 at 3:32 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
> [SNIP]
> >>
> >> diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
> >> index 869e4282a2c4..e3fc39370f7d 100644
> >> --- a/arch/riscv/net/bpf_jit_comp64.c
> >> +++ b/arch/riscv/net/bpf_jit_comp64.c
> >> @@ -1454,6 +1454,22 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
> >>                  if (ret < 0)
> >>                          return ret;
> >>
> >> +               if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
> >> +                       const struct btf_func_model *fm;
> >> +                       int idx;
> >> +
> >> +                       fm = bpf_jit_find_kfunc_model(ctx->prog, insn);
> >> +                       if (!fm)
> >> +                               return -EINVAL;
> >> +
> >> +                       for (idx = 0; idx < fm->nr_args; idx++) {
> >> +                               u8 reg = bpf_to_rv_reg(BPF_REG_1 + idx, ctx);
> >> +
> >> +                               if (fm->arg_size[idx] == sizeof(int))
> >> +                                       emit_sextw(reg, reg, ctx);
> >> +                       }
> >> +               }
> >> +
> >
> > The btf_func_model usage looks good.
> > Glad that no new flags were necessary, since both int and uint
> > need to be sign extend the existing arg_size was enough.
> >
> > Since we're at it. Do we need to do zero extension of return value ?
> > There is
> > __bpf_kfunc int bpf_kfunc_call_test2(struct sock *sk, u32 a, u32 b)
> > but the selftest with it is too simple:
> >          return bpf_kfunc_call_test2((struct sock *)sk, 1, 2); >
> > Could you extend this selftest with a return of large int/uint
> > with 31th bit set to force sign extension in native
>
> Sorry for late. riscv64 will sign-extend int/uint return values. I
> thought this would be a good test, so I tried the following:
> ```
> u32 bpf_kfunc_call_test2(u32 a, u32 b) __ksym; <-- here change int to u32
> int kfunc_call_test2(struct __sk_buff *skb)
> {
>         long tmp;
>
>         tmp = bpf_kfunc_call_test2(0xfffffff0, 2);
>         return (tmp >> 32) + tmp;
> }
> ```
> As expected, if the return value is sign-extended, the bpf program will
> return 0xfffffff1. If the return value is zero-extended, the bpf program
> will return 0xfffffff2. But in fact, riscv returns 0xfffffff2. Upon
> further discovery, it seems clang will compensate for unsigned return
> values. Curious!
> for example:
> ```
> u32 bpf_kfunc_call_test2(u32 a, u32 b) __ksym;
> int kfunc_call_test2(struct __sk_buff *skb)
> {
>         long tmp;
>
>         tmp = bpf_kfunc_call_test2(0xfffffff0, 2);
>         bpf_printk("tmp: 0x%lx", tmp);
>         return (tmp >> 32) + tmp;
> }
> ```
> and the bytecode will be:
> ```
>   0:       18 01 00 00 00 00 00 f0 00 00 00 00 00 00 00 00 r1 =
> 0xf0000000 ll
>   2:       b7 02 00 00 02 00 00 00 r2 = 0x2
>   3:       85 10 00 00 ff ff ff ff call -0x1
>   4:       bf 06 00 00 00 00 00 00 r6 = r0
>   5:       bf 63 00 00 00 00 00 00 r3 = r6
>   6:       67 03 00 00 20 00 00 00 r3 <<= 0x20 <-- zero extension
>   7:       77 03 00 00 20 00 00 00 r3 >>= 0x20
>   8:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll
> 10:       b7 02 00 00 0b 00 00 00 r2 = 0xb
> 11:       85 00 00 00 06 00 00 00 call 0x6
> 12:       bf 60 00 00 00 00 00 00 r0 = r6
> 13:       95 00 00 00 00 00 00 00 exit
> ```
>
> another example:
> ```
> u32 bpf_kfunc_call_test2(u32 a, u32 b) __ksym;
> int kfunc_call_test2(struct __sk_buff *skb)
> {
>         long tmp;
>
>         tmp = bpf_kfunc_call_test2(0xfffffff0, 2);
>         return (tmp >> 20) + tmp; <-- change from 32 to 20
> }
> ```
> and the bytecode will be:
> ```
>   0:       18 01 00 00 00 00 00 f0 00 00 00 00 00 00 00 00 r1 =
> 0xf0000000 ll
>   2:       b7 02 00 00 02 00 00 00 r2 = 0x2
>   3:       85 10 00 00 ff ff ff ff call -0x1
>   4:       18 02 00 00 00 00 f0 ff 00 00 00 00 00 00 00 00 r2 =
> 0xfff00000 ll <-- 32-bit truncation
>   6:       bf 01 00 00 00 00 00 00 r1 = r0
>   7:       5f 21 00 00 00 00 00 00 r1 &= r2
>   8:       77 01 00 00 14 00 00 00 r1 >>= 0x14
>   9:       0f 01 00 00 00 00 00 00 r1 += r0
> 10:       bf 10 00 00 00 00 00 00 r0 = r1
> 11:       95 00 00 00 00 00 00 00 exit
> ```
>
> It is difficult to construct this test case.

Yeah.
I also tried a bunch of experiments with llvm and gcc-bpf.
Both compilers emit zero extension when u32 is being used as u64.

> > kernel risc-v code ?
> > I suspect the bpf side will be confused.
> > Which would mean that risc-v JIT in addition to:
> >          if (insn->src_reg != BPF_PSEUDO_CALL)
> >              emit_mv(bpf_to_rv_reg(BPF_REG_0, ctx), RV_REG_A0, ctx);
> >
> > need to conditionally do:
> >   if (fm->ret_size == sizeof(int))
> >     emit_zextw(bpf_to_rv_reg(BPF_REG_0, ctx),
> >                bpf_to_rv_reg(BPF_REG_0, ctx), ctx);
> > ?
>
> Agree on zero-extending int/uint return values when returning from
> kfunc to bpf ctx. I will add it in next version. Thanks.

Looking at existing compilers behavior it's probably unnecessary.
I think this patch is fine as-is.
I'll apply it shortly.
Pu Lehui March 26, 2024, 1:32 a.m. UTC | #5
On 2024/3/26 2:34, Alexei Starovoitov wrote:
> On Mon, Mar 25, 2024 at 8:28 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
>>
>>
>>
>> On 2024/3/25 2:40, Alexei Starovoitov wrote:
>>> On Sun, Mar 24, 2024 at 3:32 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
>> [SNIP]
>>>>
>>>> diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
>>>> index 869e4282a2c4..e3fc39370f7d 100644
>>>> --- a/arch/riscv/net/bpf_jit_comp64.c
>>>> +++ b/arch/riscv/net/bpf_jit_comp64.c
>>>> @@ -1454,6 +1454,22 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
>>>>                   if (ret < 0)
>>>>                           return ret;
>>>>
>>>> +               if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
>>>> +                       const struct btf_func_model *fm;
>>>> +                       int idx;
>>>> +
>>>> +                       fm = bpf_jit_find_kfunc_model(ctx->prog, insn);
>>>> +                       if (!fm)
>>>> +                               return -EINVAL;
>>>> +
>>>> +                       for (idx = 0; idx < fm->nr_args; idx++) {
>>>> +                               u8 reg = bpf_to_rv_reg(BPF_REG_1 + idx, ctx);
>>>> +
>>>> +                               if (fm->arg_size[idx] == sizeof(int))
>>>> +                                       emit_sextw(reg, reg, ctx);
>>>> +                       }
>>>> +               }
>>>> +
>>>
>>> The btf_func_model usage looks good.
>>> Glad that no new flags were necessary, since both int and uint
>>> need to be sign extend the existing arg_size was enough.
>>>
>>> Since we're at it. Do we need to do zero extension of return value ?
>>> There is
>>> __bpf_kfunc int bpf_kfunc_call_test2(struct sock *sk, u32 a, u32 b)
>>> but the selftest with it is too simple:
>>>           return bpf_kfunc_call_test2((struct sock *)sk, 1, 2); >
>>> Could you extend this selftest with a return of large int/uint
>>> with 31th bit set to force sign extension in native
>>
>> Sorry for late. riscv64 will sign-extend int/uint return values. I
>> thought this would be a good test, so I tried the following:
>> ```
>> u32 bpf_kfunc_call_test2(u32 a, u32 b) __ksym; <-- here change int to u32
>> int kfunc_call_test2(struct __sk_buff *skb)
>> {
>>          long tmp;
>>
>>          tmp = bpf_kfunc_call_test2(0xfffffff0, 2);
>>          return (tmp >> 32) + tmp;
>> }
>> ```
>> As expected, if the return value is sign-extended, the bpf program will
>> return 0xfffffff1. If the return value is zero-extended, the bpf program
>> will return 0xfffffff2. But in fact, riscv returns 0xfffffff2. Upon
>> further discovery, it seems clang will compensate for unsigned return
>> values. Curious!
>> for example:
>> ```
>> u32 bpf_kfunc_call_test2(u32 a, u32 b) __ksym;
>> int kfunc_call_test2(struct __sk_buff *skb)
>> {
>>          long tmp;
>>
>>          tmp = bpf_kfunc_call_test2(0xfffffff0, 2);
>>          bpf_printk("tmp: 0x%lx", tmp);
>>          return (tmp >> 32) + tmp;
>> }
>> ```
>> and the bytecode will be:
>> ```
>>    0:       18 01 00 00 00 00 00 f0 00 00 00 00 00 00 00 00 r1 =
>> 0xf0000000 ll
>>    2:       b7 02 00 00 02 00 00 00 r2 = 0x2
>>    3:       85 10 00 00 ff ff ff ff call -0x1
>>    4:       bf 06 00 00 00 00 00 00 r6 = r0
>>    5:       bf 63 00 00 00 00 00 00 r3 = r6
>>    6:       67 03 00 00 20 00 00 00 r3 <<= 0x20 <-- zero extension
>>    7:       77 03 00 00 20 00 00 00 r3 >>= 0x20
>>    8:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll
>> 10:       b7 02 00 00 0b 00 00 00 r2 = 0xb
>> 11:       85 00 00 00 06 00 00 00 call 0x6
>> 12:       bf 60 00 00 00 00 00 00 r0 = r6
>> 13:       95 00 00 00 00 00 00 00 exit
>> ```
>>
>> another example:
>> ```
>> u32 bpf_kfunc_call_test2(u32 a, u32 b) __ksym;
>> int kfunc_call_test2(struct __sk_buff *skb)
>> {
>>          long tmp;
>>
>>          tmp = bpf_kfunc_call_test2(0xfffffff0, 2);
>>          return (tmp >> 20) + tmp; <-- change from 32 to 20
>> }
>> ```
>> and the bytecode will be:
>> ```
>>    0:       18 01 00 00 00 00 00 f0 00 00 00 00 00 00 00 00 r1 =
>> 0xf0000000 ll
>>    2:       b7 02 00 00 02 00 00 00 r2 = 0x2
>>    3:       85 10 00 00 ff ff ff ff call -0x1
>>    4:       18 02 00 00 00 00 f0 ff 00 00 00 00 00 00 00 00 r2 =
>> 0xfff00000 ll <-- 32-bit truncation
>>    6:       bf 01 00 00 00 00 00 00 r1 = r0
>>    7:       5f 21 00 00 00 00 00 00 r1 &= r2
>>    8:       77 01 00 00 14 00 00 00 r1 >>= 0x14
>>    9:       0f 01 00 00 00 00 00 00 r1 += r0
>> 10:       bf 10 00 00 00 00 00 00 r0 = r1
>> 11:       95 00 00 00 00 00 00 00 exit
>> ```
>>
>> It is difficult to construct this test case.
> 
> Yeah.
> I also tried a bunch of experiments with llvm and gcc-bpf.
> Both compilers emit zero extension when u32 is being used as u64.
> 
>>> kernel risc-v code ?
>>> I suspect the bpf side will be confused.
>>> Which would mean that risc-v JIT in addition to:
>>>           if (insn->src_reg != BPF_PSEUDO_CALL)
>>>               emit_mv(bpf_to_rv_reg(BPF_REG_0, ctx), RV_REG_A0, ctx);
>>>
>>> need to conditionally do:
>>>    if (fm->ret_size == sizeof(int))
>>>      emit_zextw(bpf_to_rv_reg(BPF_REG_0, ctx),
>>>                 bpf_to_rv_reg(BPF_REG_0, ctx), ctx);
>>> ?
>>
>> Agree on zero-extending int/uint return values when returning from
>> kfunc to bpf ctx. I will add it in next version. Thanks.
> 
> Looking at existing compilers behavior it's probably unnecessary.
> I think this patch is fine as-is.
> I'll apply it shortly.

Alright, feel free to apply it. Thanks
diff mbox series

Patch

diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
index 869e4282a2c4..e3fc39370f7d 100644
--- a/arch/riscv/net/bpf_jit_comp64.c
+++ b/arch/riscv/net/bpf_jit_comp64.c
@@ -1454,6 +1454,22 @@  int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 		if (ret < 0)
 			return ret;
 
+		if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
+			const struct btf_func_model *fm;
+			int idx;
+
+			fm = bpf_jit_find_kfunc_model(ctx->prog, insn);
+			if (!fm)
+				return -EINVAL;
+
+			for (idx = 0; idx < fm->nr_args; idx++) {
+				u8 reg = bpf_to_rv_reg(BPF_REG_1 + idx, ctx);
+
+				if (fm->arg_size[idx] == sizeof(int))
+					emit_sextw(reg, reg, ctx);
+			}
+		}
+
 		ret = emit_call(addr, fixed_addr, ctx);
 		if (ret)
 			return ret;