How can I improve compile times of very large code-generated Rust?

67 points chocol4tebubble

As part of my PhD I'm developing a compiler that takes formal models of instruction set architectures and produces fast simulators of those architectures. So far I can generate Rust code that, when compiled, executes the first instructions of the Linux kernel but the final program is very, very slow.

Unfortunately due to the volume of code generated (>2M LoC), I can only achieve reasonable compile times placing each function in a separate crate (2000 crates currently, 14000 expected) instead of a single file, enabling the parallel frontend, and using the dev profile.

If I enable opt-level=1 compile times grow from 2 minutes to >6 hours (exact time unknown as I haven't actually seen it terminate). From -Z time-passes I can see this is almost all LLVM.

Since Rust has no unstructured control flow, but my internal IR treats functions as a collection of basic blocks, I emit Rust functions which contain a variable storing the current basic block and use a loop and match statement to jump around. Below is an example of such a function, but the real issue are the functions with 1000s of blocks. I've observed that generally as the number of blocks grows, the compile time grows much faster.

My main question would be is there a better representation of this control flow in Rust that will compile faster with optimizations enabled? Does anyone have any other suggestions or advice for improving compile times/optimization? Thanks:)

    pub fn SSAdvance<T: Tracer>(state: &mut State, tracer: &mut T, gs_24616: ()) -> () {
    const BLOCK_0: u32 = 0u32;
    const BLOCK_1: u32 = 1u32;
    const BLOCK_2: u32 = 2u32;
    const BLOCK_3: u32 = 3u32;
    const BLOCK_4: u32 = 4u32;
    const BLOCK_5: u32 = 5u32;
    const BLOCK_6: u32 = 6u32;
    const BLOCK_7: u32 = 7u32;
    const BLOCK_8: u32 = 8u32;
    let mut gs_24618: bool = Default::default();
    let mut gs_24617: bool = Default::default();
    let mut current_block = BLOCK_0;
    loop {
        match current_block {
            BLOCK_0 => {
                // C s_0_0: const #() : ()
                let s_0_0: () = ();
                // S s_0_1: call DebugTarget(s_0_0)
                let s_0_1: u8 = DebugTarget(state, tracer, s_0_0);
                // S s_0_2: call ELUsingAArch32(s_0_1)
                let s_0_2: bool = ELUsingAArch32(state, tracer, s_0_1);
                // S s_0_3: not s_0_2
                let s_0_3: bool = !s_0_2;
                // N s_0_4: branch s_0_3 b8 b1
                current_block = if s_0_3 { BLOCK_8 } else { BLOCK_1 };
            }
            BLOCK_1 => {
                // C s_1_0: const #0u : u1
                let s_1_0: bool = false;
                // D s_1_1: write-var gs#24617 <= s_1_0
                gs_24617 = s_1_0;
                // N s_1_2: jump b2
                current_block = BLOCK_2;
            }
            BLOCK_2 => {
                // D s_2_0: read-var gs#24617:u1
                let s_2_0: bool = gs_24617;
                // N s_2_1: branch s_2_0 b7 b3
                current_block = if s_2_0 { BLOCK_7 } else { BLOCK_3 };
            }
            BLOCK_3 => {
                // C s_3_0: const #0u : u1
                let s_3_0: bool = false;
                // D s_3_1: write-var gs#24618 <= s_3_0
                gs_24618 = s_3_0;
                // N s_3_2: jump b4
                current_block = BLOCK_4;
            }
            BLOCK_4 => {
                // D s_4_0: read-var gs#24618:u1
                let s_4_0: bool = gs_24618;
                // N s_4_1: branch s_4_0 b6 b5
                current_block = if s_4_0 { BLOCK_6 } else { BLOCK_5 };
            }
            BLOCK_5 => {
                // N s_5_0: return
                return;
            }
            BLOCK_6 => {
                // C s_6_0: const #0u : u1
                let s_6_0: bool = false;
                // C s_6_1: const #16856u : u64
                let s_6_1: u64 = 16856;
                // D s_6_2: read-reg s_6_1:struct
                let s_6_2: CompositeTypec98939056e929b9c = {
                    let value = state
                        .read_register::<CompositeTypec98939056e929b9c>(s_6_1 as isize);
                    tracer.read_register(s_6_1 as usize, value);
                    value
                };
                // D s_6_3: mutate-field s_6_2.23 <= s_6_0
                let s_6_3: CompositeTypec98939056e929b9c = {
                    let mut local = s_6_2.clone();
                    local._23 = s_6_0;
                    local
                };
                // N s_6_4: write-reg s_6_1 <= s_6_3
                let s_6_4: () = {
                    state
                        .write_register::<
                            CompositeTypec98939056e929b9c,
                        >(s_6_1 as isize, s_6_3);
                    tracer.write_register(s_6_1 as usize, s_6_3);
                };
                // N s_6_5: return
                return;
            }
            BLOCK_7 => {
                // C s_7_0: const #16879u : u64
                let s_7_0: u64 = 16879;
                // D s_7_1: read-reg s_7_0:u1
                let s_7_1: bool = {
                    let value = state.read_register::<bool>(s_7_0 as isize);
                    tracer.read_register(s_7_0 as usize, value);
                    value
                };
                // C s_7_2: const #1u : u8
                let s_7_2: u8 = 1;
                // D s_7_3: cast zx s_7_1 -> u64
                let s_7_3: u64 = (s_7_1 as u64);
                // D s_7_4: bundle s_7_3, s_7_2
                let s_7_4: Bundle<u64, u8> = Bundle {
                    value: s_7_3,
                    length: s_7_2,
                };
                // C s_7_5: const #1u : u8
                let s_7_5: u8 = 1;
                // C s_7_6: const #1u : u64
                let s_7_6: u64 = 1;
                // D s_7_7: bundle s_7_6, s_7_5
                let s_7_7: Bundle<u64, u8> = Bundle {
                    value: s_7_6,
                    length: s_7_5,
                };
                // D s_7_8: cmp-eq s_7_4 s_7_7
                let s_7_8: bool = ((s_7_4) == (s_7_7));
                // D s_7_9: write-var gs#24618 <= s_7_8
                gs_24618 = s_7_8;
                // N s_7_10: jump b4
                current_block = BLOCK_4;
            }
            BLOCK_8 => {
                // C s_8_0: const #103872u : u32
                let s_8_0: u32 = 103872;
                // D s_8_1: read-reg s_8_0:struct
                let s_8_1: CompositeType5c790c8ef59cc8b2 = {
                    let value = state
                        .read_register::<CompositeType5c790c8ef59cc8b2>(s_8_0 as isize);
                    tracer.read_register(s_8_0 as usize, value);
                    value
                };
                // D s_8_2: call _get_MDSCR_EL1_Type_SS(s_8_1)
                let s_8_2: bool = u_get_MDSCR_EL1_Type_SS(state, tracer, s_8_1);
                // C s_8_3: const #1u : u8
                let s_8_3: u8 = 1;
                // D s_8_4: cast zx s_8_2 -> u64
                let s_8_4: u64 = (s_8_2 as u64);
                // D s_8_5: bundle s_8_4, s_8_3
                let s_8_5: Bundle<u64, u8> = Bundle {
                    value: s_8_4,
                    length: s_8_3,
                };
                // C s_8_6: const #1u : u8
                let s_8_6: u8 = 1;
                // C s_8_7: const #1u : u64
                let s_8_7: u64 = 1;
                // D s_8_8: bundle s_8_7, s_8_6
                let s_8_8: Bundle<u64, u8> = Bundle {
                    value: s_8_7,
                    length: s_8_6,
                };
                // D s_8_9: cmp-eq s_8_5 s_8_8
                let s_8_9: bool = ((s_8_5) == (s_8_8));
                // D s_8_10: write-var gs#24617 <= s_8_9
                gs_24617 = s_8_9;
                // N s_8_11: jump b2
                current_block = BLOCK_2;
            }
            u => panic!("undefined block {u}"),
        }
    }
    }