Why couldn't it be done? Like sure, it won't happen in every case (e.g. too big value classes), but for a typical local variable of a value type, it's a trivial optimization to make it stack allocated. Even now it happens quite often (requires escape analysis), but the semantics change of value classes allows for it to be done "freely".
C# stackalloc returns a ‘ref struct’ which has certain restrictions and would be a Q-world type.
Java chose to go with an L-world implementation where everything is still a reference type on the heap, but memory management is more efficient via flattening. That’s why it’s a ‘value class’ and not a ‘struct’.
Primitive wrappers are scalarized into registers.
Stack allocated value types would have different semantics and is incompatible with the L-world implementation. Sure, they could implement it, but it would be the Q-world implementation that they decided to forego.
They can do automatic stack allocation for optimization via escape analysis as you mentioned. But, stackalloc is user allocated.
Why couldn't it be done? Like sure, it won't happen in every case (e.g. too big value classes), but for a typical local variable of a value type, it's a trivial optimization to make it stack allocated. Even now it happens quite often (requires escape analysis), but the semantics change of value classes allows for it to be done "freely".