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.