If we’re only talking about accesses, then heap vs. locals (or, if you prefer to talk about that, the means of memory allocation) is not inherently relevant. The compiler is, and absolutely should be, allowed to remove (or introduce, or shuffle around) memory accesses no matter the source. More importantly, it’s absolutely possible for LLVM to deduce that a load through a pointer yields uninitialzed bytes even without seeing where that memory is allocated. Consider this function for example:
unsafe fn make_and_read_undef(p: *mut u8) -> u8 {
let u = mem::uninitialized();
*p = u;
*p
}
Even leaving aside any knowledge of uninitialized memory, it ought to be possible to optimize that code into this:
unsafe fn make_and_read_undef(p: *mut u8) -> u8 {
let u = mem::uninitialized();
*p = u;
u // <- changed
}
… which has the effect of making the memory access *p yield uninitialized bytes, regardless of how and where it was allocated.
So as long as “all memory is the same” and there is some source of uninitialized memory, any memory can be made uninitialized, unless we start randomly restricting useful optimizations such as store forwarding.
Again, whether something is UB or not is a semantic question, and so if we want to make certain operations defined or not, we need to lay out the criteria in terms of Rust semantics. What some compiler does or doesn’t know, and what it does with that knowledge, is only relevant when testing whether said compiler is faithful to the semantics. These two concerns only intersect when it comes to judging how practical some proposed semantics are.
I tried to guess at what semantics you are proposing by reading it as “reading uninit stack memory is UB, reading uninit heap memory is fine” but apparently that was wrong. I apologize and would like to learn what semantics you are proposing. I believe that would be easier for me if you explained these semantics separately from how they can or cannot be implemented on LLVM.
This is not entirely true, LLVM knows about the symbol malloc (among others) and will treat it as the C function of the same name. So Rust code using the system allocator will also be affected by this, assuming some interprocedural analysis or inlining (which we’d like to have, at the very least to avoid additional overhead from wrapper functions compared to calling malloc directly—zero cost abstractions and all that jazz) .