[...]
The above analysis ignores the fact that, if one conflates "value
type" with "stack-allocated", then value types can wind up having
_longer_ lifetimes than reference types. In particular, they won't be
released until the stack frame in which they were allocated is removed.
Why can't an aggressive compiler reuse the stack space used for a
stack-allocated object once it's no longer live, before the whole frame
is popped?
I've never seen a compiler that reuses local variable slots _across_
method calls. Doing so within a given method call is fine and reasonably
common AFAIK, but it doesn't change the fact that that stack slot is
still allocated until the method where it was used returns.
And I'm skeptical that it's feasible to reuse the stack slot when some
callee of the method owning that frame hasn't returned yet. How is the
compiler supposed to know when it generates the code for some other
method that the method where the stack slot to be reused will always be
present in the call chain, and always in the same location?
I would much rather see the compiler writer spend more effort on
determining "definite assignment" than attempt to solve the static
analysis problem that's required to accomplish what you suggest across
method calls. And in any case, even if it could be accomplished in some
scenarios, it's not possible to always guarantee the conditions that
would be required to reuse the stack slot. So the fact that the
optimization might occur some of the time doesn't change the fact that
making something a value type can't ensure it can occur _all_ of the time.
Hence, "value type" is not a suitable way to describe object lifetime.
Which is my point. It can correlate with object lifetime, but the real
reason to have value types has nothing to do with object lifetime.
I think it can do this at the point the variable holding the object in
the method execution owning that frame dies; the point at which a
heap-allocated version of the object becomes dead cannot be any earlier
than that.
The heap-allocated version of the object can be released from the heap
at that point though, regardless of the current depth of call stack or
where the execution pointer is.
Even if it's practical to write a compiler as aggressive with
optimizations as you suggest, that stack slot is still present for
_something_. What happens when _none_ of the variables that were sharing
that stack slot are no longer needed? Unlike with the heap-allocated
object, that stack slot remains and cannot be freed until the method
where it was declared has returned.
Again, "value type" isn't a reliable way to describe object lifetime,
nor is that IMHO something that the author of managed code (such as Java
or .NET code) should be thinking about anyway. If you want that kind of
fine-grained control over object lifetime, I don't think a language that
uses garbage collection is really the best choice in the first place
(with the corollary being that most people over-estimate their need to
control object lifetime, given a sophisticated enough garbage
collection, such as found in Java or .NET).