llvm / llvm/llvm-project

[M68k] Miscompile: compiler_builtins memcpy fills destination with the first source byte

Open
#207,150 1 comment 0 reactions 0 assignees View on GitHub
backend:m68k miscompilation
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Summary

On the M68k backend, `compiler_builtins`' `memcpy` (the one Rust emits `-Zbuild-std-features=compiler-builtins-mem`, and that `<[T]>::copy_from_slice` / `ptr::copy_nonoverlapping` lower to) is **miscompiled**: the destination ends up filled with the **first source byte repeated**, instead of the source bytes. A hand-written index copy loop over the same data is correct, which isolates the fault to the optimized `memcpy` codegen rather than general loops.

This is distinct from #152816 (the `COPY`-kills-CCR bug): it still reproduces after applying the #152816 `copyPhysReg` fix, and #152816's fix does not change this behavior.

## Reproducer

`rustc 1.98.0-nightly (13f1859f2 2026-06-27)`, target `m68k-unknown-none-elf`, `-Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem`, `opt-level=1`:

```rust
#![no_std]
#[no_mangle]
pub extern "C" fn probe(out: *mut u8) -> u32 {
let src: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123"; // 40 distinct-ish bytes
let mut dst = [0u8; 40];
dst.copy_from_slice(src); // -> compiler_builtins memcpy
// copy dst out for inspection
unsafe { core::ptr::copy_nonoverlapping(dst.as_ptr(), out, 40); }
dst[10] as u32 // expected b'A' (0x41); observed b'0' (0x30)
}
```

Observed: `dst` is `"0000000000...."` (all `0x30`, the first source byte) rather than the source string. Returned `dst[10]` is `0x30` instead of `0x41`.

Control (correct): replacing `copy_from_slice` with an explicit loop
```rust
let mut i = 0; while i < src.len() { dst[i] = src[i]; i += 1; }
```
produces the correct bytes. So general codegen and `(An)`/mem access are fine; only the optimized `memcpy` implementation is wrong.

## Impact

This breaks Rust `std` on m68k wholesale once #152816 is worked around: every `String`/`CString`/`format!` buffer and every path passed to the fs layer is corrupted (a `File::create("/tmp/x")` receives an effectively empty/garbled path). With a correct `memcpy` provided by the C runtime instead, `std` runs (Vec/HashMap/format!/time/threads-unsupported all pass).

## Verification

The wrong result is byte-identical on two independent M68000-PRM implementations (a translating JIT and an interpreter, cross-checked against each other on a large corpus), and a gcc-`m68k-elf`-compiled memcpy (also memory-to-memory) over the same data on the same engines is correct — so this is the LLVM-generated code for `compiler_builtins::mem::memcpy`, not an emulation or engine artifact.

Happy to attach the emitted `.s` / LLVM IR for `compiler_builtins::mem::memcpy`, or reduce further, on request.

Contributor guide

Open the contributing guide

Research direction

Reproduce with the shown Rust probe for m68k-unknown-none-elf using -Zbuild-std=core, -Zbuild-std-features=compiler-builtins-mem, and opt-level=1. Start at compiler_builtins::mem::memcpy and inspect its emitted LLVM IR or assembly; done means the destination preserves every source byte and dst[10] returns 0x41, including under both M68000-PRM implementations.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.