Skip to content

Miscompilation of Bevy (and some wgpu) apps resulting in segfault on macOS. #117902

Description

@inodentry

Recent rustc seems to miscompile something somewhere in the dependency tree of Bevy, causing apps to crash with a segfault.

The issue seems to only manifest when optimizations are enabled and codegen units are > 1. The default cargo release configuration triggers it. Unoptimized builds are fine. Setting codegen-units = 1 with opt-level = 3 also seems to be fine.

I am on macOS 14.1 Sonoma, on Apple M1 Pro hardware.

Code

You can reproduce with any Bevy application. It is a huge code base, I know, I'm sorry I can't find a more minimal example.

Clone the Bevy repo: https://github.com/bevyengine/bevy

all these revs have the issue:

  • commit bf4f4e42da3da07b0e84a4d6e40077f7398aea8b (i had that one laying around)
  • commit 749f3d74305d35b4a453a80e45b22f08d5189a48 (current latest)
  • tag v0.12.0
  • tag v0.11.3

Run some example in release mode:

cargo run --example bevymark --release

Version it worked on

The previous 1.74.0-beta.7 did not have the issue. I am not sure which nightly introduced the regression.

Version with regression

I first noticed the issue when I installed the 2023-11-11 nightly, though it might also be present in earlier nightlies. I just installed 1.75.0-beta.1 and updated my nightly, and can confirm the issue is present there, too.

rustc 1.75.0-beta.1 (782883f60 2023-11-12)
binary: rustc
commit-hash: 782883f609713fe9617ba64d90086742ec62d374
commit-date: 2023-11-12
host: aarch64-apple-darwin
release: 1.75.0-beta.1
LLVM version: 17.0.4
rustc 1.76.0-nightly (ba7c7a301 2023-11-13)
binary: rustc
commit-hash: ba7c7a301984967c8c13adb580ef9b86ba706a83
commit-date: 2023-11-13
host: aarch64-apple-darwin
release: 1.76.0-nightly
LLVM version: 17.0.4

Activity

  1. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Nov 14, 2023
  2. Noratrieb commented on Nov 14, 2023

    @Noratrieb
  3. inodentry commented on Nov 14, 2023

    @inodentry
    Author
  4. Noratrieb commented on Nov 14, 2023

    @Noratrieb
  5. added
    E-needs-bisectionCall for participation: This issue needs bisection: https://github.com/rust-lang/cargo-bisect-rustc
    on Nov 14, 2023
  6. added
    E-needs-mcveCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Nov 14, 2023
  7. Noratrieb commented on Nov 14, 2023

    @Noratrieb
    Member

    It would be useful if you could use cargo-bisect-rustc to bisect the regression to a single PR, which would make it easier to figure out what's happening and whether it really is a miscompilation or just UB that's now going wrong in bevy's dep tree.

  8. Noratrieb commented on Nov 14, 2023

    @Noratrieb
    Member

    I was not able to reproduce this on Windows. Marking as O-MacOS, but I suspect that the problem is platform-independent and just only manifests in platform-specific bevy code.

  9. inodentry commented on Nov 14, 2023

    @inodentry
    Author

    OK. TIL about cargo-bisect-rustc. Installing it and reading the documentation now. Will try it in a moment.

    Edit: I created a simple bevy project that should automatically exit after the first frame is rendered to the screen. This should be scriptable. It will exit successfully on older rustc, or segfault on new rustc. cargo bisect-rustc is running now, as I am typing this.

    Here is the bevy code if anyone else wants to try it:

    use bevy::prelude::*;
    fn main() {
        App::new()
            .add_plugins(DefaultPlugins)
            .add_systems(Update, exit)
            .run();
    }
    fn exit(mut evw: EventWriter<bevy::app::AppExit>) {
        evw.send(bevy::app::AppExit);
    }
  10. 42 remaining items

  11. saethlin commented on Dec 3, 2023

    @saethlin
    Member

    Yes that should turn off the MIR inliner. I was aiming for a reproducer that has -Zmir-opt-level=0 because that would turn off all MIR optimizations, and I found that when I added that flag I stopped getting segfaults unless I enabled MIR inlining (which is a pass, but it also has its own special flag).

  12. lqd commented on Dec 3, 2023

    @lqd
    Member

    Maybe the smaller repro behaves slightly differently now, -Zinline-mir=no -Zmir-opt-level=0 still segfaults for me, interestingly enough.

  13. ActuallyHappening commented on Dec 3, 2023

    @ActuallyHappening

    It's nice to know I'm not the only one experiencing random segfaults in my bevy apps on nightly, and that it is being actively explored!
    I have no rustc dev experience to offer, but I did notice another compiler bug (original) here, but I'm pretty sure they are unrelated.

  14. dianqk commented on Dec 4, 2023

    @dianqk
    Member

    In any case, the LLVM optimization pass that seems to start introducing the segfault is Machine Copy Propagation Pass on function (repro_117902::TextureFormat::required_features) from the lib — RUSTFLAGS="-Cllvm-args=-opt-bisect-limit=1255" to reproduce this on the latest revision (with -Zinline-mir=no or -Zinline-mir=no -Zmir-opt-level=0, the limit is 1404 on the lib).

    cc @dianqk

    Nice reproduction. But I can't reproduce it on macOS x86_64. Purchasing M1.
    I have written very less Machine IR related code. But I'll try to look into it when the M1 arrives.

  15. dianqk commented on Dec 7, 2023

    @dianqk
    Member

    Upstream issue: llvm/llvm-project#74680.
    I tried to submit a fix as well.

    Base on https://github.com/lqd/repro-117902, I got

    fn main() {
        call_from_main();
    }
    
    #[inline(never)]
    #[no_mangle]
    fn call_from_main() {
        let desc = TextureDescriptor(TextureFormat::Bc6hRgbUfloat);
        device_create_texture(desc);
    }
    
    #[inline(never)]
    #[no_mangle]
    pub fn device_create_texture(desc: TextureDescriptor) {
        let format = desc.0;
        format.required_features().used();
        format.used();
    }
    
    enum Feature {
        A,
        B,
    }
    
    impl Feature {
        #[inline(never)]
        fn used(self) {
            core::hint::black_box(self);
        }
    }
    
    #[repr(C)]
    pub enum AstcBlock {
        B12x12,
    }
    pub enum AstcChannel {
        Unorm,
        Hdr,
    }
    #[repr(C)]
    pub enum TextureFormat {
        Rgba8UnormSrgb,
        Bc6hRgbUfloat,
        Key,
        Astc {
            channel: AstcChannel,
            block: AstcBlock,
        },
    }
    impl TextureFormat {
        #[inline(never)]
        fn used(&self) {
            core::hint::black_box(self);
        }
        #[inline(never)]
        // #[no_mangle]
        fn required_features(&self) -> Feature {
            match self {
                Self::Rgba8UnormSrgb => Feature::A,
                Self::Bc6hRgbUfloat => Feature::B,
                Self::Key => Feature::B,
                Self::Astc { channel, .. } => match channel {
                    AstcChannel::Hdr => Feature::A,
                    AstcChannel::Unorm => Feature::B,
                },
            }
        }
    }
    pub struct TextureDescriptor(pub TextureFormat);
  16. dianqk commented on Dec 8, 2023

    @dianqk
    Member

    Hmm, I can only use nightly to reproduce, and I can't use local stage1/stage2 to reproduce. This could be a difference in build configuration. So I think this minimal reproduction is unstable. If we want to use this as a test case, I would expect a more stable test case.

    I added a more detailed explanation of this issue at llvm/llvm-project#74682 (comment). We should be able to create a must-be-dirty stack to reproduce.

  17. dianqk commented on Dec 8, 2023

    @dianqk
    Member

    I got a "perfect" test case.

    I can reproduce it with nightly, 1.74.1 and even 1.69.0.

    fn main() {
        let desc = TextureDescriptor(TextureFormat::A, 1, 1);
        device_create_texture(desc);
    }
    
    #[inline(never)]
    #[no_mangle]
    pub fn device_create_texture(desc: TextureDescriptor) {
        let format = desc.0;
        assert_eq!(format.required_features(desc.1), Feature::A);
    }
    
    #[derive(PartialEq, Eq, Debug)]
    enum Feature {
        A,
        B,
    }
    
    #[repr(i32)]
    pub enum TextureFormat {
        A,
        B,
        C,
        D,
    }
    
    impl TextureFormat {
        #[inline(never)]
        fn required_features(&self, v: i8) -> Feature {
            match self {
                Self::A => Feature::A,
                Self::B => Feature::B,
                Self::C => Feature::B,
                Self::D => match v {
                    0 => Feature::A,
                    _ => Feature::B,
                },
            }
        }
    }
    
    pub struct TextureDescriptor(pub TextureFormat, i8, i16);

    By examining the generated instructions, the following instruction sets x0 to 0x0001000100000000:

    mov     x0, #4294967296
    movk    x0, #1, lsl #48

    godbolt: https://rust.godbolt.org/z/vnnvEszoP

  18. removed their assignment
    on Dec 10, 2023
  19. added a commit that references this issue on Dec 15, 2023
    604f185
  20. lqd commented on Dec 15, 2023

    @lqd
    Member

    Reopening to track beta backport in #118994.

  21. reopened this on Dec 15, 2023
  22. lqd commented on Dec 16, 2023

    @lqd
    Member

    This can now be closed: this issue was fixed on nightly by the cherry-picked LLVM fix in #118936, and fixed on beta by backporting that PR in #118994, so it thankfully won't reach stable in a couple weeks. Thanks in particular to @saethlin and @dianqk.

  23. removed
    E-needs-mcveCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example
    on Mar 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessO-macosOperating system: macOSP-criticalCritical priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.regression-from-stable-to-betaPerformance or correctness regression from stable to beta.

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions