What are the conditions where codegen would emit bad code? Do you have a minimal example where julia emits bad machine code?
Are you saying that isinitialized-checks could be optimized away, due to assumptions that are valid in julia’s type system but invalid on the llvm-level? I thought that the optimization pass that would consider optimizing these checks away has access to the llvm-code only, and hence cannot make such mistakes. [edit: This would be really, really scary, and I don’t understand Julia’s internals well enough to be sufficiently certain. Please tell me that such optimizations cannot happen?]
I naively assumed that most internal machinery of julia (especially the garbage collector and type inference) is capable of dealing with uninitialized references (null-pointers) anyway, since these can occur often (e.g. in arrays, partial constructors, etc).
Null-pointers for uninitialized object-references have the extremely tempting properties of being binary compatible with C-code that expects NULL-pointers for missing values (which almost all C and C++ code does), and being quite type-stable (type-inference will never box anything due to possible uninitialized references).
Unfortunately, “nothing” is not represented by a null-pointer in Union{T, Void}, where T is a non-bitstype. Hence, Union{T,Void} always generates different code from the unsafe_store!-deinitialization, and is not binary compatible with the usual C constructs. Using Julia object references instead of pointers in fields give all the nice advantages of memory management by the garbage collector, and play very nice with the standard julia libraries.
An extremely simple example where explicit nulling/deinitialization makes sense, even without C compatibility:
Take the deque in the DataStructures package. They use a doubly linked list of blocks; each block contains a Vector{T}(capacity), and stores the interval (front, back) of valid entries. Assume that T is not bitstype. Initially, all entries are uninitialized (null-pointers); upon removal of an entry, front is incremented or back is decremented. However, the “removed” entries are still visible to the garbage collector and hence kept alive! (they get released when the entire block is destroyed, which might be pretty late)
In order to get rid of them, one would need to overwrite the reference with something. However, deque knows no constructor for a null-object of type T. There is such a constructor (put in a null-pointer if reference type, use uninitialized memory / NOP for bitstype), which is what allows us to write Vector{T}(capacity) in the first place; I was complaining about the lacking availability of this null-constructor for general purpose operations.
You could argue that one should use Union{Void, T} for such cases; however, the fact that even the almost-std-lib people from DataStructures.jl could not be bothered to do this tells us something about the elegance of such an approach (again, using the requirement that this should have zero runtime overhead if types and @inbounds are correctly annotated).
You could also argue that we should modify the underlying array (delete from it). This has [edit: maybe, maybe not, maybe rarely, see youyichao’s comment, who knows much more about this] the unfortunate consequence of occasionally causing memmoves if we delete from the front, and preventing them is the entire point of the deque. The thresholds for memmoving the array contents upon deletion from front are compile-time constants (macros) in array.c, so there is no reasonable way of fiddling with them for the deque, if it is to be written in julia instead of C.