-
Couldn't load subscription status.
- Fork 13.9k
Description
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.