Skip to content

Conversation

@LucasSte
Copy link
Contributor

Problem

In Rust, checked math functions (like checked_mul, overflowing_mul, saturating_mul) are part of the primitive implementation of integers (see u64 for instance). The Rust compiler builds the Rust compiler-builtins crate as a step in the compilation processes, since it contains the math builtins to be lowered in the target.

For BPF, however, when using those functions in Rust we hit the following errors:

ERROR llvm: <unknown>:0:0: in function func i64 (i64, i64): A call to built-in function '__multi3' is not supported.

ERROR llvm: <unknown>:0:0: in function func i64 (i64, i64): only small returns supported

Those errors come from the following code:

pub fn func(a: u64, b: u64) -> u64 {
    a.saturating_mul(b)
}

Those functions invoke underneath the llvm instrinc { i64, i1 } @llvm.umul.with.overflow.i64(i64, i64) or its variants.

It is very useful to use safe math operations when writing BPF code in Rust, and I would like to add support for those in the target.

Changes

  1. Create a target feature allow-builtin-calls to enable code generation for builtin functions.
  2. Implement CanLowerReturn to fix the error only small returns supported.
  3. Add code to correctly invoke lib functions.
  4. Add a test case together with the corresponding C code.

@github-actions
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@LucasSte LucasSte changed the title Allow libcalls in BPF (behind a feature gate) [BPF] Allow libcalls behind a feature gate Nov 18, 2025
@LucasSte
Copy link
Contributor Author

@yonghong-song gentle ping

Copy link
Contributor

@vadorovsky vadorovsky left a comment

Choose a reason for hiding this comment

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

Great stuff!

Could you add the [BPF] tag and description to your commit message as well?

Comment on lines 571 to 575
if (!AllowBuiltinCalls) {
fail(CLI.DL, DAG,
Twine("A call to built-in function '" + StringRef(E->getSymbol()) +
"' is not supported."));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: usually if statements with one-liners are written without braces in the BPF backend code:

Suggested change
if (!AllowBuiltinCalls) {
fail(CLI.DL, DAG,
Twine("A call to built-in function '" + StringRef(E->getSymbol()) +
"' is not supported."));
}
if (!AllowBuiltinCalls)
fail(CLI.DL, DAG,
Twine("A call to built-in function '" + StringRef(E->getSymbol()) +
"' is not supported."));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this is a simple change, I'll add it to the existing commit in this PR and force push it again with the correct title you asked for.

@github-actions
Copy link

github-actions bot commented Nov 18, 2025

🐧 Linux x64 Test Results

  • 186429 tests passed
  • 4864 tests skipped

Copy link
Contributor

@yonghong-song yonghong-song left a comment

Choose a reason for hiding this comment

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

Please explain how bpf program handles builtin function like __multi3() in rust environment. Currently the builtin function like __multi3() won't work for libbpf and kernel as there is no actual function for __multi3() in final bpf program.

EmitInstrWithCustomInserterLDimm64(MachineInstr &MI,
MachineBasicBlock *BB) const;

/// Returns true if arguments should be sign-extended in lib calls.
Copy link
Contributor

Choose a reason for hiding this comment

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

Let us do

// Returns true if arguments should be sign-extended in lib calls.

similar to the comments in previous lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did I miss anything or is your suggestion the same thing as what I wrote there already?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, sorry, I didn't see you were using two slashes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed the comment in the squashed commit.

; CHECK: r5 s>>= 63
; CHECK: r1 = r10
; CHECK: r1 += -16
; CHECK: call __multi3
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you explain in the commit/description message how do you handle __multi3 function? This is clang built-in so you will implement this function in the bpf prog?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When we build the core library in Rust, which implements the primitive types and their math operations, we also build as a dependency Rust's compiler-builtin crate. This crate has the implementation of __multi3 (see https://github.com/rust-lang/compiler-builtins/blob/eba1a3f3d2824739f07fe434624b0d2fee917d89/compiler-builtins/src/int/mul.rs#L108-L110).

Once we build the target program and all the dependencies (core and compiler-builtins), we merge all LLVM modules and emit a final object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this message clear for the commit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I updated the commit message. Let me know if it is clear. I'm happy to re-write it.

; RUN: not llc -mtriple=bpf < %s 2> %t1
; RUN: FileCheck %s < %t1
; CHECK: only small returns
; CHECK: aggregate returns are not supported
Copy link
Contributor

Choose a reason for hiding this comment

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

For struct_ret2.ll, the following is the error message:

$ llc -march=bpf test/CodeGen/BPF/struct_ret2.ll
error: <unknown>:0:0: in function foo { i64, i32 } (i32, i32, i32): 0x3d86ac0: i64 = GlobalAddress<ptr @bar> 0 too many arguments

error: <unknown>:0:0: in function foo { i64, i32 } (i32, i32, i32): aggregate returns are not supported

I suggest to have the first error message for CHECK, i.e., 'too many arguments" which is caused by CanLowerReturn().

Please also update struct_ret1.ll like below:

$ llc -march=bpf test/CodeGen/BPF/struct_ret1.ll
error: <unknown>:0:0: in function bar { i64, i32 } (i32, i32, i32, i32, i32): stack arguments are not supported

error: <unknown>:0:0: in function bar { i64, i32 } (i32, i32, i32, i32, i32): aggregate returns are not supported

error: <unknown>:0:0: in function baz void (ptr): aggregate returns are not supported

Similarly, I would like the error message in CHECK should be the first error message.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed both tests in the squashed commit.

When we build the core library in Rust, which implements the primitive
types an their math operations, we also build as a dependency Rust's
compiler-builtin[0] crate. This crate has the implementation of __multi3
[1].

Once we build the target program and all the dependencies (core and
compiler-builtins), we merge all LLVM modules and emit a final object.

[0] https://github.com/rust-lang/compiler-builtins

[1] https://github.com/rust-lang/compiler-builtins/blob/eba1a3f3d2824739f07fe434624b0d2fee917d89/compiler-builtins/src/int/mul.rs#L108-L110
Copy link
Contributor

@yonghong-song yonghong-song left a comment

Choose a reason for hiding this comment

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

Thanks, LGTM.

@LucasSte
Copy link
Contributor Author

@yonghong-song Thanks for the approval.

Is there any other feedback I need to address to get this PR merged? I don't have permissions to merge it myself.

@yonghong-song yonghong-song merged commit 23907a2 into llvm:main Nov 24, 2025
10 checks passed
@github-actions
Copy link

@LucasSte Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

@yonghong-song
Copy link
Contributor

Is there any other feedback I need to address to get this PR merged? I don't have permissions to merge it myself.

Looks like there are no other coments. Just merged.

@arsenm
Copy link
Contributor

arsenm commented Nov 24, 2025

Create a target feature allow-builtin-calls to enable code generation for builtin functions.

This should not be a subtarget feature

Copy link
Contributor

@arsenm arsenm left a comment

Choose a reason for hiding this comment

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

This is the wrong solution to the problem. BPF isn't correctly configuring its set of RuntimeLibcalls. If you removed the function from the default set, you should hit the software expansion without hitting the external call.

Whether or not the libcall is available should be treated as an ABI property, preferably directly implied by the triple without a function-level control like a subtarget feature

SmallVector<CCValAssign, 16> RVLocs;
CCState CCInfo(CallConv, IsVarArg, MF, RVLocs, Context);
return CCInfo.CheckReturn(Outs, getHasAlu32() ? RetCC_BPF32 : RetCC_BPF64);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

End of file whitespace error

return IsSigned || Ty->isIntegerTy(32);
}

bool BPFTargetLowering::CanLowerReturn(
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be a separate change

@@ -1196,3 +1198,18 @@ bool BPFTargetLowering::isLegalAddressingMode(const DataLayout &DL,

return true;
}

bool BPFTargetLowering::shouldSignExtendTypeInLibCall(Type *Ty,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a separate change

@LucasSte
Copy link
Contributor Author

This is the wrong solution to the problem. BPF isn't correctly configuring its set of RuntimeLibcalls. If you removed the function from the default set, you should hit the software expansion without hitting the external call.

Are you suggesting that we create an entry in RuntimeLibcalls.td to specify which lib calls are not available in BPF?

Whether or not the libcall is available should be treated as an ABI property, preferably directly implied by the triple without a function-level control like a subtarget feature

When building a Rust program for BPF we do build the compiler-builtins crate, so ultimately we link everything together. Should we have a specific setting to identify we are building a program for Rust?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants