Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Clang][BPF] Allow sign extension for call parameters with int types #84874

Closed
wants to merge 1 commit into from

Conversation

yonghong-song
Copy link
Contributor

@yonghong-song yonghong-song commented Mar 12, 2024

Pu Lehui (pulehui@huaweicloud.com) reported an issue in private that at no_alu32 mode clang may generate code which produced incorrect result with riscv architecture.

The affected bpf prog is kfunc_call_test4 at bpf selftests prog/kfunc_call_test.c. The following is the source code:

  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);
        ...
  }

For the above code, at no_alu32 mode (-mcpu=v2), the asm code looks like
0: r1 = -3
1: r2 = -30
2: r3 = 0xffffff38 ll // 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

In bpf_kfunc_call_test4(), arguments with 'char', 'short' and 'long' are generated correctly, for r1, r2 and r4 in the above. But the argument r3 is generated with ld_imm64.

Further investigation is found that

  • char/short type arguments are signed extended so naturally using MOV insn
  • int type argument are zero extended so using ld_imm64 insn
  • long type argument can do sign extension with 32-bit value so using MOV insn

In riscv case, the 'r3' value (0xffffff38 ll) will be passed to riscv kernel code which does not do 32-bit sign extension and caused incorrect result.

Why intel/arm64 does not have this issue? x86_64/arm64 supports subrgisters so for 'int' types, subregisters are directly used hence there is no issue.

Considering BPF is a 64-bit arch, so I think it makes sense at IR level
'int' type argument should have the same sign-extension as 'char' or 'short'
type. This will solve the above riscv issue.

This patch will cause two codegen changes:
- for an 'int' constant argument, a MOV insn will be used instead of a ld_imm64.
- for an 'int' register argument, for cpu=v1/v2, left/right shift will happen.
for cpu=v3/v4, there is no change from previous behavior as subregisters will
be used.

Tested with bpf selftests with all of no-alu32, cpu=v3 and cpu=v4, and all passed.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen labels Mar 12, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Mar 12, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-codegen

Author: None (yonghong-song)

Changes

Pu Lehui (pulehui@huaweicloud.com) reported an issue in private that at no_alu32 mode clang may generate code which produced incorrect result with riscv architecture.

The affected bpf prog is kfunc_call_test4 at bpf selftests prog/kfunc_call_test.c. The following is the source code:

  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);
        ...
  }

For the above code, at no_alu32 mode (-mcpu=v2), the asm code looks like
0: r1 = -3
1: r2 = -30
2: r3 = 0xffffff38 ll // 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

In bpf_kfunc_call_test4(), arguments with 'char', 'short' and 'long' are generated correctly, for r1, r2 and r4 in the above. But the argument r3 is generated with ld_imm64.

Further investigation is found that

  • char/short type arguments are signed extended so naturally using MOV insn
  • int type argument are zero extended so using ld_imm64 insn
  • long type argument can do sign extension with 32-bit value so using MOV insn

In riscv case, the 'r3' value (0xffffff38 ll) will be passed to riscv kernel code which does not do 32-bit sign extension and caused incorrect result.

Why intel/arm64 does not have this issue? x86_64/arm64 supports subrgisters so for 'int' types, subregisters are directly used hence there is no issue.

Considering BPF is a 64-bit arch, so I think it makes sense at IR level 'int' type argument should have the same sign-extension as 'char' or 'short' type. This will solve the above riscv issue.

This patch will cause two codegen changes:

  • for an 'int' constant argument, a MOV insn will be used instead of a ld_imm64.
  • for an 'int' register argument, for cpu=v1/v2, left/right shift will happen. for cpu=v3/v4, there is no change from previous behavior as subregisters will be used.

Tested with bpf selftests with all of no-alu32, cpu=v3 and cpu=v4, and all passed.


Full diff: https://github.com/llvm/llvm-project/pull/84874.diff

3 Files Affected:

  • (modified) clang/lib/CodeGen/Targets/BPF.cpp (+13)
  • (modified) clang/test/CodeGen/bpf-abiinfo.c (+10)
  • (added) llvm/test/CodeGen/BPF/cc_args_int.ll (+34)
diff --git a/clang/lib/CodeGen/Targets/BPF.cpp b/clang/lib/CodeGen/Targets/BPF.cpp
index 2849222f7a1869..01937574779618 100644
--- a/clang/lib/CodeGen/Targets/BPF.cpp
+++ b/clang/lib/CodeGen/Targets/BPF.cpp
@@ -22,6 +22,19 @@ class BPFABIInfo : public DefaultABIInfo {
 public:
   BPFABIInfo(CodeGenTypes &CGT) : DefaultABIInfo(CGT) {}
 
+  bool isPromotableIntegerTypeForABI(QualType Ty) const {
+    if (ABIInfo::isPromotableIntegerTypeForABI(Ty) == true)
+      return true;
+
+    if (const auto *BT = Ty->getAs<BuiltinType>()) {
+      // For 'signed int' type, return true to allow sign-extension.
+      if (BT->getKind() == BuiltinType::Int)
+        return true;
+    }
+
+    return false;
+  }
+
   ABIArgInfo classifyArgumentType(QualType Ty) const {
     Ty = useFirstFieldIfTransparentUnion(Ty);
 
diff --git a/clang/test/CodeGen/bpf-abiinfo.c b/clang/test/CodeGen/bpf-abiinfo.c
index 366e8003f45572..6d259d8e6d6c73 100644
--- a/clang/test/CodeGen/bpf-abiinfo.c
+++ b/clang/test/CodeGen/bpf-abiinfo.c
@@ -22,3 +22,13 @@ int foo_int(void) {
         if (bar_int() != 10) return 0; else return 1;
 }
 // CHECK: %call = call i32 @bar_int()
+
+void sprog1(short, int, int);
+void mprog1() {
+  sprog1(-3, 4, -5);
+// CHECK: call void @sprog1(i16 noundef signext -3, i32 noundef signext 4, i32 noundef signext -5)
+}
+void mprog2(long a, long b) {
+  sprog1(a, b, b);
+// CHECK: call void @sprog1(i16 noundef signext %{{[0-9a-z]+}}, i32 noundef signext %{{[0-9a-z]+}}, i32 noundef signext %{{[0-9a-z]+}})
+}
diff --git a/llvm/test/CodeGen/BPF/cc_args_int.ll b/llvm/test/CodeGen/BPF/cc_args_int.ll
new file mode 100644
index 00000000000000..79a9d27b87d709
--- /dev/null
+++ b/llvm/test/CodeGen/BPF/cc_args_int.ll
@@ -0,0 +1,34 @@
+; RUN: llc -march=bpfel -mcpu=v1 < %s | FileCheck --check-prefix=CHECK-V1 %s
+; RUN: llc -march=bpfel -mcpu=v2 < %s | FileCheck --check-prefix=CHECK-V2 %s
+; RUN: llc -march=bpfel -mcpu=v3 < %s | FileCheck --check-prefix=CHECK-V3 %s
+; RUN: llc -march=bpfel -mcpu=v4 < %s | FileCheck --check-prefix=CHECK-V4 %s
+
+declare dso_local void @bar(i16 noundef signext, i32 noundef signext, i32 noundef signext) local_unnamed_addr
+
+define void @test() {
+entry:
+  tail call void @bar(i16 noundef signext -3, i32 noundef signext 4, i32 noundef signext -5)
+; CHECK-V1:     r2 = 4
+; CHECK-V1:     r3 = -5
+; CHECK-V2:     r2 = 4
+; CHECK-V2:     r3 = -5
+; CHECK-V3:     w2 = 4
+; CHECK-V3:     w3 = -5
+; CHECK-V4:     w2 = 4
+; CHECK-V4:     w3 = -5
+  ret void
+}
+
+define dso_local void @test2(i64 noundef %a, i64 noundef %b) local_unnamed_addr {
+entry:
+  %conv = trunc i64 %a to i16
+  %conv1 = trunc i64 %b to i32
+  tail call void @bar(i16 noundef signext %conv, i32 noundef signext %conv1, i32 noundef signext %conv1)
+; CHECK-V1:     r2 <<= 32
+; CHECK-V1:     r2 s>>= 32
+; CHECK-V2:     r2 <<= 32
+; CHECK-V2:     r2 s>>= 32
+; CHECK-V3-NOT: r2 <<= 32
+; CHECK-V4-NOT: r2 <<= 32
+  ret void
+}

@yonghong-song
Copy link
Contributor Author

cc @anakryiko @jemarch

@pulehui
Copy link

pulehui commented Mar 12, 2024

Thanks Yonghong. It works through the kfunc_call case in no_alu32 mode, as well as other cases in riscv64.
Tested-by: Pu Lehui pulehui@huawei.com


if (const auto *BT = Ty->getAs<BuiltinType>()) {
// For 'signed int' type, return true to allow sign-extension.
if (BT->getKind() == BuiltinType::Int)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should BuiltinType::UInt be included as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should BuiltinType::UInt be included as well?

Yes, make sense. I will add that as well.

Pu Lehui (pulehui@huaweicloud.com) reported an issue in private
that at no_alu32 mode clang may generate code which produced
incorrect result with riscv architecture.

The affected bpf prog is kfunc_call_test4 at bpf selftests
prog/kfunc_call_test.c. The following is the source code:

  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);
        ...
  }

For the above code, at no_alu32 mode (-mcpu=v2), the asm code looks like
  0: r1 = -3
  1: r2 = -30
  2: r3 = 0xffffff38 ll // 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

In bpf_kfunc_call_test4(), arguments with 'char', 'short' and 'long' are
generated correctly, for r1, r2 and r4 in the above. But the argument
r3 is generated with ld_imm64.

Further investigation is found that
  - char/short type arguments are signed extended so naturally using MOV insn
  - int type argument are zero extended so using ld_imm64 insn
  - long type argument can do sign extension with 32-bit value so using MOV insn

In riscv case, the 'r3' value (0xffffff38 ll) will be passed to riscv kernel
code which does not do 32-bit sign extension and caused incorrect result.

Why intel/arm64 does not have this issue? x86_64/arm64 supports subrgisters
so for 'int' types, subregisters are directly used hence there is no issue.

Considering BPF is a 64-bit arch, so I think it makes sense at IR level
'int' type argument should have the same sign-extension as 'char' or 'short'
type. This will solve the above riscv issue.

This patch will cause two codegen changes:
  - for an 'int' constant argument, a MOV insn will be used instead of a ld_imm64.
  - for an 'int' register argument, for cpu=v1/v2, left/right shift will happen.
    for cpu=v3/v4, there is no change from previous behavior as subregisters will
    be used.

Tested with bpf selftests with all of no-alu32, cpu=v3 and cpu=v4, and
all passed.

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
@efriedma-quic
Copy link
Collaborator

The way LLVM backend works in general is that if an integer is smaller than a register, when type legalization converts that to a larger integer, the high bits are undefined. This generally doesn't cause any issues because nothing can actually observe those bits. Your analysis that concludes "int" is special is probably wrong; I think you're just getting lucky with heuristics for lowering constants.

Some backends explicitly zero-extend or sign-extend function arguments to match ABI conventions, at least in some cases. The BPF backend is one of those backends: BPFTargetLowering::LowerCall will sign-extend arguments marked "signext".

Sign-extending 32-bit integers is a choice you can make, I guess, but if you're not trying to conform to an external ABI specification, it doesn't really solve anything.

@yonghong-song yonghong-song changed the title [Clang][BPF] Allow sign extension for int type call parameters [Clang][BPF] Allow sign/zero extension for call parameters with int/uint types Mar 12, 2024
Copy link

github-actions bot commented Mar 12, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@yonghong-song
Copy link
Contributor Author

@efriedma-quic Thanks for your comments. BPF llvm backend and kernel BPF jit try to make BPF<->other_architecture compatible. riscv started to support BPF starting in 2020 and probably BPF has limited usage so the bug is exposed until now.
Yes, BPFTargetLowering::LowerCall does do sign-extend arguments marked "signext", and this can help BPF<->other_architecture too. Previously only for char/short (to cover architectures which only have 32bit subregister). Now we found int type also needs support.
BPF community will continuously monitor any potential inter-arch incompatibility issue and once found will address them promptly.

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is making a bit more sense now... I didn't realize this was overriding a method that handles char/short already. And I guess for BPF targets, you can assume "long" is 64 bits.

That said, this isn't handling "unsigned int" correctly. "unsigned int" is supposed to be sign-extended on RISC-V targets. (How you resolve the conflict with PowerPC, where "unsigned int" is supposed to be zero-extended, I'm not sure.)

@eddyz87
Copy link
Contributor

eddyz87 commented Mar 13, 2024

Hi @efriedma-quic,

That said, this isn't handling "unsigned int" correctly. "unsigned int" is supposed to be sign-extended on RISC-V targets. (How you resolve the conflict with PowerPC, where "unsigned int" is supposed to be zero-extended, I'm not sure.)

Could you please elaborate a bit?
My understanding is that you refer to the following part of the specification:

In RV64, 32-bit types, such as int, are stored in integer registers as proper sign extensions of their
32-bit values; that is, bits 63..31 are all equal. This restriction holds even for unsigned 32-bit types.

I assume that sign extension operations for RV64 take care of this detail.
The way I understand this pull request -- clang would now guarantee sign or zero extension for all integral function parameters. Given that extension would be done according to the platform rules, why do you think this could be an issue?

@yonghong-song
Copy link
Contributor Author

The following is an example to show 'unsigned int' case. The current behavior (i.e., without this patch):

$ cat u.c
void foo(unsigned);
void bar(unsigned a, unsigned b) {
  foo(a + b);
}
$ clang --target=bpf -O2 -c u.c && llvm-objdump -d u.o

u.o:    file format elf64-bpf

Disassembly of section .text:

0000000000000000 <bar>:
       0:       0f 12 00 00 00 00 00 00 r2 += r1
       1:       bf 21 00 00 00 00 00 00 r1 = r2
       2:       85 10 00 00 ff ff ff ff call -0x1
       3:       95 00 00 00 00 00 00 00 exit

Basically, we didn't do zero extension for 'uint' argument in the above. We expect the callee 'foo' to do proper zero extension. If both 'foo' and 'far' are functions written in bpf programs, we should be okay. But if 'foo' is a kfunc (e.g., a kernel function for a specific arch e.g. riscv) and the arch will just use the full register 'r1' to do operation without zero extension of 32-bit value, the wrong results could happen.

With this patch, we will have the following asm code,

$ clang --target=bpf -O2 -c u.c && llvm-objdump -d u.o

u.o:    file format elf64-bpf

Disassembly of section .text:

0000000000000000 <bar>:
       0:       0f 12 00 00 00 00 00 00 r2 += r1
       1:       67 02 00 00 20 00 00 00 r2 <<= 0x20
       2:       77 02 00 00 20 00 00 00 r2 >>= 0x20
       3:       bf 21 00 00 00 00 00 00 r1 = r2
       4:       85 10 00 00 ff ff ff ff call -0x1
       5:       95 00 00 00 00 00 00 00 exit

That is, we will do zero extension for uint argument here. This will guarantee correct value if 'foo' is a kernel function. If 'foo' is not a kernel function, the 'foo' will be able to use r2 directly as a 32-bit value without doing shifting, one example like below

long g1, g2;
__attribute__((noinline)) void foo(unsigned a) {
  if (a == g1) g2 = 1;
  else g2 = 2;  
}

....
0000000000000000 <foo>:
       0:       18 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r2 = 0x0 ll
       2:       79 23 00 00 00 00 00 00 r3 = *(u64 *)(r2 + 0x0)
       3:       b7 02 00 00 01 00 00 00 r2 = 0x1
       4:       1d 13 01 00 00 00 00 00 if r3 == r1 goto +0x1 <LBB0_2>
       5:       b7 02 00 00 02 00 00 00 r2 = 0x2

0000000000000030 <LBB0_2>:
       6:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll
       8:       7b 21 00 00 00 00 00 00 *(u64 *)(r1 + 0x0) = r2
       9:       95 00 00 00 00 00 00 00 exit

@yonghong-song
Copy link
Contributor Author

@pulehui Could you check whether with -mcpu=v2 (no-alu32 mode) we have 'unsigned int' related issue or not? Specifically, given a 'unsigned int' does riscv use subregister to access 32-bit value, or use 64-bit register to access the value without zero-extension of 32-bit value?

@efriedma-quic
Copy link
Collaborator

My understanding is that you refer to the following part of the specification:

Yes. For example, on RISC-V, the function long f(unsigned g) { return (int)g; } compiles to just a "ret".

clang would now guarantee sign or zero extension for all integral function parameters.

clang would guarantee sign-extension for signed parameters, and zero-extension for unsigned parameters. Which is not the same as the RISCV ABI rules.

That is, we will do zero extension for uint argument here. This will guarantee correct value if 'foo' is a kernel function.

A kernel function will expect the uint to be sign-extended, not zero-extended.

@yonghong-song
Copy link
Contributor Author

yonghong-song commented Mar 13, 2024

For this one:

A kernel function will expect the uint to be sign-extended, not zero-extended.

I suspect riscv bpf jit will do sign-extension. @pulehui can confirm. This may be true for 32-bit subregisters, but I am not sure whether this is true for full register or not.

@pulehui
Copy link

pulehui commented Mar 13, 2024

A kernel function will expect the uint to be sign-extended, not zero-extended.

I suspect riscv bpf jit will do sign-extension. @pulehui can confirm. This may be true for 32-bit subregisters, but I am not sure whether this is true for full register or not.

Sorry for late.

riscv64 will do sign-extension for 'unsigned int' and riscv64 bpf JIT will do the same. We need to explicitly zero-extend it. But I don't have problems with ‘unsigned int‘ before this patch.

void bpf_kfunc_call_test4(unsigned int a) __ksym;
int kfunc_call_test4(struct __sk_buff *skb) {
unsigned int a = xxx;
bpf_kfunc_call_test4(a);
}

if a = 2147483647(0x7FFFFFFFU, bit 31 is 0), it will be a mov insn. It will be valid 'unsigned int' in kfunc after sign-extension.

0:       b7 01 00 00 ff ff ff 7f r1 = 0x7fffffff
1:       85 10 00 00 ff ff ff ff call -0x1

but if a = 4294967294(0xFFFFFFFEU, bit 31 is 1), it will be a ld_imm64 insn. And it also be valid 'unsigned int' in kfunc.

0:       18 01 00 00 fe ff ff ff 00 00 00 00 00 00 00 00 r1 = 0xfffffffe ll
2:       85 10 00 00 ff ff ff ff call -0x1

But after this patch, the two numbers get the same result as before this patch. I don't see ld_imm64 becoming a mov instruction, as well as explicit zero extension for 'unsigned int'. Is that expected?

@eddyz87
Copy link
Contributor

eddyz87 commented Mar 13, 2024

@yonghong-song , if I understand @efriedma-quic correctly (thank you for explanations), the following fragment is not RISC-V ABI conformant:

$ cat u.c
void foo(unsigned);
void bar(unsigned a, unsigned b) {
  foo(a + b);
}

       1:       67 02 00 00 20 00 00 00 r2 <<= 0x20
       2:       77 02 00 00 20 00 00 00 r2 >>= 0x20
       3:       bf 21 00 00 00 00 00 00 r1 = r2
       4:       85 10 00 00 ff ff ff ff call -0x1

The r1 would be zero extended, while to be ABI conformant it has to be sign extended, as for RV64 the 31-st bit should be the same as upper 32 bits. The fact that decision to zero or sign extend the argument depends on the architecture means that this extension has to be done at JIT time (meaning that BTF is mandatory) or be a part of kfunc.

@yonghong-song
Copy link
Contributor Author

But after this patch, the two numbers get the same result as before this patch. I don't see ld_imm64 becoming a mov instruction, as well as explicit zero extension for 'unsigned int'. Is that expected?

IIUC, we have the same result before and after this patch. So this patch didn't make things worse or better. So didn't really solve your problem for 'unsigned int'.

@yonghong-song
Copy link
Contributor Author

yonghong-song commented Mar 13, 2024

@yonghong-song , if I understand @efriedma-quic correctly (thank you for explanations), the following fragment is not RISC-V ABI conformant:

$ cat u.c
void foo(unsigned);
void bar(unsigned a, unsigned b) {
  foo(a + b);
}

       1:       67 02 00 00 20 00 00 00 r2 <<= 0x20
       2:       77 02 00 00 20 00 00 00 r2 >>= 0x20
       3:       bf 21 00 00 00 00 00 00 r1 = r2
       4:       85 10 00 00 ff ff ff ff call -0x1

The r1 would be zero extended, while to be ABI conformant it has to be sign extended, as for RV64 the 31-st bit should be the same as upper 32 bits. The fact that decision to zero or sign extend the argument depends on the architecture means that this extension has to be done at JIT time (meaning that BTF is mandatory) or be a part of kfunc.

I do not know how riscv handles the above additional left/right shift and not sure whether it introduced additional issue comparing to without left/right shift.

I guess I will remove UInt support from this patch since it does not improve anything and may risk break something. We can add it back later if needed and other issue (with riscv) is clarified/resolved.

@yonghong-song
Copy link
Contributor Author

Just uploaded a new revision which targets 'Int' type only, i.e, v1.

@yonghong-song yonghong-song changed the title [Clang][BPF] Allow sign/zero extension for call parameters with int/uint types [Clang][BPF] Allow sign extension for call parameters with int types Mar 13, 2024
@yonghong-song
Copy link
Contributor Author

@pulehui On top of this patch, the following is a hack to emit 'sign-extension' for uint types (unsigned char/short not affected).

The llvm hack:

diff --git a/llvm/lib/Target/BPF/BPFISelLowering.cpp b/llvm/lib/Target/BPF/BPFISelLowering.cpp
index b8ca8ec98c06..e4ab04c99c60 100644
--- a/llvm/lib/Target/BPF/BPFISelLowering.cpp
+++ b/llvm/lib/Target/BPF/BPFISelLowering.cpp
@@ -37,6 +37,10 @@ static cl::opt<bool> BPFExpandMemcpyInOrder("bpf-expand-memcpy-in-order",
   cl::Hidden, cl::init(false),
   cl::desc("Expand memcpy into load/store pairs in order"));
 
+static cl::opt<bool> BPFForRISCV("bpf-for-riscv",
+  cl::Hidden, cl::init(false),
+  cl::desc("BPF prog intended to run on riscv arch"));
+
 static void fail(const SDLoc &DL, SelectionDAG &DAG, const Twine &Msg,
                  SDValue Val = {}) {
   std::string Str;
@@ -239,6 +243,12 @@ bool BPFTargetLowering::isZExtFree(SDValue Val, EVT VT2) const {
   return TargetLoweringBase::isZExtFree(Val, VT2);
 }
 
+bool BPFTargetLowering::isSExtCheaperThanZExt(EVT SrcVT, EVT DstVT) const {
+  if (BPFForRISCV)
+    return SrcVT == MVT::i32 && DstVT == MVT::i64;
+  return TargetLoweringBase::isSExtCheaperThanZExt(SrcVT, DstVT);
+}
+
 BPFTargetLowering::ConstraintType
 BPFTargetLowering::getConstraintType(StringRef Constraint) const {
   if (Constraint.size() == 1) {
diff --git a/llvm/lib/Target/BPF/BPFISelLowering.h b/llvm/lib/Target/BPF/BPFISelLowering.h
index 42707949e864..e157a402b6e0 100644
--- a/llvm/lib/Target/BPF/BPFISelLowering.h
+++ b/llvm/lib/Target/BPF/BPFISelLowering.h
@@ -152,6 +152,7 @@ private:
   bool isZExtFree(Type *Ty1, Type *Ty2) const override;
   bool isZExtFree(EVT VT1, EVT VT2) const override;
   bool isZExtFree(SDValue Val, EVT VT2) const override;
+  bool isSExtCheaperThanZExt(EVT SrcVT, EVT DstVT) const override;
 
   unsigned EmitSubregExt(MachineInstr &MI, MachineBasicBlock *BB, unsigned Reg,
                          bool isSigned) const;

The following is a simple example,

$ cat t.c
void bar(unsigned int, unsigned int);
void foo() {    
  bar(5, -5);   
}
$ clang --target=bpf -O2 -c t.c && llvm-objdump -d t.o

t.o:    file format elf64-bpf

Disassembly of section .text:

0000000000000000 <foo>:
       0:       b7 01 00 00 05 00 00 00 r1 = 0x5
       1:       18 02 00 00 fb ff ff ff 00 00 00 00 00 00 00 00 r2 = 0xfffffffb ll
       3:       85 10 00 00 ff ff ff ff call -0x1
       4:       95 00 00 00 00 00 00 00 exit
$ clang --target=bpf -O2 -c t.c -mllvm -bpf-for-riscv && llvm-objdump -d t.o

t.o:    file format elf64-bpf

Disassembly of section .text:

0000000000000000 <foo>:
       0:       b7 01 00 00 05 00 00 00 r1 = 0x5
       1:       b7 02 00 00 fb ff ff ff r2 = -0x5
       2:       85 10 00 00 ff ff ff ff call -0x1
       3:       95 00 00 00 00 00 00 00 exit
$

But I really do not like to generate BPF codes for different architectures. Maybe riscv can propose some code patterns where bpf backend can generate generic code (for all arches) and those codes can be easier for riscv jit to do proper sign extension?

@4ast
Copy link
Member

4ast commented Mar 13, 2024

Just uploaded a new revision which targets 'Int' type only, i.e, v1.

Looks like it's a bug in risc-v JIT. We never promised that kfunc call from bpf calling convention into native cpu calling convention will be free on all architectures. That's why we have btf_func_model and JITs suppose to use it when calling conventions don't match. It seems risc-v JIT needs to emit sign extension when 'int' argument is passed into kfunc.
Simple stuff, comparing to what we do in 32-bit x86 JIT where 64-bit bpf program can call into 32-bit x86 kfunc.

@yonghong-song
Copy link
Contributor Author

@pulehui I will not merge this patch now since it does not really solve the whole riscv issue. As @4ast commented above, the verifier made btf_func_model to jit.

struct btf_func_model {
        u8 ret_size;
        u8 ret_flags;
        u8 nr_args;
        u8 arg_size[MAX_BPF_FUNC_ARGS];
        u8 arg_flags[MAX_BPF_FUNC_ARGS];
};

For example, for each argument, you will know its size and flags has some information as well.The flags has

static u8 __get_type_fmodel_flags(const struct btf_type *t)
{
        u8 flags = 0;

        if (__btf_type_is_struct(t))
                flags |= BTF_FMODEL_STRUCT_ARG;
        if (btf_type_is_signed_int(t))
                flags |= BTF_FMODEL_SIGNED_ARG;

        return flags;
}

So you will know whether it is a struct or signed or not. If you feel you need explicit UNSIGNED flag, you can add it in btf as well.

Let us try to resolve the issue in riscv backend. Thanks!

@pulehui
Copy link

pulehui commented Mar 15, 2024

The r1 would be zero extended, while to be ABI conformant it has to be sign extended, as for RV64 the 31-st bit should be the same as upper 32 bits. The fact that decision to zero or sign extend the argument depends on the architecture means that this extension has to be done at JIT time (meaning that BTF is mandatory) or be a part of kfunc.

Thanks for the clarification. riscv64 will do sign-extension both for int and uint. For uint, zero-extension is required, but after my investigation, it will be handled in the callee.

@pulehui
Copy link

pulehui commented Mar 15, 2024

@pulehui I will not merge this patch now since it does not really solve the whole riscv issue. As @4ast commented above, the verifier made btf_func_model to jit.
[snip]
So you will know whether it is a struct or signed or not. If you feel you need explicit UNSIGNED flag, you can add it in btf as well.

Let us try to resolve the issue in riscv backend. Thanks!

Thanks Yonghong and Alexei, will try to resolve this incompatibility between bpf abi and riscv abi.

kernel-patches-daemon-bpf bot pushed a commit to kernel-patches/bpf that referenced this pull request Mar 24, 2024
…v abi

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: llvm/llvm-project#84874 [0]
Fixes: d40c384 ("riscv, bpf: Add kfunc support for RV64")
Signed-off-by: Pu Lehui <pulehui@huawei.com>
kernel-patches-daemon-bpf-rc bot pushed a commit to kernel-patches/bpf-rc that referenced this pull request Mar 24, 2024
…v abi

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: llvm/llvm-project#84874 [0]
Fixes: d40c384 ("riscv, bpf: Add kfunc support for RV64")
Signed-off-by: Pu Lehui <pulehui@huawei.com>
bjoto pushed a commit to linux-riscv/linux-riscv that referenced this pull request Mar 24, 2024
…v abi

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: llvm/llvm-project#84874 [0]
Fixes: d40c384 ("riscv, bpf: Add kfunc support for RV64")
Signed-off-by: Pu Lehui <pulehui@huawei.com>
Signed-off-by: Björn Töpel <bjorn@rivosinc.com>
bjoto pushed a commit to linux-riscv/linux-riscv that referenced this pull request Mar 25, 2024
…v abi

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: llvm/llvm-project#84874 [0]
Fixes: d40c384 ("riscv, bpf: Add kfunc support for RV64")
Signed-off-by: Pu Lehui <pulehui@huawei.com>
Tested-by: Puranjay Mohan <puranjay12@gmail.com>
Reviewed-by: Puranjay Mohan <puranjay12@gmail.com>
Signed-off-by: Björn Töpel <bjorn@rivosinc.com>
puranjaymohan pushed a commit to puranjaymohan/linux that referenced this pull request Mar 25, 2024
…v abi

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: llvm/llvm-project#84874 [0]
Fixes: d40c384 ("riscv, bpf: Add kfunc support for RV64")
Signed-off-by: Pu Lehui <pulehui@huawei.com>
kernel-patches-daemon-bpf-rc bot pushed a commit to kernel-patches/bpf-rc that referenced this pull request Mar 25, 2024
…v abi

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: llvm/llvm-project#84874 [0]
Fixes: d40c384 ("riscv, bpf: Add kfunc support for RV64")
Signed-off-by: Pu Lehui <pulehui@huawei.com>
Tested-by: Puranjay Mohan <puranjay12@gmail.com>
Reviewed-by: Puranjay Mohan <puranjay12@gmail.com>
Link: https://lore.kernel.org/r/20240324103306.2202954-1-pulehui@huaweicloud.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
puranjaymohan pushed a commit to puranjaymohan/linux that referenced this pull request Mar 26, 2024
…v abi

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: llvm/llvm-project#84874 [0]
Fixes: d40c384 ("riscv, bpf: Add kfunc support for RV64")
Signed-off-by: Pu Lehui <pulehui@huawei.com>
luigi311 pushed a commit to luigi311/linux that referenced this pull request Mar 31, 2024
…v abi

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: llvm/llvm-project#84874 [0]
Fixes: d40c384 ("riscv, bpf: Add kfunc support for RV64")
Signed-off-by: Pu Lehui <pulehui@huawei.com>
Tested-by: Puranjay Mohan <puranjay12@gmail.com>
Reviewed-by: Puranjay Mohan <puranjay12@gmail.com>
Link: https://lore.kernel.org/r/20240324103306.2202954-1-pulehui@huaweicloud.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
luigi311 pushed a commit to luigi311/linux that referenced this pull request Mar 31, 2024
…v abi

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: llvm/llvm-project#84874 [0]
Fixes: d40c384 ("riscv, bpf: Add kfunc support for RV64")
Signed-off-by: Pu Lehui <pulehui@huawei.com>
Tested-by: Puranjay Mohan <puranjay12@gmail.com>
Reviewed-by: Puranjay Mohan <puranjay12@gmail.com>
Link: https://lore.kernel.org/r/20240324103306.2202954-1-pulehui@huaweicloud.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:BPF clang:codegen clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants