-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Allow to allocate objects on the stack #240
Comments
@ygc369 What form would this take as a language feature? |
@gafter
public void Runner(transient MyProcessor processor)
{
(...)
var myProcessor = processor; // is equivalent to an explicit declaration of:
transient MyProcessor myProcessor2 = processor;
// We can access any method/property/field on this transient object
processor.Run(...);
// But we cannot store the transient variable into a non transient avariable
MyProcessor test = processor; // won't compile
// or cannot store it in a field/property
myProcessorField = processor; // won't compile
(...)
}
unsafe {
// Allocate an array of int
int* pInts = stackalloc int[5];
// Allocate an array of Vector3 struct (which cannot contain any reference type)
Vector3* v = stackalloc Vector3[4];
}
// Create a new instance of MyProcessor on the stack
transient MyProcessor processor = stackalloc MyProcessor(...);
processor.Run(...);
/* Assume that the lambda is allocated on the stack excplitely when
it is marked as `transient` or prefixed by `stackalloc`? */
var matchElement = list.FirstOrDefault(transient t => t.Name == name);
public class MyProcessor
{
public MyProcessor Bis;
public void Run()
{
// this can be used in the context of a transient callsite but here, we cannot
// verify this. It would mean that the `store` variable could live more than
// the callsite (and will just result in a program crash)
Bis = this;
(...)
}
}
var processor = stackalloc MyProcessor();
processor.Run();
// Bam! The processorBis variable is no longer a transient variable
var processorBis = processor.Bis;
|
@xoofx do |
@benaadams |
@benaadams not really. A ref to a reference types doesn't tell you that this reference was allocated on the stack (it could be a ref to a heap alloc field). When passing a transient reference to a parameter method, you can't rely on ref, you need to have a semantic that says "you should not store this reference anywhere except on the stack in a transient variable"... |
Before last summer, I heard that there was an internship in the .NET team supposed to work on this... (not strictly the transient thing, afair, it was to try to get escape analysis working for very basic cases, but not for parameter passed to virtual methods...etc.) but not sure what happened to this... My blog post experiment provides a very primitive specs and a functional prototype, but it may requires a more formalized RFC, and someone dedicated from MSFT .NET Team to follow this (as it has an impact on the runtime and the language) |
@xoofx I believe you're talking about dotnet/coreclr#6653. No idea if there is any followup to that. |
@svick good catch, yes, that's this one 😉 |
In my humble opinion stack escape analysis could be implemented for the simplest cases as a starting point, I don't like at a developer using a high level language to have to think where allocate objects thins should be the work of the compiler... |
@fanoI in my opinion we should have both escape analysis and assurance something is allocated from stack, like this proposal is proposing. One thing I agree with you is that if it's hard to know if something escapes, then just do simplest cases and start from there. If someone is creating software where you need absolute guarantees that something doesn't allocate, you need way to enforce stack allocation, like game loop, real-time audio or maybe .net framework itself. I think when using stack allocation keyword/attribute for method parameter, it should only tell that it's safe to call it with stack allocated class, you should be able to use class allocated from heap as well. |
I wondered about this years ago. It struck me that the language designers could have done something more elegant than what they eventually did do. Specifically they chose to associate how something is allocated with it's type and I think this could have been done in a better way. Rather than having struct and class for defining types, they could have simply used the keyword type and leave the specifics of allocation to the point of the declaration. This way we'd simply create types and we'd decide whether we want these on heap or stack when we declare them.
Instead we have the keywords class and struct that not only define a type's characteristics but also how it is to be allocated in memory. Currently in C# a class and a struct with identical member fields are structurally the same except that instances of the class carry a runtime metadata header. There's no way this will change in C# but I wonder if this approach would have been better than the one chosen, where we must declare a type and its allocation mode together (struct or class). Today if we have a type that (for whatever reasons) must be available both as heap and stack instances we must either declare a class version and a struct version or declare a struct version and then declare a struct member in the class:
The above defines the type in one place and 'reuses' it in the defintion of the class SystemDataC, this is clumsy and inelegant - separating a type defintion from the semantics of how its allocated is IMHO better. |
It's the CLR that strikes a sharp distinction between the two. C# as a language is required to observe that distinction. Not even C++/CLI allows you to arbitrarily decide where a managed type is allocated. |
@Korporal struct doesn't specify its allocation type. A struct can be heap or stack (if boxed or stored as member of class) |
This is not really accurate... First, you can perfectly allocate a struct on the heap directly (it is called boxing, though I agree, unfortunate that the language made the new operator for struct as not a heap allocation...anyway) and you can also (as my prototype above) allocate a class on the stack. So it is not the type that dictates its allocation model as you said. Note that when you are on the heap, you always need a "vtable" (but for struct, it is more used as a "type table") Then, the language made a strict difference between passing by value and passing something by reference. By default, a struct is passed by value, a class by reference. You can pass a struct by reference but you can't pass a class by value (no copy semantics) But a more fundamental difference is the presence of a vtable. A class will always have a vtable, supports inheritance and virtual dispatch (even if it is allocated on the stack). A struct can have a pseudo vtable only when it is allocated on the heap, but it doesn't support inheritance (you can add a loosy inheritance as described in another of my post) and can't have virtual dispatch. What you are looking for is what Go or Rust have been providing. Both are quite "recent" languages, keep in mind that C# is in the footsteps of Java, so don't expect a platform that is more than 15 years old where OOP was king to comeback to a "simpler" type model.... But neither Go or Rust provides data inheritance for their types. They basically don't have any vtable. They are either a value or a reference (with a * for Go or & for Rust). They support both static and dynamic dispatch (that is different from virtual dispatch, while C# also support static dispatch, and interface dispatch which is more costly than virtual). So this is the fundamental difference. Inheritance of data is not allowed and functions/process associated to a data is separated from it. As said by others, C# (and more generally as @HaloFour said, the CLR) is deeply rooted in OO language concepts. We can't really expect any changes at this level of the foundations of a language. |
@HaloFour |
Not necessarily. A struct as a class field also lives on the heap without being boxed. |
The situation in Java is identical to the one in C#. Neither language supports allocating classes on the stack. Neither bytecode has opcodes that can support allocating classes on the stack. There is absolutely no way for a developer to explicitly specify that they want a class allocated on the stack. The difference is in the JIT. I see that you've already opened https://github.com/dotnet/coreclr/issues/1784 and that is where that belongs. I am a proponent of that optimization as well. |
I feel the need to re-iterate Eric Lippert's amazing post: The Truth About Value Types. Specifically because people keep proposing language level changes to something that's only an implementation detail. As HaloFour said, you go to the CoreCLR repo for that, not the language. |
Fyi, work has started on stack-allocated objects. dotnet/coreclr#20251 |
To reduce GC, C# should provide mechanisms to allocate objects on the stack.
(from http://xoofx.com/blog/2015/10/08/stackalloc-for-class-with-roslyn-and-coreclr/)
So besides escape analysis, we want to have syntax to guarantee some objects allocated on the stack. If the stack-allocated object has finalizer, then its finalizer should be executed once its last reference goes out of scope, not need wait until GC finishes.
The text was updated successfully, but these errors were encountered: