[all-commits] [llvm/llvm-project] 891b8f: [BPF] crash fix for memcpy with unsupported alignm...

Claire Fan via All-commits all-commits at lists.llvm.org
Thu May 28 23:11:11 PDT 2026


  Branch: refs/heads/main
  Home:   https://github.com/llvm/llvm-project
  Commit: 891b8f6fa8e796464b34fa9460933fccf69f3cc3
      https://github.com/llvm/llvm-project/commit/891b8f6fa8e796464b34fa9460933fccf69f3cc3
  Author: Claire Fan <fanyungching at gmail.com>
  Date:   2026-05-28 (Thu, 28 May 2026)

  Changed paths:
    M llvm/lib/Target/BPF/BPFSelectionDAGInfo.cpp
    M llvm/lib/Target/BPF/BPFSelectionDAGInfo.h
    A llvm/test/CodeGen/BPF/libcall-memcpy-threshold.ll

  Log Message:
  -----------
  [BPF] crash fix for memcpy with unsupported alignment (#199541)

### Summary
BPF only has scalar load/store opcodes up to 8 bytes and `memcpy` with
16 byte alignment crashes with `llvm_unreachable("unsupported memcpy
alignment");` in `BPFInstrInfo::expandMEMCPY`

### Issue
The crash can be reproduced from Rust code like:

```rust
#[no_mangle]
pub extern "C" fn entry(src: *const u128) -> u64 {
    let mut dst = [0u128; 2];
    unsafe { ptr::copy_nonoverlapping(src, dst.as_mut_ptr(), 2) };
    let _ = black_box(dst);
    0
}
```

This produces a `memcpy` with 16-byte alignment and
`BPFSelectionDAGInfo::EmitTargetCodeForMemcpy` emit the `BPFISD::MEMCPY`
pseudo for 16-byte alignment. That pseudo later reaches `expandMEMCPY`
during the Post-RA pseudo expansion pass and hits:
```
llvm_unreachable("unsupported memcpy alignment");
```

The crash report looks like:
```
= note: PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/
        and include the crash backtrace and instructions to reproduce the bug.
        Stack dump:
        0. Running pass 'Function Pass Manager' on module 'linked_module'.
        1. Running pass 'Post-RA pseudo instruction expansion pass' on function '@entry'
```
### Fix
Return `SDValue()` in `BPFSelectionDAGInfo::EmitTargetCodeForMemcpy`
when the alignment is larger than 8 bytes. LLVM then naturally falls
back to the existing libcall path and emits `call memcpy` instead of
creating a pseudo instruction that cannot be expanded later.

Since `memcpy`, `memset`, and `memmove` are now whitelisted, I think
this fallback behavior is the right fix: unsupported target expansion
should decline and use the existing libcall path instead of crashing.

### Opt-in controls around libcalls
I also want to use this chance to introduce two opt-in controls around
libcalls for VM/runtime use cases built on top of the BPF backend (both
controls are opt-in and disabled by default, so existing kernel projects
and current BPF behavior remain unchanged),

### --bpf-max-stores-per-memfunc
First, an opt-in flag to control over
`BPFSelectionDAGInfo::getCommonMaxStoresPerMemFunc()`.

Today, the target memory expansion path uses:

```c++
if (StoresNumEstimate > getCommonMaxStoresPerMemFunc())
  return SDValue();
```

This threshold controls whether `EmitTargetCodeForMemcpy` continues
emitting the BPF memory pseudo(which is later expanded with load/store
ops) or falls back to the libcall path.

Currently this value is effectively fixed at 128, which means most
constant-size memory operations are expanded into inline load/store
sequences. Exposing the threshold gives VM/runtime users a way to allow
more memory operations to fall through to libcalls, while keeping the
current default behavior unchanged.

### --bpf-allows-libcalls (PR #199542 )
Second, an opt-in flag to allow additional libcalls beyond the currently
whitelisted ones.

Today BPF rejects unsupported libcalls here:

```c++
if (Sym != BPF_TRAP && Sym != "__multi3" && Sym != "__divti3" &&
    Sym != "__modti3" && Sym != "__udivti3" &&
    Sym != "__umodti3" && Sym != "memcpy" &&
    Sym != "memset" && Sym != "memmove")
  fail(
      CLI.DL, DAG,
      Twine("A call to built-in function '" + Sym +
            "' is not supported."));
```

With the new flag enabled, _this error path is skipped_ so VM/runtime
environments can provide their own libcall implementations.

For VM runtimes using the BPF backend, libcalls are useful because they
create an interface between normal compiler-generated code and
runtime-specific optimization. We have already explored this model for
u128 arithmetic, where libcalls allow normal Rust code to stay unchanged
while the runtime can provide optimized behavior:


https://blueshift.gg/research/accelerating-u128-math-with-libcalls-and-jit-intrinsics

The same idea applies to memory operations. Users can continue writing
normal Rust like `ptr::copy_nonoverlapping`, while the runtime can
choose an implementation tailored to its execution environment.

Both controls are opt-in and disabled by default, so existing kernel
projects and current BPF behavior remain unchanged.



To unsubscribe from these emails, change your notification settings at https://github.com/llvm/llvm-project/settings/notifications


More information about the All-commits mailing list