Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Semantics of StorageLive in loops #42371

Closed
RalfJung opened this issue Jun 2, 2017 · 17 comments · Fixed by #61872 or #79931
Closed

Semantics of StorageLive in loops #42371

RalfJung opened this issue Jun 2, 2017 · 17 comments · Fixed by #61872 or #79931
Assignees
Labels
A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. A-MIR Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@RalfJung
Copy link
Member

RalfJung commented Jun 2, 2017

This code

fn factorial_loop() -> i64 {
    let mut product = 1;
    let mut i = 1;

    while i <= 10 {
        product *= i;
        i += 1;
    }

    product
}

compiles to

fn factorial_loop() -> i64 {
    let mut _0: i64;                     // return pointer
    scope 1 {
        let mut _1: i64;                 // "product" in scope 1 at src/main.rs:2:9: 2:20
        scope 2 {
            let mut _2: i64;             // "i" in scope 2 at src/main.rs:3:9: 3:14
        }
    }
    let mut _3: ();
    let mut _4: bool;
    let mut _5: i64;
    let mut _6: ();
    let mut _7: i64;
    let mut _8: (i64, bool);
    let mut _9: (i64, bool);
    let mut _10: i64;

    bb0: {
        StorageLive(_1);                 // scope 0 at src/main.rs:2:9: 2:20
        _1 = const 1i64;                 // scope 0 at src/main.rs:2:23: 2:24
        StorageLive(_2);                 // scope 1 at src/main.rs:3:9: 3:14
        _2 = const 1i64;                 // scope 1 at src/main.rs:3:17: 3:18
        goto -> bb1;                     // scope 2 at src/main.rs:5:5: 8:6
    }

    bb1: {
        StorageLive(_4);                 // scope 2 at src/main.rs:5:11: 5:18
        StorageLive(_5);                 // scope 2 at src/main.rs:5:11: 5:12
        _5 = _2;                         // scope 2 at src/main.rs:5:11: 5:12
        _4 = Le(_5, const 10i64);        // scope 2 at src/main.rs:5:11: 5:18
        StorageDead(_5);                 // scope 2 at src/main.rs:5:18: 5:18
        switchInt(_4) -> [0u8: bb2, otherwise: bb3]; // scope 2 at src/main.rs:5:5: 8:6
    }

    bb2: {
        _3 = ();                         // scope 2 at src/main.rs:5:5: 8:6
        StorageDead(_4);                 // scope 2 at src/main.rs:8:6: 8:6
        StorageLive(_10);                // scope 2 at src/main.rs:10:5: 10:12
        _10 = _1;                        // scope 2 at src/main.rs:10:5: 10:12
        _0 = _10;                        // scope 2 at src/main.rs:10:5: 10:12
        StorageDead(_10);                // scope 2 at src/main.rs:10:12: 10:12
        StorageDead(_2);                 // scope 1 at src/main.rs:11:2: 11:2
        StorageDead(_1);                 // scope 0 at src/main.rs:11:2: 11:2
        return;                          // scope 0 at src/main.rs:11:2: 11:2
    }

    bb3: {
        StorageLive(_7);                 // scope 2 at src/main.rs:6:20: 6:21
        _7 = _2;                         // scope 2 at src/main.rs:6:20: 6:21
        _8 = CheckedMul(_1, _7);         // scope 2 at src/main.rs:6:9: 6:21
        assert(!(_8.1: bool), "attempt to multiply with overflow") -> bb4; // scope 2 at src/main.rs:6:9: 6:21
    }

    bb4: {
        _1 = (_8.0: i64);                // scope 2 at src/main.rs:6:9: 6:21
        StorageDead(_7);                 // scope 2 at src/main.rs:6:21: 6:21
        _9 = CheckedAdd(_2, const 1i64); // scope 2 at src/main.rs:7:9: 7:15
        assert(!(_9.1: bool), "attempt to add with overflow") -> bb5; // scope 2 at src/main.rs:7:9: 7:15
    }

    bb5: {
        _2 = (_9.0: i64);                // scope 2 at src/main.rs:7:9: 7:15
        _6 = ();                         // scope 2 at src/main.rs:5:19: 8:6
        goto -> bb1;                     // scope 2 at src/main.rs:5:5: 8:6
    }
}

On every run through the loop, StorageLive(_4) is executed, and there is no matching StorageDead.

The intended semantics of multiple StorageLive on the same location are unclear. However, evidence suggests that LLVM considers a variable dead before any llvm.lifetime.start, in particular, in llvm.lifetime.start; load; llvm.lifetime.start, LLVM feels free to treat that load as yielding undef. The reference manual says so (however, it doesn't say anything about llvm.lifetime.start; load; llvm.lifetime.end; llvm.lifetime.start being fine, so it seems incomplete), and @eddyb managed to actually obtain UB from a redundant llvm.lifetime.start in

#![feature(link_llvm_intrinsics, untagged_unions)]

extern {
    #[link_name = "llvm.lifetime.start"]
    fn MyStorageLive(size: usize, ptr: *const u8);
}

#[allow(unions_with_drop_fields)]
union Foo<T> { x: T }

fn main() {
    unsafe {
        let mut x = Foo { x: String::new() };
        while x.x.len() < 10 {
            x.x.push('a');
            assert_eq!(std::mem::size_of_val(&x.x), 24);
            MyStorageLive(24, &x.x as *const _ as *const _);
            println!("{}", &x.x[..])
        }
    }
}

Playpen
If you comment out the MyStorageLive, this prints some a's. If you leave it in, nothing is printed.

This suggests we should rather not permit "redundant" StorageLive, and hence the program at the beginning of this report should be translated such that _4 is marked live before the loop, rather than inside the loop.

@RalfJung
Copy link
Member Author

RalfJung commented Jun 2, 2017

I suppose a somewhat weaker interpretation would be for llvm.lifetime.start to kill the current value in the memory, even if it is already live. (This has been suggested by @arielb1.)
Then the code above would be correct. I guess part of the question is how conservative we want to be here.

@nikomatsakis nikomatsakis added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Jun 2, 2017
@RalfJung RalfJung changed the title StorageLive emitted incorrectly for loop conditionals Semantics of StorageLive in loops Jun 2, 2017
@RalfJung
Copy link
Member Author

RalfJung commented Jun 2, 2017

FYI, I implemented the above suggestion in Miri, and the test suite passes. Now that's not a whole lot of code on the grand scheme of things, but this does invoke some interesting unsafe code in Vec and the start lang item, so it's better than nothing.

@Mark-Simulacrum Mark-Simulacrum added C-question C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. and removed C-question labels Jul 27, 2017
@RalfJung RalfJung added the A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. label May 18, 2019
@RalfJung
Copy link
Member Author

Accidental triage (because I forgot about this and just rediscovered the issue^^): the situation is unchanged.

bors added a commit that referenced this issue Jun 26, 2019
…sakis

Clean up MIR drop generation

* Don't assign twice to the destination of a `while` loop containing a `break` expression
* Use `as_temp` to evaluate statement expression
* Avoid consecutive `StorageLive`s for the condition of a `while` loop
* Unify `return`, `break` and `continue` handling, and move it to `scopes.rs`
* Make some of the `scopes.rs` internals private
* Don't use `Place`s that are always `Local`s in MIR drop generation

Closes #42371
Closes #61579
Closes #61731
Closes #61834
Closes #61910
Closes #62115
@RalfJung
Copy link
Member Author

@matthewjasper I don't think this is fixed. I am still seeing already-live locals marked as live if I make Miri check for that.

You can see that in this example: the MIR with your patch is

Expand MIR
fn  index_for_loop() -> usize {
    let mut _0: usize;                   // return place in scope 0 at rust.rs:1:28: 1:33
    let mut _1: usize;                   // "sum" in scope 0 at rust.rs:2:9: 2:16
    let mut _3: std::ops::Range<usize>;  // in scope 0 at rust.rs:4:14: 4:24
    let mut _4: std::ops::Range<usize>;  // in scope 0 at rust.rs:4:14: 4:24
    let mut _5: usize;                   // in scope 0 at rust.rs:4:17: 4:24
    let mut _6: &[usize];                // in scope 0 at rust.rs:4:17: 4:18
    let mut _7: &[usize; 4];             // in scope 0 at rust.rs:4:17: 4:18
    let mut _10: std::option::Option<usize>; // in scope 0 at rust.rs:4:14: 4:24
    let mut _11: &mut std::ops::Range<usize>; // in scope 0 at rust.rs:4:14: 4:24
    let mut _12: &mut std::ops::Range<usize>; // in scope 0 at rust.rs:4:14: 4:24
    let mut _13: isize;                  // in scope 0 at rust.rs:4:9: 4:10
    let mut _15: usize;                  // in scope 0 at rust.rs:4:9: 4:10
    let mut _17: usize;                  // in scope 0 at rust.rs:5:16: 5:20
    let mut _18: usize;                  // in scope 0 at rust.rs:5:18: 5:19
    let mut _19: usize;                  // in scope 0 at rust.rs:5:16: 5:20
    let mut _20: bool;                   // in scope 0 at rust.rs:5:16: 5:20
    let mut _21: (usize, bool);          // in scope 0 at rust.rs:5:9: 5:20
    scope 1 {
        let _2: [usize; 4];              // "a" in scope 1 at rust.rs:3:9: 3:10
        scope 2 {
            let mut _8: std::ops::Range<usize>; // "iter" in scope 2 at rust.rs:4:14: 4:24
            scope 3 {
                let mut _9: usize;       // "__next" in scope 3 at rust.rs:4:14: 4:24
                scope 4 {
                    let _14: usize;      // "val" in scope 4 at rust.rs:4:9: 4:10
                    let _16: usize;      // "i" in scope 4 at rust.rs:4:9: 4:10
                    scope 5 {
                    }
                    scope 6 {
                    }
                }
            }
        }
    }

    bb0: {
        StorageLive(_1);                 // bb0[0]: scope 0 at rust.rs:2:9: 2:16
        _1 = const 0usize;               // bb0[1]: scope 0 at rust.rs:2:19: 2:20
                                         // ty::Const
                                         // + ty: usize
                                         // + val: Scalar(0x0000000000000000)
                                         // mir::Constant
                                         // + span: rust.rs:2:19: 2:20
                                         // + ty: usize
                                         // + literal: Const { ty: usize, val: Scalar(0x0000000000000000) }
        StorageLive(_2);                 // bb0[2]: scope 1 at rust.rs:3:9: 3:10
        _2 = [const 0usize, const 10usize, const 20usize, const 30usize]; // bb0[3]: scope 1 at rust.rs:3:13: 3:28
                                         // ty::Const
                                         // + ty: usize
                                         // + val: Scalar(0x0000000000000000)
                                         // mir::Constant
                                         // + span: rust.rs:3:14: 3:15
                                         // + ty: usize
                                         // + literal: Const { ty: usize, val: Scalar(0x0000000000000000) }
                                         // ty::Const
                                         // + ty: usize
                                         // + val: Scalar(0x000000000000000a)
                                         // mir::Constant
                                         // + span: rust.rs:3:17: 3:19
                                         // + ty: usize
                                         // + literal: Const { ty: usize, val: Scalar(0x000000000000000a) }
                                         // ty::Const
                                         // + ty: usize
                                         // + val: Scalar(0x0000000000000014)
                                         // mir::Constant
                                         // + span: rust.rs:3:21: 3:23
                                         // + ty: usize
                                         // + literal: Const { ty: usize, val: Scalar(0x0000000000000014) }
                                         // ty::Const
                                         // + ty: usize
                                         // + val: Scalar(0x000000000000001e)
                                         // mir::Constant
                                         // + span: rust.rs:3:25: 3:27
                                         // + ty: usize
                                         // + literal: Const { ty: usize, val: Scalar(0x000000000000001e) }
        StorageLive(_3);                 // bb0[4]: scope 2 at rust.rs:4:14: 4:24
        StorageLive(_4);                 // bb0[5]: scope 2 at rust.rs:4:14: 4:24
        StorageLive(_5);                 // bb0[6]: scope 2 at rust.rs:4:17: 4:24
        StorageLive(_6);                 // bb0[7]: scope 2 at rust.rs:4:17: 4:18
        StorageLive(_7);                 // bb0[8]: scope 2 at rust.rs:4:17: 4:18
        _7 = &_2;                        // bb0[9]: scope 2 at rust.rs:4:17: 4:18
        _6 = move _7 as &[usize] (Pointer(Unsize)); // bb0[10]: scope 2 at rust.rs:4:17: 4:18
        StorageDead(_7);                 // bb0[11]: scope 2 at rust.rs:4:17: 4:18
        _5 = const core::slice::<impl [usize]>::len(move _6) -> bb1; // bb0[12]: scope 2 at rust.rs:4:17: 4:24
                                         // ty::Const
                                         // + ty: for<'r> fn(&'r [usize]) -> usize {core::slice::<impl [usize]>::len}
                                         // + val: Scalar(<ZST>)
                                         // mir::Constant
                                         // + span: rust.rs:4:19: 4:22
                                         // + ty: for<'r> fn(&'r [usize]) -> usize {core::slice::<impl [usize]>::len}
                                         // + literal: Const { ty: for<'r> fn(&'r [usize]) -> usize {core::slice::<impl [usize]>::len}, val: Scalar(<ZST>) }
    }

    bb1: {
        StorageDead(_6);                 // bb1[0]: scope 2 at rust.rs:4:23: 4:24
        (_4.0: usize) = const 0usize;    // bb1[1]: scope 2 at rust.rs:4:14: 4:24
                                         // ty::Const
                                         // + ty: usize
                                         // + val: Scalar(0x0000000000000000)
                                         // mir::Constant
                                         // + span: rust.rs:4:14: 4:15
                                         // + ty: usize
                                         // + literal: Const { ty: usize, val: Scalar(0x0000000000000000) }
        (_4.1: usize) = move _5;         // bb1[2]: scope 2 at rust.rs:4:14: 4:24
        StorageDead(_5);                 // bb1[3]: scope 2 at rust.rs:4:23: 4:24
        _3 = const <std::ops::Range<usize> as std::iter::IntoIterator>::into_iter(move _4) -> bb2; // bb1[4]: scope 2 at rust.rs:4:14: 4:24
                                         // ty::Const
                                         // + ty: fn(std::ops::Range<usize>) -> <std::ops::Range<usize> as std::iter::IntoIterator>::IntoIter {<std::ops::Range<usize> as std::iter::IntoIterator>::into_iter}
                                         // + val: Scalar(<ZST>)
                                         // mir::Constant
                                         // + span: rust.rs:4:14: 4:24
                                         // + ty: fn(std::ops::Range<usize>) -> <std::ops::Range<usize> as std::iter::IntoIterator>::IntoIter {<std::ops::Range<usize> as std::iter::IntoIterator>::into_iter}
                                         // + literal: Const { ty: fn(std::ops::Range<usize>) -> <std::ops::Range<usize> as std::iter::IntoIterator>::IntoIter {<std::ops::Range<usize> as std::iter::IntoIterator>::into_iter}, val: Scalar(<ZST>) }
    }

    bb2: {
        StorageDead(_4);                 // bb2[0]: scope 2 at rust.rs:4:23: 4:24
        StorageLive(_8);                 // bb2[1]: scope 2 at rust.rs:4:14: 4:24
        _8 = move _3;                    // bb2[2]: scope 2 at rust.rs:4:14: 4:24
        goto -> bb3;                     // bb2[3]: scope 3 at rust.rs:4:5: 6:6
    }

    bb3: {
        StorageLive(_9);                 // bb3[0]: scope 3 at rust.rs:4:14: 4:24
        StorageLive(_10);                // bb3[1]: scope 4 at rust.rs:4:14: 4:24
        StorageLive(_11);                // bb3[2]: scope 4 at rust.rs:4:14: 4:24
        StorageLive(_12);                // bb3[3]: scope 4 at rust.rs:4:14: 4:24
        _12 = &mut _8;                   // bb3[4]: scope 4 at rust.rs:4:14: 4:24
        _11 = _12;                       // bb3[5]: scope 4 at rust.rs:4:14: 4:24
        _10 = const <std::ops::Range<usize> as std::iter::Iterator>::next(move _11) -> bb4; // bb3[6]: scope 4 at rust.rs:4:14: 4:24
                                         // ty::Const
                                         // + ty: for<'r> fn(&'r mut std::ops::Range<usize>) -> std::option::Option<<std::ops::Range<usize> as std::iter::Iterator>::Item> {<std::ops::Range<usize> as std::iter::Iterator>::next}
                                         // + val: Scalar(<ZST>)
                                         // mir::Constant
                                         // + span: rust.rs:4:14: 4:24
                                         // + ty: for<'r> fn(&'r mut std::ops::Range<usize>) -> std::option::Option<<std::ops::Range<usize> as std::iter::Iterator>::Item> {<std::ops::Range<usize> as std::iter::Iterator>::next}
                                         // + literal: Const { ty: for<'r> fn(&'r mut std::ops::Range<usize>) -> std::option::Option<<std::ops::Range<usize> as std::iter::Iterator>::Item> {<std::ops::Range<usize> as std::iter::Iterator>::next}, val: Scalar(<ZST>) }
    }

    bb4: {
        StorageDead(_11);                // bb4[0]: scope 4 at rust.rs:4:23: 4:24
        _13 = discriminant(_10);         // bb4[1]: scope 4 at rust.rs:4:9: 4:10
        switchInt(move _13) -> [0isize: bb5, 1isize: bb7, otherwise: bb6]; // bb4[2]: scope 4 at rust.rs:4:9: 4:10
    }

    bb5: {
        StorageDead(_12);                // bb5[0]: scope 4 at rust.rs:4:23: 4:24
        StorageDead(_10);                // bb5[1]: scope 4 at rust.rs:4:23: 4:24
        StorageDead(_9);                 // bb5[2]: scope 3 at rust.rs:6:5: 6:6
        StorageDead(_8);                 // bb5[3]: scope 2 at rust.rs:6:5: 6:6
        StorageDead(_3);                 // bb5[4]: scope 2 at rust.rs:4:23: 4:24
        _0 = _1;                         // bb5[5]: scope 2 at rust.rs:7:5: 7:8
        StorageDead(_2);                 // bb5[6]: scope 1 at rust.rs:8:1: 8:2
        StorageDead(_1);                 // bb5[7]: scope 0 at rust.rs:8:1: 8:2
        return;                          // bb5[8]: scope 0 at rust.rs:8:2: 8:2
    }

    bb6: {
        unreachable;                     // bb6[0]: scope 4 at rust.rs:4:14: 4:24
    }

    bb7: {
        StorageLive(_14);                // bb7[0]: scope 4 at rust.rs:4:9: 4:10
        _14 = ((_10 as Some).0: usize);  // bb7[1]: scope 4 at rust.rs:4:9: 4:10
        StorageLive(_15);                // bb7[2]: scope 5 at rust.rs:4:9: 4:10
        _15 = _14;                       // bb7[3]: scope 5 at rust.rs:4:9: 4:10
        _9 = move _15;                   // bb7[4]: scope 5 at rust.rs:4:9: 4:10
        StorageDead(_15);                // bb7[5]: scope 5 at rust.rs:4:9: 4:10
        StorageDead(_14);                // bb7[6]: scope 4 at rust.rs:4:9: 4:10
        StorageDead(_12);                // bb7[7]: scope 4 at rust.rs:4:23: 4:24
        StorageDead(_10);                // bb7[8]: scope 4 at rust.rs:4:23: 4:24
        StorageLive(_16);                // bb7[9]: scope 4 at rust.rs:4:9: 4:10
        _16 = _9;                        // bb7[10]: scope 4 at rust.rs:4:14: 4:24
        StorageLive(_17);                // bb7[11]: scope 6 at rust.rs:5:16: 5:20
        StorageLive(_18);                // bb7[12]: scope 6 at rust.rs:5:18: 5:19
        _18 = _16;                       // bb7[13]: scope 6 at rust.rs:5:18: 5:19
        _19 = const 4usize;              // bb7[14]: scope 6 at rust.rs:5:16: 5:20
                                         // ty::Const
                                         // + ty: usize
                                         // + val: Scalar(0x0000000000000004)
                                         // mir::Constant
                                         // + span: rust.rs:5:16: 5:20
                                         // + ty: usize
                                         // + literal: Const { ty: usize, val: Scalar(0x0000000000000004) }
        _20 = Lt(_18, _19);              // bb7[15]: scope 6 at rust.rs:5:16: 5:20
        assert(move _20, "index out of bounds: the len is move _19 but the index is _18") -> bb8; // bb7[16]: scope 6 at rust.rs:5:16: 5:20
    }

    bb8: {
        _17 = _2[_18];                   // bb8[0]: scope 6 at rust.rs:5:16: 5:20
        _21 = CheckedAdd(_1, move _17);  // bb8[1]: scope 6 at rust.rs:5:9: 5:20
        assert(!move (_21.1: bool), "attempt to add with overflow") -> bb9; // bb8[2]: scope 6 at rust.rs:5:9: 5:20
    }

    bb9: {
        _1 = move (_21.0: usize);        // bb9[0]: scope 6 at rust.rs:5:9: 5:20
        StorageDead(_17);                // bb9[1]: scope 6 at rust.rs:5:19: 5:20
        StorageDead(_16);                // bb9[2]: scope 4 at rust.rs:6:5: 6:6
        StorageDead(_9);                 // bb9[3]: scope 3 at rust.rs:6:5: 6:6
        goto -> bb3;                     // bb9[4]: scope 3 at rust.rs:4:5: 6:6
    }
}

You can see that there is a StorageLive(_18) each time around the loop, and no corresponding StorageDead.

@RalfJung RalfJung reopened this Jun 26, 2019
@RalfJung
Copy link
Member Author

In fact, MIR generation in that example does not seem to change at all with your PR.

@RalfJung
Copy link
Member Author

Note-mostly-to-self: the branch to make Miri check if this issue is resolved is at https://github.com/RalfJung/rust/tree/miri-storage-live.

Centril added a commit to Centril/rust that referenced this issue Jul 10, 2019
…=pnkfelix

Sometimes generate storage statements for temporaries with type `!`

Closes rust-lang#62165
cc rust-lang#42371
@RalfJung RalfJung added the A-MIR Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html label Apr 14, 2020
@RalfJung
Copy link
Member Author

Alive2 is a tool to check LLVM optimizations correct, and it has to give an operational semantics to LLVM IR. What they do for lifetime.start/end is interesting:

FYI: In Alive2, the semantics of alloca depends on whether it has lifetime intrisic uses at parsing time. :)
If alloca has any uses with lifetime intrinsics, it is truly allocated at lifetime.start. Before that, it is assigned a block id, but it does not occupy the space and accessing it is UB.
If alloca is not used by any lifetime intrinsics, it is immediately allocated at the definition.

This is close to what Miri does, except Miri re-assigns a new block ID and address each time the lifetime of a local ends and starts again. (Compiling from Miri to Alive2 semantics is correct but not the other way around, so we are good.)

Also:

  • Having lifetime.start twice on the same alloca does not reallocate the block

and

When lifetime.start/end is in a loop:

p = alloca
q = alloca
// 1
for (..) {
lifetime.start(p)
// 2
lifetime.end(p)
// 3
}

In 1, p's block id is set (this was needed because getelementptr p could be moved into/out of the lifetime pairs), but accessing p is UB.
In 2, p is alive, and its address is disjoint with any other alloca like q. In any iteration, p's integer address is fixed.
In 3, p's block is marked as free, and accessing it is UB.

In summary, p's integer addresses/alignment is fixed at definition, but it is simply updated as usable/unusable at lifetime.start/end. :)

@aqjune
Copy link

aqjune commented Jun 22, 2020

Hi, maybe a silly question - is there a reason that Rust emits StorageLive(_4); at bb0 instead of bb1?

I'm wondering whether it makes sense for LLVM to typecheck whether a program has lifetime intrinsics arranged within well-known patterns only. If the usage pattern was too complex, LLVM would already have given up using the pairs for optimization.

@RalfJung
Copy link
Member Author

Hi, maybe a silly question - is there a reason that Rust emits StorageLive(_4); at bb0 instead of bb1?

You mean the other way around? It seems to be in bb1.

I'm afraid I do not know why we emit StorageLive the way we do. Cc @matthewjasper

@tmiasko
Copy link
Contributor

tmiasko commented Nov 21, 2020

The lifetime.start marks a stack slot as in-scope, and overwrites it with undef, so a direct translation of the example MIR to LLVM would be correct. BTW, StackColoring, which is primary user of lifetime intrinsics, describes the effects of those intrinsics in more detail than the reference.

The MIR now has a StorageDead before next iteration of the loop, regardless.

@RalfJung
Copy link
Member Author

The lifetime.start marks a stack slot as in-scope, and overwrites it with undef, so a direct translation of the example MIR to LLVM would be correct. BTW, StackColoring, which is primary user of lifetime intrinsics, describes the effects of those intrinsics in more detail than the reference.

Would be good to have the LangRef updated then... where can I read more about StackColoring?

The MIR now has a StorageDead before next iteration of the loop, regardless.

Oh, nice! I'll try to see if I can now adjust StorageLive to be UB when called on an already-live stack slot.

@tmiasko
Copy link
Contributor

tmiasko commented Nov 21, 2020

Would be good to have the LangRef updated then... where can I read more about StackColoring?

https://github.com/rust-lang/llvm-project/blob/ee1617457899ef2eb55dcf7ee2758b4340b6533f/llvm/lib/CodeGen/StackColoring.cpp#L160-L177

@RalfJung
Copy link
Member Author

I love the introduction^^

// The effect of these intrinsics seems to be as follows (maybe I should
// specify this in the reference?):

Seems like everyone is very sure what these things do... doesn't make me worried about LLVM as the foundation of Rust at all...^^

@RalfJung
Copy link
Member Author

Currently this fails to evaluate many constants; I suspect that is related to @oli-obk's #78679. I'll try again once that landed.

For future reference, this is the branch.

@aqjune
Copy link

aqjune commented Dec 26, 2020

(I'm not sure whether here is a good place to ask this, but) can Rust generate lifetime.start/end with non-zero offset?
I found this old bug report from LLVM bugzilla: https://bugs.llvm.org/show_bug.cgi?id=27999 . Its fix patch shows a simple example.
Rust was fiercely changing at that point; I wonder whether this is still happening.

@RalfJung
Copy link
Member Author

Syntactically, the Rust/MIR equivalent of these intrinsics can only be applied to locals, not to a part of a place/local.

However, fixing #71416 might require adding offsets to dynamically-sized alloca to make sure the alignment is right. Not sure if there is a way to make the lifetime intrinsics still refer to the original alloca as opposed to the properly aligned ptr.

@aqjune
Copy link

aqjune commented Dec 29, 2020

Okay, I understood the issue.
I feel like this should be allowed, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. A-MIR Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants